From cfc2ac765b8df9d3c3b28e002b08d3482f2c41c5 Mon Sep 17 00:00:00 2001 From: Jeremie Dimino Date: Wed, 31 Aug 2016 16:09:28 +0100 Subject: [PATCH] 114.08+63 --- README.md | 128 ++++++++++++++++++++++++++----------------------- _oasis | 2 +- opam | 3 +- src/ppx_let.ml | 17 ++++++- test/test.ml | 10 ++++ 5 files changed, 98 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index de43bbc63..39f824987 100644 --- a/README.md +++ b/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 map : 'a t -> f:('a -> 'b) -> '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,47 +46,61 @@ 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 -let%map P = M in E +let%map P = M in E 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 ```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 -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 @@ -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`. diff --git a/_oasis b/_oasis index 305d7f260..ec4c940b6 100644 --- a/_oasis +++ b/_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 Copyrights: (C) 2015-2016 Jane Street Group LLC diff --git a/opam b/opam index 11096c238..b02f5c062 100644 --- a/opam +++ b/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"} ] diff --git a/src/ppx_let.ml b/src/ppx_let.ml index 7c5c3198c..21c8de750 100644 --- a/src/ppx_let.ml +++ b/src/ppx_let.ml @@ -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 } diff --git a/test/test.ml b/test/test.ml index 90de6a874..9c526d27d 100644 --- a/test/test.ml +++ b/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