f# - 将 "bind"与异步函数一起使用

标签 f# monad-transformers

假设我有一些返回 Async<Result<string>> 的函数:

let getData id = async {
   return Ok (string id)
}

现在这个函数的输入是另一个函数的结果,该函数返回 Result<int> .

我正在努力思考如何将 2 与 Result.bind 组合在一起在异步 CE 内。

例如:

let main = async {
    let id = Ok 123
    let! x = id |> Result.bind getData

    return x
}

这不起作用,我收到错误:

error FS0001: Type mismatch. Expecting a
    'Result<int,'a> -> Async<'b>'    
but given a
    'Result<int,'a> -> Result<'c,'a>'   

或者如果我不使用let!我得到并使用 let

error FS0001: Type mismatch. Expecting a
    'int -> Result<'a,'b>'    
but given a
    'int -> Async<Result<string,'c>>

我看到一些答案说不要使用 Result<'a>并让异步异常处理完成艰苦的工作,但我遇到了与 Option<'a> 相同的问题和Option.bind .

我知道我可以使用 Option.isSome/isNone和/或写我自己的 isOk/isError Result 的函数,但我觉得我不应该这样做。

关于组合这样的东西的最佳方式有什么想法吗?

最佳答案

问题是Result.bind不能与 getData 一起使用因为签名不匹配。 Result.bind期望一个函数产生 Result<>但是getData产生 Async<Result<_,_>> 。您需要一个bind对于 Async<Result<_,_>> .

定义 AsyncResult.bind Async<Result<_,_>> 的功能像这样:

module AsyncResult =
    let bind  fRA  vRA = async { 
        let! vR       = vRA
        match   vR with
        | Ok    v -> return! fRA v
        | Error m -> return  Error m 
    }

现在您可以编写您的 getData具有返回 Result 的函数的函数像这样:

let composed p = resultFunction p |> async.Return |> AsyncResult.bind getData

如果您为 AsyncResult 定义了 CE,那么您可以像这样编写它:

let composed2 p =  asyncResult {
    let! id = resultFunction p |> async.Return
    return! getData id
}
<小时/>

这是我用于处理 Async<Result<>> 的完整实现。

首先是 Result 的一些有用定义:

module Result =
    open Result

    let rtn                          = Ok
    let toOption                   r = r   |> function Ok v -> Some v |       _ -> None
    let defaultWith              f r = r   |> function Ok v ->      v | Error e -> f e
    let defaultValue             d r = r   |> function Ok v ->      v | Error _ -> d
    let failIfTrue               m v = if     v then m |> Error  else Ok () 
    let failIfFalse              m v = if not v then m |> Error  else Ok () 
    let iter                  fE f r = r   |> map f |> defaultWith fE : unit
    let get                        r = r   |>          defaultWith (string >> failwith)
    let ofOption              f   vO = vO  |> Option.map Ok           |> Option.defaultWith (f >> Error)
    let insertO                  vRO = vRO |> Option.map(map Some)    |> Option.defaultWith(fun () -> Ok None)
    let absorbO               f  vOR = vOR |> bind (ofOption f)

...以及 Async :

module Async =
    let inline rtn   v    = async.Return v
    let inline bind  f vA = async.Bind(  vA, f)
    let inline map   f    = bind (f >> rtn)
    let inline iterS (f: 'a->unit) = map f >> Async.RunSynchronously
    let inline iterA f             = map f >> Async.Start

...现在是AsyncResult :

type AsyncResult<'v, 'm> = Async<Result<'v, 'm>>

module AsyncResult =
    let mapError fE v  = v |> Async.map (Result.mapError fE)

    let rtn        v   = async.Return(Ok v  )
    let rtnR       vR  = async.Return    vR
    let iterS fE f vRA = Async.iterS (Result.iter fE f) vRA
    let iterA fE f vRA = Async.iterA (Result.iter fE f) vRA
    let bind  fRA  vRA = async { 
        let! vR       = vRA
        match   vR with
        | Ok    v -> return! fRA v
        | Error m -> return  Error m 
    }
    let inline map  f m = bind  (f >> rtn) m            
    let rec whileLoop cond fRA =
        if   cond () 
        then fRA  () |> bind (fun () -> whileLoop cond fRA)
        else rtn  ()
    let (>>=)                              v f = bind f v
    let rec    traverseSeq     f            sq = let folder head tail = f head >>= (fun h -> tail >>= (fun t -> List.Cons(h,t) |> rtn))
                                                 Array.foldBack folder (Seq.toArray sq) (rtn List.empty) |> map Seq.ofList
    let inline sequenceSeq                  sq = traverseSeq id sq
    let insertO   vRAO                         = vRAO |> Option.map(map Some) |> Option.defaultWith(fun () -> rtn None)
    let insertR ( vRAR:Result<_,_>)            = vRAR |> function | Error m -> rtn (Error m) | Ok v -> map Ok v
    let absorbR   vRRA                         = vRRA |> Async.map (Result.bind    id)
    let absorbO f vORA                         = vORA |> Async.map (Result.absorbO  f)

最后,CE 的构建器 asyncResult { ... }

type AsyncResultBuilder() =
    member __.ReturnFrom vRA        : Async<Result<'v  , 'm>> =                       vRA
    member __.ReturnFrom vR         : Async<Result<'v  , 'm>> = AsyncResult.rtnR      vR
    member __.Return     v          : Async<Result<'v  , 'm>> = AsyncResult.rtn       v  
    member __.Zero       ()         : Async<Result<unit, 'm>> = AsyncResult.rtn       () 
    member __.Bind      (vRA,  fRA) : Async<Result<'b  , 'm>> = AsyncResult.bind fRA  vRA
    member __.Bind      (vR ,  fRA) : Async<Result<'b  , 'm>> = AsyncResult.bind fRA (vR  |> AsyncResult.rtnR)
    member __.Combine   (vRA,  fRA) : Async<Result<'b  , 'm>> = AsyncResult.bind fRA  vRA
    member __.Combine   (vR ,  fRA) : Async<Result<'b  , 'm>> = AsyncResult.bind fRA (vR  |> AsyncResult.rtnR)
    member __.Delay            fRA                            = fRA
    member __.Run              fRA                            = AsyncResult.rtn () |> AsyncResult.bind fRA
    member __.TryWith   (fRA , hnd) : Async<Result<'a  , 'm>> = async { try return! fRA() with e -> return! hnd e  }
    member __.TryFinally(fRA , fn ) : Async<Result<'a  , 'm>> = async { try return! fRA() finally   fn  () }
    member __.Using(resource , fRA) : Async<Result<'a  , 'm>> = async.Using(resource,       fRA)
    member __.While   (guard , fRA) : Async<Result<unit, 'a>> = AsyncResult.whileLoop guard fRA 
    member th.For  (s: 'a seq, fRA) : Async<Result<unit, 'b>> = th.Using(s.GetEnumerator (), fun enum ->
                                                                    th.While(enum.MoveNext,
                                                                      th.Delay(fun () -> fRA enum.Current)))
let asyncResult = AsyncResultBuilder()


[<AutoOpen>]
module Extensions =      
    type AsyncResultBuilder with
      member __.ReturnFrom (vA: Async<'a>     ) : Async<Result<'a, 'b>> =                       Async.map Ok vA
      member __.Bind       (vA: Async<'a>, fRA) : Async<Result<'b, 'c>> = AsyncResult.bind fRA (Async.map Ok vA)
      member __.Combine    (vA: Async<'a>, fRA) : Async<Result<'b, 'c>> = AsyncResult.bind fRA (Async.map Ok vA)

关于f# - 将 "bind"与异步函数一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54713827/

相关文章:

list - F#选择对随机整数列表进行排序

scala - 将\/[A, B] 提升到EitherT[Future, A, B]

Haskell 提前退出状态 monad(守卫?)

optimization - 左倾红黑树的 F# 代码优化

f# 类型提供程序和 INPC 元编程

macos - 在 Mac 上编译单个 F# 程序

f# - 在创建中间值时,我应该存储它吗?

haskell - 与 monad 中的值进行模式匹配

haskell - 在变压器堆栈的底部插入 ErrorT

haskell - 交换内部和外部单子(monad)