23 Binding operators
(Introduced in 4.08.0)
Binding operators offer syntactic sugar to expose library functions
under (a variant of) the familiar syntax of standard keywords.
Currently supported “binding operators” are let<op> and and<op>,
where <op> is an operator symbol, for example and+$.
Binding operators were introduced to offer convenient syntax for
working with monads and applicative functors; for those, we propose
conventions using operators * and + respectively. They may be used
for other purposes, but one should keep in mind that each new
unfamiliar notation introduced makes programs harder to understand for
non-experts. We expect that new conventions will be developed over
time on other families of operator.
23.1 Examples
Users can define let operators:
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>
and then apply them using this convenient syntax:
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>
which is equivalent to this expanded form:
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>
Users can also define and operators:
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
to support the syntax:
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>
which is equivalent to this expanded form:
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 Conventions
An applicative functor should provide a module implementing the following
interface:
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
where (let+) is bound to the map operation and (and+) is bound to
the monoidal product operation.
A monad should provide a module implementing the following interface:
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
where (let*) is bound to the bind operation, and (and*) is also
bound to the monoidal product operation.
23.3 General desugaring rules
The form
let<op0>
x1 = e1
and<op1>
x2 = e2
and<op2>
x3 = e3
in e
desugars into
( let<op0> )
(( and<op2> )
(( and<op1> )
e1
e2)
e3)
(fun ((x1, x2), x3) -> e)
This of course works for any number of nested and-operators. One can
express the general rule by repeating the following simplification
steps:
-
The first and-operator in
let<op0> x1 = e1 and<op1> x2 = e2 and... in e
can be desugared into a function application
let<op0> (x1, x2) = ( and<op1> ) e1 e2 and... in e.
- Once all and-operators have been simplified away,
the let-operator in
let<op> x1 = e1 in e
can be desugared into an application
( let<op> ) e1 (fun x1 -> e).
Note that the grammar allows mixing different operator symbols in the
same binding (<op0>, <op1>, <op2> may be distinct), but we
strongly recommend APIs where let-operators and and-operators working
together use the same symbol.
23.4 Short notation for variable bindings (let-punning)
(Introduced in 4.13.0)
When the expression being bound is a variable, it can be convenient to
use the shorthand notation let+ x in ..., which expands to let+ x = x in .... This notation, also known as let-punning, allows the
sum3 function above can be written more concisely as:
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>
This notation is also supported for extension nodes, expanding
let%foo x in ... to let%foo x = x in .... However, to avoid
confusion, this notation is not supported for plain let bindings.
Copyright © 2024 Institut National de
Recherche en Informatique et en Automatique