F# 函数式编程之 - 隐藏运算
“隐藏运算” 是我发明的词,它的正式名称是 “computation expressions”。
但 “computation expressions” 这个名称实在让人非常费解,也不能反映它的作用,不是一个好名称。
它的作用是在背后对两个表达式进行一些操作,让表达式们表面上看起来简单。
请看例子:
let divideBy bottom top =
match bottom with
| 0 -> None
| _ -> Some <| top/bottom
let maybe = new MaybeBuilder()
let divideWorkflow init x y z =
maybe {
let! a = init |> divideBy x
let! b = a |> divideBy y
let! c = b |> divideBy z
return c
}
divideWorkflow 12 3 2 1 // Some(2)
divideWorkflow 12 3 0 1 // None
请看那几个 let!, 表面上是 init 除以 x, 得出结果 a 再除以 y, 以此类推。这个表面上的逻辑非常清晰,但如果不是背后隐藏了一些处理(意思是,如果用 let 而不是用 let!),这个代码是跑不通的。因为 init 除以 x 得出来的 a 不是一个数字(divideBy 的返回值是 Option, 不是 int), 因此如果让 a 直接除以 y 会产生类型不匹配的错误。
简单来说,let! 表示 “computation expressions”, 也就是意味着在背后隐藏着一些我们表面上看不见的运算。
那么,这个背后的运算具体是什么?我们可以看到,全部 let! 都在 maybe{...} 里面,而 maybe 是由 MaybeBuilder 生成的,显然,关键在于 MaybeBuilder 的定义:
type MaybeBuilder() =
member this.Bind(x, f) =
match x with
| None -> None
| Some a -> f a
member this.Return(x) = Some x
如上所示,这个 MaybeBuilder 的定义不是系统自带的,要我们自己写。可以看到,这个 “背后的代码” 对 x:Option 进行处理,如果是 None 则直接返回 None, 并且由于这个 None 会传递给下一个 let!, 因此可以预见一旦遇到 None, 后续的 f 就一律不会被运算,最终结果返回 None。如果是 Some 则从 Some 中提出取 a:int 并执行 f(a), 因此,经过这个背后的处理,表面上的 a |> divideBy y 就不会出错了。
“computation expressions” 可以把啰嗦的辅助语句隐藏在背后,让表面上的核心语句看起来很简洁,因此很好用,是 F# 里很常用的一种技术。但由于它是 “隐式的” 而不是 “显示的”, 所以理解起来需要在脑子里多绕几个弯,本文只是简单地介召了它的主要特点,想了解得更详细请看以下资料:
- https://fsharpforfunandprofit.com/posts/computation-expressions-intro/
- https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions
更新、补充
由于标准库里也带了一些好用的函数,比如 Option.bind, 因此 MaybeBuilder 也可以改写成:
type MaybeBuilder() =
member this.Bind(x, f) = Option.bind f x
member this.Return(x) = Some x

浙公网安备 33010602011771号