f# - 使用 bind 编写跨世界的异步函数

标签 f# monad-transformers f#+

我有一个运行良好的示例铁路管道:

open FSharpPlus

let funA n =
    if n < 10 then Ok n
    else Error "not less than 10"

let funB n =
    if n < 5 then Ok (n, n * 2)
    else Error "not less than 5"

let funC n = // int -> Result<(int * int), string>
    n
    |> funA
    >>= funB // it works

但是当我想转funB到异步函数,我得到一个编译错误。逻辑上应该不一样。相同的输出/输入...怎么了?

应该怎么做才能让它发挥作用?!

open FSharpPlus

let funA n =
    if n < 10 then Ok n
    else Error "not less than 10"

let funB n = async {
    if n < 5 then return Ok (n, n * 2)
    else return Error "not less than 5" }

let funC n = // int -> Async<Result<(int * int), string>>
    n
    |> funA
    >>= funB // compile error

最佳答案

Same output/input... What's wrong?



不,它们没有相同的输出/输入。

如果您查看 (>>=) 的类型类似于 'Monad<'T> -> ('T -> 'Monad<'U>) -> 'Monad<'U>这是泛型绑定(bind)操作的伪造签名,通常为 Monads 重载。在您的第一个示例中,Monad 是 Result<_,'TError> ,所以你的第一个例子可以重写为:
let funC n = // int -> Result<(int * int), string>
    n
    |> funA
    |> Result.bind funB
Result.bind的签名是 ('T -> Result<'U,'TError>) -> Result<'T,'TError> -> Result<'U,'TError> .如果您考虑一下,这是有道理的。这就像应用替换 Monad<_>Result<_,'TError>你的论点被翻转了,这就是我们使用 |> 的原因.

那么你的函数都是int -> Result<_,'TError>所以类型匹配,这是有道理的(并且有效)。

现在,转到您的第二个代码片段,函数 funB有不同的签名,它有 Async<Result<_,'TError>>所以现在类型不匹配。而且也有道理,不能使用Result的绑定(bind)实现对于 Async .

那么,解决方案是什么?

最简单的解决方案是不要使用绑定(bind),至少不要使用 2 个单子(monad)。你可以将你的第一个函数“提升”到 Async并使用 async.Bind , 使用通用 >>=或标准异步工作流程,但在其中您必须使用手册 match将结果绑定(bind)到第二个函数。

另一种方法更有趣,但也更难理解,它包括使用称为 Monad Transformers 的抽象:
open FSharpPlus.Data

let funC n = // int -> Result<(int * int), string>
    n
    |> (funA >> async.Return >> ResultT)
    >>= (funB >> ResultT)
    |> ResultT.run

所以,我们在这里所做的是我们“提升” funA函数到 Async ,然后我们将其包装在 ResultT 中这是 Result 的单子(monad)变压器, 所以它有一个绑定(bind)操作来处理外部 monad 的绑定(bind),在我们的例子中是 Async .

然后我们简单地包装funB进入 ResultT在函数的最后,我们从 ResultT 展开与 Result.run .

有关 F# 中分层 monad 的更多示例,see these questions

还有其他方法,一些库提供了一些“神奇的工作流程”,它使用临时重载将 monad 与组合 monad(也称为分层 monad)结合起来,因此您编写的代码更少,但推理类型并不容易,因为重载不遵循任何替换规则,您必须查看源代码以了解发生了什么。

注意:这样的编码是一个很好的练习,但在现实生活中也可以考虑使用异常,以免代码过于复杂。

关于f# - 使用 bind 编写跨世界的异步函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57190595/

相关文章:

f# - 如何从 C# 应用程序将 F# 函数传递给另一个 F# 函数?

f# - F# 中的递归映射引用

haskell - F# 中的箭头 "proc"符号

f# - Thoth.Json.Net – 解码器作为仿函数,可能吗?

f# - 为什么在 Observable.merge 的这种用法中顺序很重要?

f# - 为什么这个函数不是尾递归?

html - 如何独立于外部请求只渲染一次 Widget?

haskell - 创建 Reader 和 Maybe Monad(应用仿函数)的组合

haskell - 在 Monad Transformer 中回溯底层 monad

f# - 使用 FSharpPlus 的 Reader monad 转换器示例