f# - 将“绑定”与异步函数一起使用
问题描述
假设我有一些返回的函数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>
and Option.bind
。
我知道我可以为 Result 使用Option.isSome/isNone
和/或编写自己的isOk/isError
函数,但我觉得我不应该这样做。
关于将这样的东西组合在一起的最佳方式有什么想法吗?
解决方案
问题是Result.bind
无法使用,getData
因为签名不匹配。Result.bind
期望产生一个Result<>
但getData
产生一个的函数Async<Result<_,_>>
。你需要一个bind
for 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)
推荐阅读
- javascript - typeorm find with and & or where block
- node.js - 带有 Express-Messages 的 Express-Validator
- javascript - 使用正则表达式后如何不将大写转换为小写?
- c - 如何使用fork在C中的procecces之间发送信息
- python - pyinstaller 工作但 .exe 应用程序没有启动
- java - 我无法在 Vaadin 中将 int 转换为字符串
- kotlin - 我可以使用序列/集合 API 在 kotlin 中对 InputStream 进行分块吗?
- html - 为什么有这么多