Пример преобразователя монад для чтения с помощью 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#+ определяет некоторые готовые к использованию универсальные функции, и эти функции иногда можно комбинировать таким образом, чтобы вы создавали свои собственные общие функции, это не является целью библиотеки, я имею в виду, что вы можете, но тогда вы самостоятельно, в некоторых сценариях, таких как исследовательская разработка, это может иметь смысл.