Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RebindableSyntax does not work as expected

Tags:

haskell

When I define >> function (with RebindableSyntax extension) and use it directly (mempty >> 1 >> 2 >> 3) everything works as expected, but when I use do block I get following error:

Couldn't match expected type ‘Integer’
            with actual type ‘Aggregator Integer’
In a stmt of a 'do' block: 1
In the expression:
  do mempty
     1
     2
     3
In an equation for ‘notWorking’:
    notWorking
      = do mempty
           1
           2
           3

Whole code:

{-# LANGUAGE  RebindableSyntax #-}

module RebindableSyntaxStuff where

import Prelude hiding ((>>), (>>=), return)

newtype Aggregator a = Aggregator [a]
    deriving(Show)

instance Semigroup (Aggregator a) where
    (Aggregator xs) <> (Aggregator ys) = Aggregator (xs++ys)

instance Monoid (Aggregator a) where
    mempty = Aggregator []

pack :: a -> Aggregator a
pack x = Aggregator [x]

working :: Aggregator Integer
working = mempty >> 1 >> 2 >> 3 --Returns Aggregator [1,2,3]

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

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

As I understand do will add >> on every new line, so it should work.
Where did I make a mistake?

Solution

The way Haskell desugars the do notation is right associative

I changed (>>) function to be infixr and also order of arguments. Code below:

working :: Aggregator Integer
working = 1 >> 2 >> 3 >> mempty 

notWorking :: Aggregator Integer
notWorking = do -- Now works
    1
    2
    3
    mempty

infixr 0 >>
(>>) :: Integer -> Aggregator Integer -> Aggregator Integer
(>>) a = (<>) (pack a)
like image 999
t4ccer Avatar asked Jul 26 '20 21:07

t4ccer


1 Answers

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.

like image 94
Ben Avatar answered Sep 27 '22 19:09

Ben