
It seems the applicative do is almost identical to the form ML and Scheme has had since the beginning. Perhaps that semantic similarity might inform the syntactic debate.
do a <- f g b <- h pure $ foo a b
into this:
(\a b -> pure $ foo a b) <*> (f <*> g *> h)
This form, although very similar in appearance to the monadic do, is very different in the scoping of bound identifiers. To me, that is quite unfortunate, greatly increasing confusion. In the monadic do, do x <- e1 y <- e2 e3 the identifier 'x' may appear in e2 and e3. To be more precise, the scope of each bound identifier extends to the rest of the do-block and can be used on the right-hand-side of other bindings in the rest of the block. In the proposed applicative do above, the identifier 'a' may not appear within g and h. Such a significant difference in scoping calls for a different name at the very least; furthermore, it calls for a compiler supports so that errors, such as using the identifier 'a' in 'h' by mistake (common during editing and refactoring) could be reported with an intelligent error message. Also, in monadic-do, it makes sense to bind the same identifier several times, as in do {x <- e1; x <- e2; e3}. It makes little sense doing that in applicative-do, and probably should be disallowed. Bob Atkey wrote:
applicatively let x = foo let y = bar in <pure stuff>
But OCaml already has the right syntax -- and so does Scheme. That syntax is let. In Scheme expression (let ((x e1) (y e2) (_ e11)) e3) only the expression e3 is in scope of x and y. Emphatically, e2 is not in scope in x; so the value bound to x cannot be used during the evaluation of e2. The expression e11 is evaluated only for its side-effect (underscore is just an identifier, I could've used `dummy'). Again, the expression e11 is not in scope of x and y. All identifiers bound by the let-clause must be distinct. The equivalent OCaml construction is let x = e1 and y = e2 and _ = e11 in e3 If, by mistake, we do mention x or y in e11 (and there are no global binding to x and y), OCaml will report the error about the unbound identifier. It seems the parallel let of Scheme or OCaml fall short of Applicative in one respect: the order of evaluating binding is not specified. If we agreed that the binders of a parallel let must be evaluated in order, we would get exactly the desired `applicative-do'. Perhaps, a better form in OCaml would be let seq x = foo and y = bar in e3 or let x = foo andalso y = bar in e3 Perhaps it may make sense to use the keyword `let' rather than 'do' in Haskell as well? P.S. For completeness, both Scheme and OCaml have the exact equivalent of monadic do. Haskell's do x <- e1 y <- e2 x <- e3 e4 e5 can be written in Scheme as (let* ((x e1) (y e2) (x e3) (_ e4)) e5) and in OCaml as let x = e1 in let y = e2 in let x = e3 in let () = e4 in e5 The latter pattern is very common in OCaml.

Excerpts from oleg's message of Sat Oct 10 11:36:44 +0200 2009:
It seems the applicative do is almost identical to the form ML and Scheme has had since the beginning. Perhaps that semantic similarity might inform the syntactic debate.
do a <- f g b <- h pure $ foo a b
into this:
(\a b -> pure $ foo a b) <*> (f <*> g *> h)
This form, although very similar in appearance to the monadic do, is very different in the scoping of bound identifiers. To me, that is quite unfortunate, greatly increasing confusion. In the monadic do, do x <- e1 y <- e2 e3 the identifier 'x' may appear in e2 and e3. To be more precise, the scope of each bound identifier extends to the rest of the do-block and can be used on the right-hand-side of other bindings in the rest of the block. In the proposed applicative do above, the identifier 'a' may not appear within g and h. Such a significant difference in scoping calls for a different name at the very least; furthermore, it calls for a compiler supports so that errors, such as using the identifier 'a' in 'h' by mistake (common during editing and refactoring) could be reported with an intelligent error message.
I also think that an applicative 'do' will be very confusing and unfortunate. And these are very good arguments.
Also, in monadic-do, it makes sense to bind the same identifier several times, as in do {x <- e1; x <- e2; e3}. It makes little sense doing that in applicative-do, and probably should be disallowed.
Again good argument. [...]
P.S. For completeness, both Scheme and OCaml have the exact equivalent of monadic do. Haskell's do x <- e1 y <- e2 x <- e3 e4 e5 can be written in Scheme as (let* ((x e1) (y e2) (x e3) (_ e4)) e5) and in OCaml as let x = e1 in let y = e2 in let x = e3 in let () = e4 in e5 The latter pattern is very common in OCaml.
Seriously, are you joking? I agree that the OCaml 'let' denotes both a local binding and the sequence of effects. But the relation with the monadic do is very thin. As you know OCaml always lives in an implicit big IO monad and so its 'let' syntax is a monadic do. However there is plenty of other monads that could use the do notation, and of course you know them well (and there is Camlp4 extensions in OCaml for that). Best regards, -- Nicolas Pouillard http://nicolaspouillard.fr
participants (2)
-
Nicolas Pouillard
-
oleg@okmij.org