RebindableSyntax does not work as expected

You need to define your operator like this1:

(>>) :: Integer -> Aggregator Integer -> Aggregator Integer
(>>) a b = (<>) (pack a) b

And then you can do this:

finallyWorks :: Aggregator Integer
finallyWorks = do
    1
    2
    3
    mempty

The reason is that Haskell's do syntax is defined to use the >> operator in a right-associative fashion. do { mempty; 1; 2; 3; } ends up being read as mempty >> (1 >> (2 >> 3)) The >> operator itself is left-associative, so your manual example mempty >> 1 >> 2 >> 3 is read as ((mempty >> 1) >> 2) >> 3; not the same thing.

I believe this is because the rules for desugaring do blacks that bind variables (using the >>= operator rather than >>) have to associate to the right. This:

do r1 <- action1
   r2 <- action2 r1
   f r2

Desugars to this:

action1 >>= (\r1 -> action2 r1 >>= (\r2 -> f r2))

Fundamentally the later actions in the do block need to be "nested inside" the lambdas binding the results of the earlier actions, in order for those results to be in scope. This is what leads to the right-associative nature of lines in a do block.

The actual >> operator from Monad is in principle associative, so a >> (b >> c) has the same end result as (a >> b) >> c. So do blocks like yours that don't bind any variables could in theory desugar to left-associative applications of >>. But since >> is associative, there was no reason not to desugar do lines without variables in a similar manner to those that do bind variables.


1 Or a >> (Aggregator b) = Aggregator (a : b). It looks like it should even be (>>) = coerce (:), but it doesn't work without a type annotation on the :, which made it not look so nice anymore.


As I understand do will add >> on every new line, so it should work.

The way Haskell desugars the do notation is right associative. Indeed, if you write:

do {
    mempty;
    1;
    2;
    3
}

then this is desugared to:

mempty >> do {
    1;
    2;
    3
}

and thus further to:

mempty >> (1 >> do {
    2;
    3
})

if we keep desugaring, we eventually obtain mempty >> (1 >> (2 >> 3)). This matters because here we have expressions like 1 >> …, and 2 >> … where the left operand is an Num a => a, and not a Aggregator Integer.

If we for example define a function like:

(>>) :: Integer -> Integer -> Integer
(>>) = (+)

then we can write a do block:

six :: Integer
six = do
    1
    2
    3

it will return:

*RebindableSyntaxStuff> six
6

Tags:

Haskell