114.08+63
This commit is contained in:
parent
2d59732ae3
commit
cfc2ac765b
128
README.md
128
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
|
Overview
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The aim of this rewriter is to make monadic and applicative code look
|
The aim of this rewriter is to make monadic and applicative code look nicer by
|
||||||
nicer by writing custom binders the same way that we normally bind
|
writing custom binders the same way that we normally bind variables. In OCaml,
|
||||||
variables. In OCaml, the common way to bind the result of a
|
the common way to bind the result of a computation to a variable is:
|
||||||
computation to a variable is:
|
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
let VAR = EXPR in BODY
|
let VAR = EXPR in BODY
|
||||||
```
|
```
|
||||||
|
|
||||||
ppx\_let simply adds two new binders: `let%bind` and
|
ppx\_let simply adds two new binders: `let%bind` and `let%map`. These are
|
||||||
`let%map`. These are rewritten into calls to the `bind` and
|
rewritten into calls to the `bind` and `map` functions respectively. These
|
||||||
`map` functions respectively. These functions are expected to have
|
functions are expected to have
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
val map : 'a t -> f:('a -> 'b) -> 'b t
|
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.
|
for some type `t`, as one might expect.
|
||||||
|
|
||||||
These functions are to be provided by the user, and are generally
|
These functions are to be provided by the user, and are generally expected to be
|
||||||
expected to be part of the signatures of monads and applicatives
|
part of the signatures of monads and applicatives modules. This is the case for
|
||||||
modules. This is the case for all monads and applicatives defined by
|
all monads and applicatives defined by the Jane Street's Core suite of
|
||||||
the Jane Street's Core suite of libraries. (see the section below on
|
libraries. (see the section below on getting the right names into scope).
|
||||||
getting the right names into scope).
|
|
||||||
|
|
||||||
### Parallel bindings
|
### 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
|
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
|
The `and` keyword is seen as a binding combination operator. To do so it expects
|
||||||
it expects the presence of a `both` function, that lifts the OCaml
|
the presence of a `both` function, that lifts the OCaml pair operation to the
|
||||||
pair operation to the type `t` in question:
|
type `t` in question:
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
val both : 'a t -> 'b t -> ('a * 'b) t
|
val both : 'a t -> 'b t -> ('a * 'b) t
|
||||||
@ -47,47 +46,61 @@ val both : 'a t -> 'b t -> ('a * 'b) t
|
|||||||
|
|
||||||
### Match statements
|
### Match statements
|
||||||
|
|
||||||
We found that this form was quite useful for match statements as
|
We found that this form was quite useful for match statements as well. So for
|
||||||
well. So for convenience ppx\_let also accepts `%bind` and `%map` on
|
convenience ppx\_let also accepts `%bind` and `%map` on the `match` keyword.
|
||||||
the `match` keyword. Morally `match%bind expr with cases` is seen as
|
Morally `match%bind expr with cases` is seen as `let%bind x = expr in match x
|
||||||
`let%bind x = expr in match x with cases`.
|
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
|
Syntactic forms and actual rewriting
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
`ppx_let` adds four syntactic forms
|
`ppx_let` adds six syntactic forms
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
let%bind P = M in E
|
let%bind P = M in E
|
||||||
|
|
||||||
let%map P = M in E
|
let%map P = M in E
|
||||||
|
|
||||||
match%bind M with P1 -> E1 | P2 -> E2 | ...
|
match%bind M with P1 -> E1 | P2 -> E2 | ...
|
||||||
|
|
||||||
match%map 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
|
that expand into
|
||||||
|
|
||||||
```ocaml
|
```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.
|
respectively.
|
||||||
|
|
||||||
As with `let`, `let%bind` and `let%map` also support multiple
|
As with `let`, `let%bind` and `let%map` also support multiple *parallel*
|
||||||
*parallel* bindings via the `and` keyword:
|
bindings via the `and` keyword:
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
let%bind P1 = M1 and P2 = M2 and P3 = M3 and P4 = M4 in E
|
let%bind P1 = M1 and P2 = M2 and P3 = M3 and P4 = M4 in E
|
||||||
|
|
||||||
let%map P1 = M1 and P2 = M2 and P3 = M3 and P4 = M4 in E
|
let%map P1 = M1 and P2 = M2 and P3 = M3 and P4 = M4 in E
|
||||||
```
|
```
|
||||||
|
|
||||||
that expand into
|
that expand into
|
||||||
@ -96,35 +109,34 @@ that expand into
|
|||||||
let x1 = M1 and x2 = M2 and x3 = M3 and x4 = M4 in
|
let x1 = M1 and x2 = M2 and x3 = M3 and x4 = M4 in
|
||||||
bind
|
bind
|
||||||
(both x1 (both x2 (both x3 x4)))
|
(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
|
let x1 = M1 and x2 = M2 and x3 = M3 and x4 = M4 in
|
||||||
map
|
map
|
||||||
(both x1 (both x2 (both x3 x4)))
|
(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
|
respectively. (Instead of `x1`, `x2`, ... ppx\_let uses variable names that are
|
||||||
variable names that are unlikely to clash with other names)
|
unlikely to clash with other names)
|
||||||
|
|
||||||
As with `let`, names introduced by left-hand sides of the let bindings
|
As with `let`, names introduced by left-hand sides of the let bindings are not
|
||||||
are not available in subsequent right-hand sides of the same sequence.
|
available in subsequent right-hand sides of the same sequence.
|
||||||
|
|
||||||
Getting the right names in scope
|
Getting the right names in scope
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
The description of how the `%bind` and `%map` syntax extensions expand
|
The description of how the `%bind` and `%map` syntax extensions expand left out
|
||||||
left out the fact that the names `bind`, `map`, `both`, and `return`
|
the fact that the names `bind`, `map`, `both`, and `return` are not used
|
||||||
are not used directly, but rather qualified by `Let_syntax`. For
|
directly, but rather qualified by `Let_syntax`. For example, we use
|
||||||
example, we use `Let_syntax.bind` rather than merely `bind`. This
|
`Let_syntax.bind` rather than merely `bind`. This means one just needs to get a
|
||||||
means one just needs to get a properly loaded `Let_syntax` module in
|
properly loaded `Let_syntax` module in scope to use `%bind` and `%map`.
|
||||||
scope to use `%bind` and `%map`.
|
|
||||||
|
|
||||||
For monads, `Core.Std.Monad.Make` produces a submodule `Let_syntax` of
|
For monads, `Core.Std.Monad.Make` produces a submodule `Let_syntax` of the
|
||||||
the appropriate form.
|
appropriate form.
|
||||||
|
|
||||||
For applicatives. The convention for these modules is to have a
|
For applicatives. The convention for these modules is to have a submodule
|
||||||
submodule `Let_syntax` of the form
|
`Let_syntax` of the form
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
module Let_syntax : sig
|
module Let_syntax : sig
|
||||||
@ -135,15 +147,13 @@ module Let_syntax : sig
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
The `Open_on_rhs` submodule is used by variants of `%map` and `%bind`
|
The `Open_on_rhs` submodule is used by variants of `%map` and `%bind` called
|
||||||
called `%map_open` and `%bind_open`. It is locally opened on the
|
`%map_open` and `%bind_open`. It is locally opened on the right hand sides of
|
||||||
right hand sides of the rewritten let bindings in `%map_open` and
|
the rewritten let bindings in `%map_open` and `%bind_open` expressions. For
|
||||||
`%bind_open` expressions. For `match%map_open` and `match%bind_open`
|
`match%map_open` and `match%bind_open` expressions, `Open_on_rhs` is opened for
|
||||||
expressions, `Open_on_rhs` is opened for the expression being matched
|
the expression being matched on.
|
||||||
on.
|
|
||||||
|
|
||||||
`Open_on_rhs` is useful when programming with applicatives, which
|
`Open_on_rhs` is useful when programming with applicatives, which operate in a
|
||||||
operate in a staged manner where the operators used to construct the
|
staged manner where the operators used to construct the applicatives are
|
||||||
applicatives are distinct from the operators used to manipulate the
|
distinct from the operators used to manipulate the values those applicatives
|
||||||
values those applicatives produce. For monads, `Open_on_rhs` contains
|
produce. For monads, `Open_on_rhs` contains `return`.
|
||||||
`return`.
|
|
||||||
|
2
_oasis
2
_oasis
@ -2,7 +2,7 @@ OASISFormat: 0.4
|
|||||||
OCamlVersion: >= 4.03.0
|
OCamlVersion: >= 4.03.0
|
||||||
FindlibVersion: >= 1.3.2
|
FindlibVersion: >= 1.3.2
|
||||||
Name: ppx_let
|
Name: ppx_let
|
||||||
Version: 114.08+30
|
Version: 114.08+63
|
||||||
Synopsis: Monadic let-bindings
|
Synopsis: Monadic let-bindings
|
||||||
Authors: Jane Street Group, LLC <opensource@janestreet.com>
|
Authors: Jane Street Group, LLC <opensource@janestreet.com>
|
||||||
Copyrights: (C) 2015-2016 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: [
|
depends: [
|
||||||
"ocamlbuild" {build}
|
"ocamlbuild" {build}
|
||||||
"oasis" {build & >= "0.4" & < "0.4.7"}
|
"oasis" {build & >= "0.4"}
|
||||||
"ocamlfind" {build & >= "1.3.2"}
|
"ocamlfind" {build & >= "1.3.2"}
|
||||||
"js-build-tools" {build}
|
"js-build-tools" {build}
|
||||||
"ppx_core"
|
"ppx_core"
|
||||||
"ppx_driver"
|
"ppx_driver"
|
||||||
]
|
]
|
||||||
available: [ ocaml-version >= "4.03.0" ]
|
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)
|
~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 expand ~loc:_ ~path:_ extension_name expr =
|
||||||
let loc = expr.pexp_loc in
|
let loc = expr.pexp_loc in
|
||||||
let expansion =
|
let expansion =
|
||||||
@ -110,9 +116,18 @@ let expand ~loc:_ ~path:_ extension_name expr =
|
|||||||
(Extension_name.to_string extension_name)
|
(Extension_name.to_string extension_name)
|
||||||
| Pexp_match (expr, cases) ->
|
| Pexp_match (expr, cases) ->
|
||||||
expand_match extension_name ~loc 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
|
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)
|
(Extension_name.to_string extension_name)
|
||||||
in
|
in
|
||||||
{ expansion with pexp_attributes = expr.pexp_attributes @ expansion.pexp_attributes }
|
{ 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
|
match%map a with
|
||||||
| 0 -> true
|
| 0 -> true
|
||||||
| _ -> false
|
| _ -> 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
|
end
|
||||||
|
|
||||||
module Applicative_example = struct
|
module Applicative_example = struct
|
||||||
|
Loading…
Reference in New Issue
Block a user