第 12 章 語言擴展

23 綁定運算子

(於 4.08.0 版本引入)

let-運算子::= 
 let (核心運算子字元 ∣ <) { 點運算子字元 }
 
and-運算子::= 
 and (核心運算子字元 ∣ <) { 點運算子字元 }
 
運算子名稱::= ...
 let-運算子
 and-運算子
 
letop-綁定::= 模式=運算式
 值名稱
 
運算式::= ...
 let-運算子letop-綁定 { and-運算子letop-綁定 } in運算式
 

綁定運算子提供了語法糖,可以將函式庫函數以(一種變體的)熟悉的標準關鍵字語法公開。目前支援的「綁定運算子」有 let<op>and<op>,其中 <op> 是一個運算子符號,例如 and+$

引入綁定運算子是為了提供方便的語法來處理單子 (monads) 和應用函子 (applicative functors);對於這些,我們建議分別使用運算子 *+ 的慣例。它們可能被用於其他目的,但應記住,引入的每個新的不熟悉的表示法都會使非專業人士更難以理解程式。我們預期隨著時間的推移,將會在其他運算子系列上開發新的慣例。

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 慣例

應用函子應提供一個實現以下介面的模組

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) 乘積操作。

單子應提供一個實現以下介面的模組

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*) 也繫結到單群乘積操作。

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> 可能不同),但我們強烈建議讓一起工作的 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 綁定。