My complaints about binding operators and suggestions for improvement

Binding operators were one of the additions to 4.08 that I was most excited about. With OCaml’s very own version of Haskell’s do-notation, I could finally write

let* x = m in
e

instead of the clunkier

m >>= fun x ->
e

What’s more, OCaml’s binding operators were more extensible than Haskell’s do-notation, as you can use them not only with monads, but with functors, applicatives (the latter only being supported with the ApplicativeDo extension in GHC), or even any binary operation that took a function as the second argument. I soon found binding operators useful for non-functor/monad operations, such as continuation passing style:

let (let@) f k = f k

or local changes:

let (let@) t f =
  t.let_rank <- t.let_rank + 1;
  let result = f () in
  t.let_rank <- t.let_rank - 1;
  result

However, while binding operators alleviate “callback hell,” I find code using them hard to read in their own way. The problem is that I would prefer to use a name like “cps” or “locally” for the operations defined above, but I am forced to use an operator name. I need to remember, “does let@ mean CPS?” especially if I use multiple binding operators in the same module.

In the PR discussion for binding operators, texastoland complained that using * for bind and + for map is unintuitive, as those symbols have no immediate relation to bind and map. I agree with this criticism that the operator names are hard to understand. Even if + and * were to become a widely understood naming convention in OCaml like how >>=, another arbitrary operator name, is now widely understood to mean monadic bind in Haskell, I think a more general criticism is that for sake of readability, one should be able to use descriptive variable names with let and and, not just operators.

When there are multiple monads in scope, such as when one is working with both Option and Result, the binding operators also less ideal due to naming conflicts.

In the discussion, Keleshev suggested a syntax like

let.bind x = e1 in
e2

so the operation can have a meaningful variable name. Keleshev also suggested a syntax like

let.M x = e1 in
e2

where M defines bind and prod. The second suggestion has the flaw that it would only work for monads and applicatives, and not general binary operations that take a function, but I like the idea of being able to specify a module so that multiple binding operators with the same name, from different modules, can be used in the same code.

I would love to see a syntax like

let.e1 x = e2 in
e3

, where e1 is an arbitrary “simple” expression, like an identifier such as bind, a qualified identifier such as Lwt.bind, or a parenthesized expression like (local t). This syntax would solve the following problems:

  1. Meaningless operator names can be hard to understand, while variable names can be more descriptive.
  2. Two modules may define operations with the same name, and one might need to use both (e.g. using two monads in the same code).

I would like to hear what other people think. Do you agree with my criticisms and suggestions for improvement? Would it be too late to add this syntax, now that the current binding operator syntax has been released?

1 Like

See named let syntax by EduardoRFS · Pull Request #10979 · ocaml/ocaml · GitHub for the current proposition for a syntax extension in 5.1 .

4 Likes

What I find myself doing often to improve locality of information for the reader (until something like named letops reaches a consensus) is make use of modules. they already have excellent level of namespace opening granularity (up to an expression), so that’s what I do:

Cps.(
  let@ chan = In_channel.with_open_text "hello.txt" in
    (* .. do stuff with chan .. *); In_channel.close chan
)
1 Like

This is something we have in reason and I can not imagine using anything else. The readability gains are tremendous

There is ppx_let which provides this kind of pattern in the JS ecosytem. I found pretty useful when I was in the habit of using Base. Not that it would be too hard to add in the Let_syntax modules where you need them if you wanted to make use of it though.

1 Like