この記事はML Advent Calendar 2020 12日目の記事です。
OCamlにそろそろ入りそうな機能を紹介します。
今回は、次の次のOCaml 4.13で入りそうな Syntax proposal: let punning by stedolan · Pull Request #10013 · ocaml/ocaml の紹介をします。
以前はOCamlでモナディックなコードを書こうとすると fun
と >>=
で頑張らないといけなかったが、OCaml 4.08.0 で binding operators が入ったことでふつうのコードのような見た目で書けるようになった。1
binding operatorsがない時のコード(例はAll About Monadsによる):
(* 下準備。よくあるbind演算子 *)
module Option_ops = struct
let ( >>= ) x f = Option.bind x f
end
let mother's_paternal_grandfather s =
let open Option_ops in
mother s >>= fun m ->
father m >>= fun gf ->
father gf
binding operatorsがある時のコード:
(* 下準備。 + をApplicative, * をMonadにする慣習 *)
module Option_syntax = struct
let ( let+ ) x f = Option.map f x
let ( and+ ) x y =
match x, y with
| Some x, Some y -> Some (x, y)
| _, _ -> None
let ( let* ) x f = Option.bind x f
let ( and* ) = ( and+ )
end
let mother's_paternal_grandfather s =
let open Option_syntax in
let* m = mother s in
let* gf = father m in
father gf
今回紹介するlet punningは、さらに、 binding operatorの左辺と右辺が同じ場合、右辺を省略できるようにする。
let liftA2_opt f x y =
let open Option_syntax in
let+ x and+ y in
f x y
(*
let listA2_opt f x y =
let open Option_syntax in
let+ x = x
and+ y = y in
f x y
と同じ意味
*)
これは、レコード式などで、フィールド名と変数名が同じ場合に { x = x }
を省略して {x}
と書けるrecord punningという機能に見た目が似ているため、 let punning と呼ばれている。
この構文を導入する動機は以下のような要因があると思う。
-
f <$> x <*> y
のような演算子を使う方法は、OCamlでは優先順位がいい感じにならないことが多いので、多引数関数の持ち上げが面倒臭い(ユーザーが演算子の優先順位を決められない) - OCamlには型クラスがないので、語彙を増やそうとするとファンクタ頼りになって構文的に重い(上記の
liftA2_opt
もbinding operatorを定義したモジュールを受け取るファンクタを定義して他の型にも対応したいところ)2 - 1., 2. から binding operator だけ用意しておいてベタ書きする機会が多い
- 慣習的にshadowingが好まれるので、文脈に包まれた値と文脈内の値の変数を同名にしがち(誤解がなければ)
提案段階ではbinding operatorでない素の let
についても右辺を省略できるようにして、次のようなコードを書けるようにしようという話もあったが、さすがに気持ち悪いということでこちらは見送られた。
module N = struct
open M
let compare and equal and hash
(*
上記2行で↓と同じ意味
let compare = M.compare
and equal = M.equal
and hash = M.hash
*)
end
また、 binding operator 導入以前は拡張ノードを使って let%lwt
のように ppx でモナディックな束縛を実現していた場合もあったので、こちらも同様に右辺が省略できるようになっている。
-
Modular explicitsが入ればましになる? ↩