Как сохранить тип generi c в выражении вычисления asyn c? - PullRequest
0 голосов
/ 10 июля 2020

Почему тип generi c ограничен единицей измерения и как я могу написать это так, чтобы myFun c правильно набирался как unit -> Async<'t>?

let myFunc (func: unit -> Async<'t>) = // myFunc: unit -> Async<unit>
    async {
        try
            do! Async.Sleep 500
            return! func() // 't constrained to unit here
        with _ex ->
            do! Async.Sleep 200
        
        failwith "failed"
    }

Изменить: я был надеюсь, что из этого получится хорошее минимальное воспроизведение, но вот что я действительно хочу сделать:

let retryUntilTimeout (func: unit -> Async<'t>) timeout =
    async {
        let sw = Stopwatch.StartNew()
        while sw.ElapsedMilliseconds < timeout do
            try
                return! func ()
            with _ex ->
                printfn "failed"
                do! Async.Sleep 200

        raise (TimeoutException())
    }

1 Ответ

1 голос
/ 10 июля 2020

Каждое выражение должно иметь определенный тип, включая выражение try ... with. В данном случае наличие определенного типа означает, что ветви try и with должны иметь один и тот же тип.

Но в вашем коде ветвь try возвращает 't, а ветвь with возвращает unit. Но неважно: поскольку 't может быть чем угодно, мы можем просто объединить его с unit, и теперь все выражение try ... with также возвращает unit. Проблема решена!

Чтобы исправить это, вам нужно, чтобы ветвь with также возвращала 't. Как это сделать, где взять 't? Боюсь, я не смогу тебе помочь, ты должен решить.

let myFunc (func: unit -> Async<'t>) = 
    async {
        try
            do! Async.Sleep 500
            return! func()
        with _ex ->
            do! Async.Sleep 200
            return someOtherValueOfT
        
        failwith "failed"
    }

Но, судя по общей форме кода, я подозреваю , что на самом деле вы имели в виду поместить failwith в ветку with. Поскольку failwith может иметь любой тип, который вы хотите, компилятор будет доволен сохранением типа всего блока try ... with как 't:

let myFunc (func: unit -> Async<'t>) =
    async {
        try
            do! Async.Sleep 500
            return! func()
        with _ex ->
            do! Async.Sleep 200   
            return failwith "failed"
    }

(обратите внимание, что теперь есть дополнительный return ключевое слово внутри with - это потому, что без return предполагается, что блок asyn c имеет тип unit, и мы возвращаемся к той же проблеме)

Отвечая на ваши комментарии, похоже, что вы на самом деле пытаетесь сделать бесконечную итерацию, ограниченную таймаутом, и вы хотите продолжать итерацию, пока есть ошибка.

Проблема с вашим кодом, однако, заключается в том, что он на самом деле не будет работать так, как вы ожидаете, даже если вы каким-то образом вернете значение 't из ветки with. Это связано с тем, что ключевое слово return! не «прерывает выполнение», как в C# (формально известное как «ранний возврат»), а просто запускает заданный func() и делает его значение результатом текущего блока. .

Фактически, если вы полностью удалите ветвь with, проблема не исчезнет: компилятор по-прежнему будет настаивать на том, чтобы тип func() был unit. Это связано с тем, что вызов находится внутри while l oop, а тело while l oop не должно возвращать значение, иначе это значение будет отброшено, поэтому должна быть ошибка некоторых Сортировать. Но можно выбросить unit, чтобы компилятор это разрешил.

Хороший способ сделать то, что вы пытаетесь сделать, - использовать рекурсию:

let retryUntilTimeout (func: unit -> Async<'t>) timeout =
    let sw = Stopwatch.StartNew()

    let rec loop () = async { 
        if sw.ElapsedMilliseconds > timeout 
        then 
            return raise (TimeoutException())
        else
            try
                return! func ()
            with _ex ->
                printfn "failed"
                do! Async.Sleep 200
                return! loop ()
    }

    loop ()

Здесь, функция loop сначала проверяет тайм-аут и выбрасывает (обратите внимание также на ключевое слово return - это для удовлетворения системы типов), в противном случае запускает func(), но если это не удается, немного ждет и вызывает себя рекурсивно, тем самым продолжая

Эта общая схема (или все, что на ней построено) - это то, как моделируются все итерации в функциональном программировании. Забудьте о петлях, они бесполезны.

...