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
nonexperts. 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 andoperators. One can
express the general rule by repeating the following simplification
steps:

The first andoperator 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 andoperators have been simplified away,
the letoperator 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 letoperators and andoperators working
together use the same symbol.
23.4 Short notation for variable bindings (letpunning)
(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 letpunning, 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