{"action":"created","issue":{"url":"https://api.github.com/repos/ocaml/ocaml/issues/1947","repository_url":"https://api.github.com/repos/ocaml/ocaml","labels_url":"https://api.github.com/repos/ocaml/ocaml/issues/1947/labels{/name}","comments_url":"https://api.github.com/repos/ocaml/ocaml/issues/1947/comments","events_url":"https://api.github.com/repos/ocaml/ocaml/issues/1947/events","html_url":"https://github.com/ocaml/ocaml/pull/1947","id":346167131,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA1MDk2NzIy","number":1947,"title":"WIP: Add \"monadic\" let and match operators","user":{"login":"lpw25","id":2631210,"node_id":"MDQ6VXNlcjI2MzEyMTA=","avatar_url":"https://avatars2.githubusercontent.com/u/2631210?v=4","gravatar_id":"","url":"https://api.github.com/users/lpw25","html_url":"https://github.com/lpw25","followers_url":"https://api.github.com/users/lpw25/followers","following_url":"https://api.github.com/users/lpw25/following{/other_user}","gists_url":"https://api.github.com/users/lpw25/gists{/gist_id}","starred_url":"https://api.github.com/users/lpw25/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lpw25/subscriptions","organizations_url":"https://api.github.com/users/lpw25/orgs","repos_url":"https://api.github.com/users/lpw25/repos","events_url":"https://api.github.com/users/lpw25/events{/privacy}","received_events_url":"https://api.github.com/users/lpw25/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":3,"created_at":"2018-07-31T12:33:08Z","updated_at":"2018-07-31T14:07:52Z","closed_at":null,"author_association":"CONTRIBUTOR","pull_request":{"url":"https://api.github.com/repos/ocaml/ocaml/pulls/1947","html_url":"https://github.com/ocaml/ocaml/pull/1947","diff_url":"https://github.com/ocaml/ocaml/pull/1947.diff","patch_url":"https://github.com/ocaml/ocaml/pull/1947.patch"},"body":"Based on a few recent comments and PRs, I thought it might be a good time to revive the idea of adding some support for \"monadic\" syntax to OCaml. I can't find the last attempt at this on GitHub or Mantis, but like last time the idea is to allow people to define \"let\" operators like `let*` and `let+`. The general form of the operators is `let` followed by a sequence of infix operator characters. (Note that this deliberately excludes `let!` -- the existence of `open!` and `method!` would make that too confusing).\r\n\r\n# Outline\r\n\r\nThis proposal aims to be fully general whilst making it easy to implement the common cases. In particular, it focuses on supporting functors, applicatives and monads easily. The translation chosen is a generalisation of the one used by [ppx_let](https://github.com/janestreet/ppx_let). Essentially, given a let binding like:\r\n\r\n```ocaml\r\nlet+ x = A\r\nand++ y = B\r\nand+++ z = C in\r\n  expr\r\n```\r\n\r\nit is translated directly to:\r\n\r\n```ocaml\r\nlet def = A\r\nand def2 = B\r\nand def3 = C\r\nand func = fun ((x, y), z) -> expr\r\nand (bind, single) = (let+) in\r\nbind func ((and+++) ((and++) (single def) def2) def3)\r\n```\r\n\r\nThis translation essentially treats the input to a let binding as a pair of a tagged list of bindings and a function. The `bind` function handles pairing the bindings with the function, whilst the `single`, `and++` and `and+++` functions create the list of bindings. The `bind` and `single` functions are combined to form the `(let+)` value.\r\n\r\n# Example\r\n\r\nFor a monad, I suggest the following approach to using these let operators, using `list` as an example:\r\n\r\n```ocaml\r\nlet id x = x\r\n\r\nmodule List = struct\r\n\r\n  type 'a t = 'a list\r\n\r\n  let map = List.map\r\n\r\n  let bind f l =\r\n    let l = List.map f l in\r\n    List.concat l\r\n\r\n  let product xs ys =\r\n    List.fold_right\r\n      (fun x acc -> List.map (fun y -> x, y) ys @ acc)\r\n      xs []\r\n\r\n  let (let+) = map, id\r\n  let (and+) = product\r\n  let ( let* ) = bind, id\r\n  let ( and* ) = product\r\n\r\nend\r\n```\r\n\r\nSo we use `let+` for map, `let*` for bind and set both the `and`s to the monoidal product. This gives the expected behaviours:\r\n\r\n```ocaml\r\n# let map =\r\n     List.(\r\n       let+ x = [1; 2; 3] in\r\n       x + 1\r\n     );;\r\nval map : int list = [2; 3; 4]\r\n\r\n# let map_and =\r\n     List.(\r\n       let+ x = [1; 2; 3]\r\n       and+ y = [7; 8; 9] in\r\n       x + y\r\n     );;\r\nval map_and : int list = [8; 9; 10; 9; 10; 11; 10; 11; 12]\r\n\r\n# let bind =\r\n     List.(\r\n       let* x = [1; 2; 3] in\r\n       let* y = [7; 8; 9] in\r\n       [x + y]\r\n     );;\r\nval bind : int list = [8; 9; 10; 9; 10; 11; 10; 11; 12]\r\n\r\n# let bind_and =\r\n     List.(\r\n       let* x = [1; 2; 3]\r\n       and* y = [7; 8; 9] in\r\n       [x + y]\r\n     );;\r\nval bind_and : int list = [8; 9; 10; 9; 10; 11; 10; 11; 12]\r\n\r\n# let bind_map =\r\n     List.(\r\n       let* x = [1; 2; 3] in\r\n       let+ y = [7; 8; 9] in\r\n       x + y\r\n     );;\r\nval bind_map : int list = [8; 9; 10; 9; 10; 11; 10; 11; 12]\r\n```\r\n\r\n(I've never found list to be a particularly useful monad and I think it makes these examples a bit less compelling than if they were using a more useful monad like `lwt` or `async`, however list is the simplest monad that makes clear how these operators work so I used it anyway).\r\n\r\nObserve that this approach aligns cleanly for the functor and applicative cases: for functors we can define `let+`, for applicatives we can define `let+` and `and+`, and for monads we can define all four operators. This also naturally supports monads for which the applicative operations are more efficient than the equivalent monadic operations -- i.e. where `let+ ... and+ ...` is more efficient than `let* ... let+ ...`. \r\n\r\n# Differences from `ppx_let`\r\n\r\nFor comparision, the `ppx_let` translation essentially takes:\r\n\r\n```ocaml\r\nlet+ x = A\r\nand y = B\r\nand z = C in\r\n  expr\r\n```\r\n\r\nand translates it to:\r\n\r\n```ocaml\r\nlet def = A\r\nand def2 = B\r\nand def3 = C\r\nand func = fun ((x, y), z) -> expr\r\nand (bind, and_) = (let+) in\r\nbind func (and_ (and_ def def2) def3)\r\n```\r\n\r\nA key difference between the translation in this PR and the one used by `ppx_let` is the addition of the `single` part of the `let` operators. Note that it was always `id` in the example above. One reason for this addition is that with some monads it is more efficient to gather together multiple computations and bind/map them all at once rather than to merge them together using their monoidal product and then bind/map that -- one notable example is `Deferred.t` in the async library. Having the `single` combinator allows the type of the argument to `bind` to differ from the type of the things being bound.\r\n\r\nAnother difference from `ppx_let` is the support for mixing different `let` and `and` operators together. This is partly to allow the direct alignment from operators to the functor, applicative and monad use cases -- as described above. It also makes the construct more general allowing, for example, a computation that can bind on multiple sorts of things to mix them together in a single `let ... and ...` construct.\r\n\r\n# `match` operators\r\n\r\nThis PR also adds support for `match` operators. These are simpler than `let` operators because there is no `match ... and ...` construct. The translation for these is simply:\r\n\r\n```ocaml\r\nmatch+ expr with\r\n| pat1 -> case1\r\n| pat2 -> case2\r\n```\r\n\r\nto:\r\n\r\n```ocaml\r\n(match+) (function | pat1 -> case1 | pat2 -> case2) expr\r\n```\r\n\r\nAs with `let` I would recommend that a monad bind `match+` to `map` and `match*` to `bind`.\r\n\r\n# Open questions\r\n\r\n- Currently the implementation is entirely as a translation in the parser, much as other operators are. I generally prefer to avoid elaboration and instead to give things their own AST nodes, but I wanted to keep its implementation in line with other operators for this initial attempt. Should I change the implementation before we merge? Should I give all operators there own AST nodes -- that would certainly make `pprintast.ml`s job a lot easier?\r\n\r\n- Is there any reasonable way to support qualified versions of these operators? By this I mean allowing you to use `List.(let*)` without opening `List`. I personally think it will be quite difficult to find an aesthetic syntax, and I think that much of the value of these operators does not require it. Once we have implicits this will be much less of an issue.\r\n\r\n- Currently, the `let*` is a single token from the lexer. Should it instead be two separate tokens?\r\n\r\n- How should this be integrated into the standard library? My personal preference is not to do any such integration yet, and to wait for the arrival of implicits before adding generic support for monads etc."},"comment":{"url":"https://api.github.com/repos/ocaml/ocaml/issues/comments/409233709","html_url":"https://github.com/ocaml/ocaml/pull/1947#issuecomment-409233709","issue_url":"https://api.github.com/repos/ocaml/ocaml/issues/1947","id":409233709,"node_id":"MDEyOklzc3VlQ29tbWVudDQwOTIzMzcwOQ==","user":{"login":"xavierleroy","id":3845810,"node_id":"MDQ6VXNlcjM4NDU4MTA=","avatar_url":"https://avatars0.githubusercontent.com/u/3845810?v=4","gravatar_id":"","url":"https://api.github.com/users/xavierleroy","html_url":"https://github.com/xavierleroy","followers_url":"https://api.github.com/users/xavierleroy/followers","following_url":"https://api.github.com/users/xavierleroy/following{/other_user}","gists_url":"https://api.github.com/users/xavierleroy/gists{/gist_id}","starred_url":"https://api.github.com/users/xavierleroy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xavierleroy/subscriptions","organizations_url":"https://api.github.com/users/xavierleroy/orgs","repos_url":"https://api.github.com/users/xavierleroy/repos","events_url":"https://api.github.com/users/xavierleroy/events{/privacy}","received_events_url":"https://api.github.com/users/xavierleroy/received_events","type":"User","site_admin":false},"created_at":"2018-07-31T14:07:52Z","updated_at":"2018-07-31T14:07:52Z","author_association":"CONTRIBUTOR","body":"I'd be very happy to see basic syntactic support for monads.  By which I mean a \"let\"-like construct for monadic \"bind\" and nothing else.  There is no \"and\" in any theory of monads I know of.  Not to mention the other stuff that @yminsky mentions.  \r\n\r\nCould we leave the extra syntax to existing PPXs, for people who can't live without, and just have basic monads that everyone understands in the core system?\r\n"}}
{"actor":{"display_login":"xavierleroy"}}