114.08+63
This commit is contained in:
parent
2d59732ae3
commit
cfc2ac765b
120
README.md
120
README.md
@ -1,33 +1,32 @@
|
||||
A ppx rewriter for monadic and applicative let bindings and match statements.
|
||||
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:
|
||||
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:
|
||||
|
||||
```ocaml
|
||||
let VAR = EXPR in BODY
|
||||
```
|
||||
|
||||
ppx\_let simply adds two new binders: `let%bind` and
|
||||
`let%map`. These are rewritten into calls to the `bind` and
|
||||
`map` functions respectively. These functions are expected to have
|
||||
ppx\_let simply adds two new binders: `let%bind` and `let%map`. These are
|
||||
rewritten into calls to the `bind` and `map` functions respectively. These
|
||||
functions are expected to have
|
||||
|
||||
```ocaml
|
||||
val map : 'a t -> f:('a -> 'b) -> 'b t
|
||||
val bind : 'a t -> ('a -> 'b t) -> 'b t
|
||||
val bind : 'a t -> f:('a -> 'b t) -> 'b t
|
||||
```
|
||||
|
||||
for some type `t`, 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).
|
||||
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
|
||||
|
||||
@ -37,9 +36,9 @@ ppx\_let understands parallel bindings as well. i.e.:
|
||||
let%bind VAR1 = EXPR1 and VAR2 = EXPR2 and VAR3 = EXPR3 in BODY
|
||||
```
|
||||
|
||||
The `and` keyword is seen as a binding combination operator. To do so
|
||||
it expects the presence of a `both` function, that lifts the OCaml
|
||||
pair operation to the type `t` in question:
|
||||
The `and` keyword is seen as a binding combination operator. To do so it expects
|
||||
the presence of a `both` function, that lifts the OCaml pair operation to the
|
||||
type `t` in question:
|
||||
|
||||
```ocaml
|
||||
val both : 'a t -> 'b t -> ('a * 'b) t
|
||||
@ -47,15 +46,21 @@ val both : 'a t -> 'b t -> ('a * 'b) t
|
||||
|
||||
### 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
|
||||
the `match` keyword. Morally `match%bind expr with cases` is seen as
|
||||
`let%bind x = expr in match x with cases`.
|
||||
We found that this form was quite useful for match statements as well. So for
|
||||
convenience ppx\_let also accepts `%bind` and `%map` on the `match` keyword.
|
||||
Morally `match%bind expr with cases` is seen as `let%bind x = expr in match x
|
||||
with cases`.
|
||||
|
||||
### If statements
|
||||
|
||||
As a further convenience, ppx\_let accepts `%bind` and `%map` on the `if`
|
||||
keyword. The expression `if%bind expr1 then expr2 else expr3` is morally
|
||||
equivalent to `let%bind p = expr1 in if p then expr2 else expr3`.
|
||||
|
||||
Syntactic forms and actual rewriting
|
||||
------------------------------------
|
||||
|
||||
`ppx_let` adds four syntactic forms
|
||||
`ppx_let` adds six syntactic forms
|
||||
|
||||
```ocaml
|
||||
let%bind P = M in E
|
||||
@ -65,24 +70,32 @@ let%map P = M in E
|
||||
match%bind M with P1 -> E1 | P2 -> E2 | ...
|
||||
|
||||
match%map M with P1 -> E1 | P2 -> E2 | ...
|
||||
|
||||
if%bind M then E1 else E2
|
||||
|
||||
if%map M then E1 else E2
|
||||
```
|
||||
|
||||
that expand into
|
||||
|
||||
```ocaml
|
||||
bind M (fun P -> E)
|
||||
bind M ~f:(fun P -> E)
|
||||
|
||||
map M (fun P -> E)
|
||||
map M ~f:(fun P -> E)
|
||||
|
||||
bind M (function P1 -> E1 | P2 -> E2 | ...)
|
||||
bind M ~f:(function P1 -> E1 | P2 -> E2 | ...)
|
||||
|
||||
map M (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)
|
||||
```
|
||||
|
||||
respectively.
|
||||
|
||||
As with `let`, `let%bind` and `let%map` also support multiple
|
||||
*parallel* bindings via the `and` keyword:
|
||||
As with `let`, `let%bind` and `let%map` also support multiple *parallel*
|
||||
bindings via the `and` keyword:
|
||||
|
||||
```ocaml
|
||||
let%bind P1 = M1 and P2 = M2 and P3 = M3 and P4 = M4 in E
|
||||
@ -96,35 +109,34 @@ that expand into
|
||||
let x1 = M1 and x2 = M2 and x3 = M3 and x4 = M4 in
|
||||
bind
|
||||
(both x1 (both x2 (both x3 x4)))
|
||||
(fun (P1, (P2, (P3, P4))) -> E)
|
||||
~f:(fun (P1, (P2, (P3, P4))) -> E)
|
||||
|
||||
let x1 = M1 and x2 = M2 and x3 = M3 and x4 = M4 in
|
||||
map
|
||||
(both x1 (both x2 (both x3 x4)))
|
||||
(fun (P1, (P2, (P3, P4))) -> E)
|
||||
~f:(fun (P1, (P2, (P3, P4))) -> E)
|
||||
```
|
||||
|
||||
respectively. (Instead of `x1`, `x2`, ... ppx\_let uses
|
||||
variable names that are unlikely to clash with other names)
|
||||
respectively. (Instead of `x1`, `x2`, ... ppx\_let uses variable names that are
|
||||
unlikely to clash with other names)
|
||||
|
||||
As with `let`, names introduced by left-hand sides of the let bindings
|
||||
are not available in subsequent right-hand sides of the same sequence.
|
||||
As with `let`, 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 names `bind`, `map`, `both`, and `return`
|
||||
are not used directly, but rather qualified by `Let_syntax`. For
|
||||
example, we use `Let_syntax.bind` rather than merely `bind`. This
|
||||
means one just needs to get a properly loaded `Let_syntax` module in
|
||||
scope to use `%bind` and `%map`.
|
||||
The description of how the `%bind` and `%map` syntax extensions expand left out
|
||||
the fact that the names `bind`, `map`, `both`, and `return` are not used
|
||||
directly, but rather qualified by `Let_syntax`. For example, we use
|
||||
`Let_syntax.bind` rather than merely `bind`. This means one just needs to get a
|
||||
properly loaded `Let_syntax` module in scope to use `%bind` and `%map`.
|
||||
|
||||
For monads, `Core.Std.Monad.Make` produces a submodule `Let_syntax` of
|
||||
the appropriate form.
|
||||
For monads, `Core.Std.Monad.Make` produces a submodule `Let_syntax` of the
|
||||
appropriate form.
|
||||
|
||||
For applicatives. The convention for these modules is to have a
|
||||
submodule `Let_syntax` of the form
|
||||
For applicatives. The convention for these modules is to have a submodule
|
||||
`Let_syntax` of the form
|
||||
|
||||
```ocaml
|
||||
module Let_syntax : sig
|
||||
@ -135,15 +147,13 @@ module Let_syntax : sig
|
||||
end
|
||||
```
|
||||
|
||||
The `Open_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. For `match%map_open` and `match%bind_open`
|
||||
expressions, `Open_on_rhs` is opened for the expression being matched
|
||||
on.
|
||||
The `Open_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. For
|
||||
`match%map_open` and `match%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` contains
|
||||
`return`.
|
||||
`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` contains `return`.
|
||||
|
2
_oasis
2
_oasis
@ -2,7 +2,7 @@ OASISFormat: 0.4
|
||||
OCamlVersion: >= 4.03.0
|
||||
FindlibVersion: >= 1.3.2
|
||||
Name: ppx_let
|
||||
Version: 114.08+30
|
||||
Version: 114.08+63
|
||||
Synopsis: Monadic let-bindings
|
||||
Authors: Jane Street Group, LLC <opensource@janestreet.com>
|
||||
Copyrights: (C) 2015-2016 Jane Street Group LLC <opensource@janestreet.com>
|
||||
|
3
opam
3
opam
@ -11,10 +11,11 @@ build: [
|
||||
]
|
||||
depends: [
|
||||
"ocamlbuild" {build}
|
||||
"oasis" {build & >= "0.4" & < "0.4.7"}
|
||||
"oasis" {build & >= "0.4"}
|
||||
"ocamlfind" {build & >= "1.3.2"}
|
||||
"js-build-tools" {build}
|
||||
"ppx_core"
|
||||
"ppx_driver"
|
||||
]
|
||||
available: [ ocaml-version >= "4.03.0" ]
|
||||
conclicts: [ "oasis" {= "0.4.7"} ]
|
||||
|
@ -93,6 +93,12 @@ let expand_match extension_name ~loc expr cases =
|
||||
~fn:(pexp_function ~loc cases)
|
||||
;;
|
||||
|
||||
let expand_if extension_name ~loc expr then_ else_ =
|
||||
expand_match extension_name ~loc expr
|
||||
[ case ~lhs:(pbool ~loc true) ~guard:None ~rhs:then_
|
||||
; case ~lhs:(pbool ~loc false) ~guard:None ~rhs:else_
|
||||
]
|
||||
|
||||
let expand ~loc:_ ~path:_ extension_name expr =
|
||||
let loc = expr.pexp_loc in
|
||||
let expansion =
|
||||
@ -110,9 +116,18 @@ let expand ~loc:_ ~path:_ extension_name expr =
|
||||
(Extension_name.to_string extension_name)
|
||||
| Pexp_match (expr, cases) ->
|
||||
expand_match extension_name ~loc expr cases
|
||||
| Pexp_ifthenelse (expr, then_, else_) ->
|
||||
let else_ =
|
||||
match else_ with
|
||||
| Some else_ -> else_
|
||||
| None ->
|
||||
Location.raise_errorf ~loc "'if%%%s' must include an else branch"
|
||||
(Extension_name.to_string extension_name)
|
||||
in
|
||||
expand_if extension_name ~loc expr then_ else_
|
||||
| _ ->
|
||||
Location.raise_errorf ~loc
|
||||
"'%%%s' can only be used with 'let' and 'match'"
|
||||
"'%%%s' can only be used with 'let', 'match', and 'if'"
|
||||
(Extension_name.to_string extension_name)
|
||||
in
|
||||
{ expansion with pexp_attributes = expr.pexp_attributes @ expansion.pexp_attributes }
|
||||
|
10
test/test.ml
10
test/test.ml
@ -57,6 +57,16 @@ module Monad_example = struct
|
||||
match%map a with
|
||||
| 0 -> true
|
||||
| _ -> false
|
||||
|
||||
let _mif a : _ X.t =
|
||||
if%bind_open a
|
||||
then return true
|
||||
else return false
|
||||
|
||||
let _mif' a : _ X.t =
|
||||
if%map a
|
||||
then true
|
||||
else false
|
||||
end
|
||||
|
||||
module Applicative_example = struct
|
||||
|
Loading…
Reference in New Issue
Block a user