如何使用常用运算符组合值和函数
在面向对象编程中,常见的任务是组合对象(值)。在函数式编程中,组合值和函数是常见的任务。
我们习惯于使用+
,-
,*
,/
等运算符来构建其他编程语言的经验值。
价值构成
let x = 1 + 2 + 3 * 2
由于函数式编程既包含函数也包含值,因此有一些常见的函数组合运算符如 >>
,<<
,|>
和 <|
就不足为奇了。
功能构成
// val f : int -> int
let f v = v + 1
// val g : int -> int
let g v = v * 2
// Different ways to compose f and g
// val h : int -> int
let h1 v = g (f v)
let h2 v = v |> f |> g // Forward piping of 'v'
let h3 v = g <| (f <| v) // Reverse piping of 'v' (because <| has left associcativity we need ())
let h4 = f >> g // Forward functional composition
let h5 = g << f // Reverse functional composition (closer to math notation of 'g o f')
在 F#
中,前向管道优于反向管道,因为:
- 类型推断(通常)从左到右流动,因此值和函数从左到右流动也是很自然的
- 因为
<|
和<<
应该具有正确的关联性但是在F#
中它们是左联的,这迫使我们插入() - 混合正向和反向管道通常不起作用,因为它们具有相同的优先级。
Monad 组成
由于 Monads(如 Option<'T>
或 List<'T>
)常用于函数式编程,因此还有一些常见但不太知名的运算符来组合使用 Monad 的函数,如 >>=
,>=>
,<|>
和 <*>
。
let (>>=) t uf = Option.bind uf t
let (>=>) tf uf = fun v -> tf v >>= uf
// val oinc : int -> int option
let oinc v = Some (v + 1) // Increment v
// val ofloat : int -> float option
let ofloat v = Some (float v) // Map v to float
// Different ways to compose functions working with Option Monad
// val m : int option -> float option
let m1 v = Option.bind (fun v -> Some (float (v + 1))) v
let m2 v = v |> Option.bind oinc |> Option.bind ofloat
let m3 v = v >>= oinc >>= ofloat
let m4 = oinc >=> ofloat
// Other common operators are <|> (orElse) and <*> (andAlso)
// If 't' has Some value then return t otherwise return u
let (<|>) t u =
match t with
| Some _ -> t
| None -> u
// If 't' and 'u' has Some values then return Some (tv*uv) otherwise return None
let (<*>) t u =
match t, u with
| Some tv, Some tu -> Some (tv, tu)
| _ -> None
// val pickOne : 'a option -> 'a option -> 'a option
let pickOne t u v = t <|> u <|> v
// val combine : 'a option -> 'b option -> 'c option -> (('a*'b)*'c) option
let combine t u v = t <*> u <*> v
结论
对于新的函数式程序员,使用运算符的函数组合可能看起来不透明和模糊,但这是因为这些运算符的含义并不像处理值的运算符那样通常。然而,通过使用|>
的一些训练,>>
,>>=
和 >=>
变得像使用+
,-
,*
和/
一样自然。