假设我想在async
工作流中返回Option
:
let run =
async {
let! x = doAsyncThing
let! y = doNextAsyncThing x
match y with
| None -> return None
| Some z -> return Some <| f z
}
理想情况下,我会在使用async的同时使用FSharpx中的maybe计算表达式,以避免执行match
。我可以制作一个自定义生成器,但有没有一种方法可以通用地组合两个计算表达式?它可能看起来像这样:
let run =
async {
let! x = doAsyncThing
let! y = doNextAsyncThing x
return! f y
}
通常在F#中,您不使用通用工作流,而是手动定义工作流,或者使用现成的工作流,如async
和maybe
,但如果要将它们组合使用,则需要手动编写特定的工作流组合。
或者,您可以使用F#+,这是一个为monad提供通用工作流的项目,在这种情况下,它将自动为您派生,这里有一个工作示例,使用您的工作流,然后使用OptionT
,这是monad转换器:
#r "nuget: FSharpPlus, 1.2"
open FSharpPlus
open FSharpPlus.Data
let doAsyncThing = async {return System.DateTime.Now}
let doNextAsyncThing (x:System.DateTime) = async {
let m = x.Millisecond
return (if m < 500 then Some m else None)}
let f x = 2 * x
// then you can use Async<_> (same as your code)
let run = monad {
let! x = doAsyncThing
let! y = doNextAsyncThing x
match y with
| None -> return None
| Some z -> return Some <| f z}
let res = Async.RunSynchronously run
// or you can use OptionT<Async<_>> (monad transformer)
let run' = monad {
let! x = lift doAsyncThing
let! y = OptionT (doNextAsyncThing x)
return f y}
let res' = run' |> OptionT.run |> Async.RunSynchronously
第一个函数必须"提升"到另一个monad中,因为它只处理Async
(而不是Option
),第二个函数同时处理两者,所以它只需要"打包"到我们的OptionT
DU中。
正如您所看到的,这两个工作流都是自动派生的,您拥有的工作流(异步工作流)和您想要的工作流。
有关这种方法的更多信息,请阅读Monad Transformers。
一个简单的方法是使用Option模块:
let run =
async {
let! x = doAsyncThing
let! y = doNextAsyncThing x
return Option.map f y
}
我想您不必经常在async
的上下文中处理option
。FSharpx还为option
类型提供了更多的高阶函数。在大多数情况下,我认为使用它们就足够了。
为了获得使用这些函数的感觉,请看一看这篇漂亮的文章。
type MaybeMonad() =
member __.Bind(x, f) =
match x with
| Some v -> f v
| None -> None
member __.Return(x) =
Some x
let maybe = MaybeMonad()
let run = async {
let! x = doAsyncThing
let! y = doNextAsyncThing x
return maybe {
let! y_val = y
return f y_val
}
}
只需在里面使用f#计算表达式。