如何使用常用运算符组合值和函数

在面向对象编程中,常见的任务是组合对象(值)。在函数式编程中,组合值和函数是常见的任务。

我们习惯于使用+-*/等运算符来构建其他编程语言的经验值。

价值构成

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# 中,前向管道优于反向管道,因为:

  1. 类型推断(通常)从左到右流动,因此值和函数从左到右流动也是很自然的
  2. 因为 <|<< 应该具有正确的关联性但是在 F# 中它们是左联的,这迫使我们插入()
  3. 混合正向和反向管道通常不起作用,因为它们具有相同的优先级。

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

结论

对于新的函数式程序员,使用运算符的函数组合可能看起来不透明和模糊,但这是因为这些运算符的含义并不像处理值的运算符那样通常。然而,通过使用|> 的一些训练,>>>>=>=> 变得像使用+-*/一样自然。