第 12 章 语言扩展

23 绑定运算符

(在 4.08.0 中引入)

let-运算符::= 
 let (核心运算符字符 ∣ <) { 点运算符字符 }
 
and-运算符::= 
 and (核心运算符字符 ∣ <) { 点运算符字符 }
 
运算符名称::= ...
 let-运算符
 and-运算符
 
letop 绑定::= 模式=表达式
 值名称
 
表达式::= ...
 let-运算符letop 绑定 { and-运算符letop 绑定 } in表达式
 

绑定运算符提供语法糖,以便在(标准关键字的变体)熟悉的语法下公开库函数。目前支持的“绑定运算符”是 let<op>and<op>,其中 <op> 是一个运算符符号,例如 and+$

引入绑定运算符是为了为使用 monad 和 applicative functor 提供方便的语法;对于那些,我们建议使用运算符 *+ 分别进行约定。它们可以用于其他目的,但应该记住,每个引入的新不熟悉的符号都会使程序对于非专家来说更难理解。我们预计随着时间的推移,将开发出关于其他运算符系列的新约定。

23.1 示例

用户可以定义 let 运算符

let ( let* ) o f = match o with | None -> None | Some x -> f x let return x = Some x
val ( let* ) : 'a option -> ('a -> 'b option) -> 'b option = <fun> val return : 'a -> 'a option = <fun>

并使用此方便的语法应用它们

let find_and_sum tbl k1 k2 = let* x1 = Hashtbl.find_opt tbl k1 in let* x2 = Hashtbl.find_opt tbl k2 in return (x1 + x2)
val find_and_sum : ('a, int) Hashtbl.t -> 'a -> 'a -> int option = <fun>

这等效于此扩展形式

let find_and_sum tbl k1 k2 = ( let* ) (Hashtbl.find_opt tbl k1) (fun x1 -> ( let* ) (Hashtbl.find_opt tbl k2) (fun x2 -> return (x1 + x2)))
val find_and_sum : ('a, int) Hashtbl.t -> 'a -> 'a -> int option = <fun>

用户还可以定义 and 运算符

module ZipSeq = struct type 'a t = 'a Seq.t open Seq let rec return x = fun () -> Cons(x, return x) let rec prod a b = fun () -> match a (), b () with | Nil, _ | _, Nil -> Nil | Cons(x, a), Cons(y, b) -> Cons((x, y), prod a b) let ( let+ ) f s = map s f let ( and+ ) a b = prod a b end
module ZipSeq : sig type 'a t = 'a Seq.t val return : 'a -> 'a Seq.t val prod : 'a Seq.t -> 'b Seq.t -> ('a * 'b) Seq.t val ( let+ ) : 'a Seq.t -> ('a -> 'b) -> 'b Seq.t val ( and+ ) : 'a Seq.t -> 'b Seq.t -> ('a * 'b) Seq.t end

以支持语法

open ZipSeq let sum3 z1 z2 z3 = let+ x1 = z1 and+ x2 = z2 and+ x3 = z3 in x1 + x2 + x3
val sum3 : int Seq.t -> int Seq.t -> int Seq.t -> int Seq.t = <fun>

这等效于此扩展形式

open ZipSeq let sum3 z1 z2 z3 = ( let+ ) (( and+ ) (( and+ ) z1 z2) z3) (fun ((x1, x2), x3) -> x1 + x2 + x3)
val sum3 : int Seq.t -> int Seq.t -> int Seq.t -> int Seq.t = <fun>

23.2 约定

一个 applicative functor 应该提供一个实现以下接口的模块

module type Applicative_syntax = sig type 'a t val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t val ( and+ ): 'a t -> 'b t -> ('a * 'b) t end

其中 (let+) 绑定到 map 操作,而 (and+) 绑定到 monoidal 乘积操作。

一个 monad 应该提供一个实现以下接口的模块

module type Monad_syntax = sig include Applicative_syntax val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t val ( and* ): 'a t -> 'b t -> ('a * 'b) t end

其中 (let*) 绑定到 bind 操作,而 (and*) 也绑定到 monoidal 乘积操作。

23.3 通用反糖化规则

形式

let<op0>
  x1 = e1
and<op1>
  x2 = e2
and<op2>
  x3 = e3
in e

反糖化为

( let<op0> )
  (( and<op2> )
    (( and<op1> )
      e1
      e2)
    e3)
  (fun ((x1, x2), x3) -> e)

这当然适用于任意数量的嵌套 and 运算符。可以通过重复以下简化步骤来表达一般规则

请注意,语法允许在同一绑定中混合不同的运算符符号 (<op0><op1><op2> 可能不同),但我们强烈建议 API 在一起工作的 let 运算符和 and 运算符使用相同的符号。

23.4 变量绑定的简写符号 (let-punning)

(在 4.13.0 中引入)

当被绑定的表达式是一个变量时,使用简写符号 let+ x in ... 会很方便,它扩展为 let+ x = x in ...。这个符号也称为 let-punning,允许上面的 sum3 函数更简洁地写为

open ZipSeq let sum3 z1 z2 z3 = let+ z1 and+ z2 and+ z3 in z1 + z2 + z3
val sum3 : int Seq.t -> int Seq.t -> int Seq.t -> int Seq.t = <fun>

这种表示法也支持扩展节点,将 let%foo x in ... 扩展为 let%foo x = x in ...。然而,为了避免混淆,这种表示法不支持普通的 let 绑定。