packageppx_let
Install
Dune Dependency
Authors
Maintainers
Sources
sha256=6bf57833ce402720fad8ef7aabda111c0a8640cf4441df42210eb62da8a48d78
Description
Part of the Jane Street's PPX rewriters collection.
Published:23 May 2024
README
ppx_let
A ppx rewriter for monadic and applicative let bindings, match expressions, and if expressions.
Overview
The aim of this rewriter is to make monadic and applicative code look nicer by writing custom binders the same way that we normally bind variables. In OCaml, the common way to bind the result of a computation to a variable is:
let VAR = EXPR in BODY
ppx_let simply adds two new binders:let%bind
andlet%map
. These are rewritten into calls to thebind
andmap
functions respectively. These functions are expected to have
val map : 'a t -> f:('a -> 'b) -> 'b tval bind : 'a t -> f:('a -> 'b t) -> 'b t
for some typet
, as one might expect.
These functions are to be provided by the user, and are generally expected to be part of the signatures of monads and applicatives modules. This is the case for all monads and applicatives defined by the Jane Street's Core suite of libraries. (see the section below on getting the right names into scope).
Parallel bindings
ppx_let understands parallel bindings as well. i.e.:
let%bind VAR1 = EXPR1 and VAR2 = EXPR2 and VAR3 = EXPR3 in BODY
Theand
keyword is seen as a binding combination operator. To do so it expects the presence of aboth
function, that lifts the OCaml pair operation to the typet
in question:
val both : 'a t -> 'b t -> ('a * 'b) t
Some applicatives have optimizedmap
functions for more than two arguments. These applicatives will export functions likemap4
shown below:
val map4: 'a t -> 'b t -> 'c t -> 'd t -> f:('a -> 'b -> 'c -> 'd -> 'r) -> 'r t
In order to use these optmized functions, ppx_let provides thelet%mapn
syntax, which picks the rightmap{n}
function to call based on the amount of applicatives bound by the syntax.
Match statements
We found that this form was quite useful for match statements as well. So for convenience ppx_let also accepts%bind
and%map
on thematch
keyword. Morallymatch%bind expr with cases
is seen aslet%bind x = expr in match x with cases
.
If statements
As a further convenience, ppx_let accepts%bind
and%map
on theif
keyword. The expressionif%bind expr1 then expr2 else expr3
is morally equivalent tolet%bind p = expr1 in if p then expr2 else expr3
.
Function statements
We acceptfunction%bind
andfunction%map
too.
let f = function%bind | Some a -> g a | None -> h
is equivalent to
let f = fun temp -> match%bind temp with | Some a -> g a | None -> h
While statements
We also expandwhile%bind expr1 do expr2 done
as
let rec loop () = if%bind expr1 then ( let%bind () = expr2 in loop ()) else return ()in loop ()
Note that this form will (potentially) evaluate the textual form of expr1 multiple times!
We do not supportwhile%map
, as that cannot be implemented withoutbind
.
Syntactic forms and actual rewriting
ppx_let
adds seven syntactic forms
let%bind P = M in Elet%map P = M in Ematch%bind M with P1 -> E1 | P2 -> E2 | ...match%map M with P1 -> E1 | P2 -> E2 | ...if%bind M then E1 else E2if%map M then E1 else E2while%bind M do E done
that expand into
bind M ~f:(fun P -> E)map M ~f:(fun P -> E)bind M ~f:(function P1 -> E1 | P2 -> E2 | ...)map M ~f:(function P1 -> E1 | P2 -> E2 | ...)bind M ~f:(function true -> E1 | false -> E2)map M ~f:(function true -> E1 | false -> E2)let rec loop () = bind M ~f:(function true -> bind E ~f:loop | false -> return ()) in loop ()
respectively.
As withlet
,let%bind
andlet%map
also support multipleparallel bindings via theand
keyword:
let%bind P1 = M1 and P2 = M2 and P3 = M3 and P4 = M4 in Elet%map P1 = M1 and P2 = M2 and P3 = M3 and P4 = M4 in E
that expand into
let x1 = M1 and x2 = M2 and x3 = M3 and x4 = M4 inbind (both x1 (both x2 (both x3 x4))) ~f:(fun (P1, (P2, (P3, P4))) -> E)let x1 = M1 and x2 = M2 and x3 = M3 and x4 = M4 inmap (both x1 (both x2 (both x3 x4))) ~f:(fun (P1, (P2, (P3, P4))) -> E)
respectively. (Instead ofx1
,x2
, ... ppx_let uses variable names that are unlikely to clash with other names)
As withlet
, names introduced by left-hand sides of the let bindings are not available in subsequent right-hand sides of the same sequence.
Getting the right names in scope
The description of how the%bind
and%map
syntax extensions expand left out the fact that the namesbind
,map
,both
, andreturn
are not used directly., but rather qualified byLet_syntax
. For example, we useLet_syntax.bind
rather than merelybind
.
This means one just needs to get a properly loadedLet_syntax
module in scope to use%bind
and%map
. The intended way to do this is to create a moduleLet_syntax
with a signature like:
module Let_syntax : sig module Let_syntax : sig val bind : ... val map : ... ... end ...end
and then useopen Let_syntax
to make the innerLet_syntax
module available.
Alternatively, the extension can use values from aLet_syntax
module other than the one in scope. If you write%map.A.B.C
instead of%map
, the expansion will useA.B.C.Let_syntax.Let_syntax.map
instead ofLet_syntax.map
(and similarly for all extension points).
For monads,Core.Monad.Make
produces a submoduleLet_syntax
of the appropriate form.
For applicatives, the convention for these modules is to have a submoduleLet_syntax
of the form:
module Let_syntax : sig module Let_syntax : sig val return : 'a -> 'a t val map : 'a t -> f:('a -> 'b) -> 'b t val both : 'a t -> 'b t -> ('a * 'b) t module Open_on_rhs : << some signature >> endend
TheOpen_on_rhs
submodule is used by variants of%map
and%bind
called%map_open
and%bind_open
. It is locally opened on the right hand sides of the rewritten let bindings in%map_open
and%bind_open
expressions. Formatch%map_open
andmatch%bind_open
expressions,Open_on_rhs
is opened for the expression being matched on.
Open_on_rhs
is useful when programming with applicatives, which operate in a staged manner where the operators used to construct the applicatives are distinct from the operators used to manipulate the values those applicatives produce. For monads,Open_on_rhs
containsreturn
.
Local values
ppx_let
can operate on local values. This requires a compiler that supports thelocal_
keyword and stack allocation, which as of 2023-03 is a nonstandard compiler extension.
Use
%mapl
and%bindl
instead of%map
and%bind
.Implement a
Let_syntax
module that matches the following signature:module Let_syntax : sig module Let_syntax : sig val return : local_ 'a -> local_ 'a t val map : local_ 'a t -> f:local_ (local_ 'a -> local_ 'b) -> local_ 'b t val both : local_ 'a t -> local_ 'b t -> local_ ('a * 'b) t module Open_on_rhs : << some signature >> endend
Dependencies (5)
Dev Dependencies
None
Used by (27)
- angstrom
>= "0.15.0"
- base_quickcheck
>= "v0.17.0"
- bonsai
>= "v0.17.0"
- cmdlang-to-base
- core_bench
>= "v0.17.0"
- dunolint
- dunolint-lib
- finch
- gopcaml-mode-merlin
< "0.0.6"
- gremlin
- lwt_ppx
>= "5.9.1"
- mmdb
- noise
- obeam
>= "0.1.0"
- opam-check-npm-deps
- openstellina
- opine
- orewa
- ppx_css
>= "v0.17.0"
- ppx_jane
>= "v0.17.0"
- ppx_log
>= "v0.17.0"
- ppx_pattern_bind
>= "v0.17.0"
- ppx_string_conv
- pyml_bindgen
- sexp_grammar
>= "v0.17.0"
- user-agent-parser
- zeit
Conflicts
None