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:
- Meaningless operator names can be hard to understand, while variable names can be more descriptive.
- 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?