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 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,15 +46,21 @@ 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
@ -65,24 +70,32 @@ 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
@ -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
View File

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

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

View File

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

View File

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