Пример преобразователя монад для чтения с помощью FSharpPlus

Я пытаюсь понять читателя преобразователя монад. Я использую FSharpPlus и пытаюсь скомпилировать следующий образец, который сначала читает что-то из среды чтения, затем выполняет некоторые асинхронные вычисления и, наконец, объединяет оба результата:

open FSharpPlus
open FSharpPlus.Data

let sampleReader = monad {
    let! value = ask
    return value * 2
}

let sampleWorkflow = monad {
    do! Async.Sleep 5000
    return 4
}

let doWork = monad {
    let! envValue = sampleReader
    let! workValue = liftAsync sampleWorkflow
    return envValue + workValue
}

ReaderT.run doWork 3 |> Async.RunSynchronously |> printfn "Result: %d"

При этом я получаю ошибку компиляции в строке, где говорится let! value = ask со следующим совершенно бесполезным (по крайней мере для меня) сообщением об ошибке:

Несоответствие ограничения типа при применении типа по умолчанию 'obj' для переменной вывода типа. Для метода op_GreaterGreaterEquals не найдено ни одной перегрузки.

Известный тип возврата: асинхронный

Параметры известного типа: Async) >

Такое ощущение, что где-то просто не хватает какого-то оператора, но я не могу понять.

1 ответ

Решение

Ваш код правильный, но вывод типа F# не так уж и умен в подобных случаях.

Если вы добавите аннотацию типа в sampleReader, она отлично скомпилируется:

let sampleReader : ReaderT<int,Async<_>> = monad {
    let! value = ask
    return value * 2
}

// val sampleReader : FSharpPlus.Data.ReaderT<int,Async<int>> =
//  ReaderT <fun:sampleReader@7>

Обновление:

После прочтения ваших комментариев. Если вы хотите сделать его универсальным, в первую очередь должна быть объявлена ​​ваша функция. inline в противном случае нельзя применить ограничения типа:

let inline sampleReader = monad ...

Но это подводит вас ко второй проблеме: константу нельзя объявить встроенной (на самом деле способ есть, но он слишком сложный), только функции.

Так что проще всего сделать это функцией:

let inline sampleReader () = monad ...

И вот третья проблема: код не компилируется:)

Здесь вы снова можете дать минимальную подсказку для вывода типа, просто чтобы сказать на сайте вызова, что вы ожидаете ReaderT<_,_> будет достаточно:

let inline sampleReader () = monad {
    let! value = ask
    return value * 2
}

let sampleWorkflow = monad {
    do! Async.Sleep 5000
    return 4
}

let doWork = monad {
    let! envValue = sampleReader () : ReaderT<_,_>
    let! workValue = liftAsync sampleWorkflow
    return envValue + workValue
}

ReaderT.run doWork 3 |> Async.RunSynchronously |> printfn "Result: %d"

Вывод:

Определение универсальной функции - не такая тривиальная задача в F#. Если вы посмотрите на исходный код F#+, вы поймете, что я имею в виду.

После запуска вашего примера вы увидите все сгенерированные ограничения и, вероятно, заметите, как увеличилось время компиляции, если ваша функция стала встроенной и универсальной.

Все это указывает на то, что мы доводим систему типов F# до предела.

Хотя F#+ определяет некоторые готовые к использованию универсальные функции, и эти функции иногда можно комбинировать таким образом, чтобы вы создавали свои собственные общие функции, это не является целью библиотеки, я имею в виду, что вы можете, но тогда вы самостоятельно, в некоторых сценариях, таких как исследовательская разработка, это может иметь смысл.

Другие вопросы по тегам