114.08+63

This commit is contained in:
Jeremie Dimino 2016-08-31 16:09:28 +01:00
parent 2d59732ae3
commit cfc2ac765b
5 changed files with 98 additions and 62 deletions

120
README.md
View File

@ -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
View File

@ -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
View File

@ -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"} ]

View File

@ -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 }

View File

@ -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