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?
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With