Reputation:
I'm following a monad transformers tutorial here.
At this point in the tutorial, it asks me to try to implement the Monad instance for the EitherIO
data type, defined as:
data EitherIO e a = EitherIO {
runEitherIO :: IO (Either e a)
}
So I tried:
instance Functor (EitherIO e) where
fmap f = EitherIO . fmap (fmap f) . runEitherIO
instance Monad (EitherIO e) where
return = EitherIO . return . Right
x >>= f = join $ fmap f x
The tutorial's version was a bit different:
instance Monad (EitherIO e) where
return = pure -- the same as EitherIO . return . Right
x >>= f = EitherIO $ runEitherIO x >>= either (return . Left) (runEitherIO . f)
Now, the types all fit, so I thought I was good, and congratulated myself on finally finding a use for join
.
As it turns out, further down the tutorial, I was asked to run runEitherIO getToken
on the following:
liftEither x = EitherIO (return x)
liftIO x = EitherIO (fmap Right x)
getToken = do
liftIO (T.putStrLn "Enter email address:")
input <- liftIO T.getLine
liftEither (getDomain input)
Using my version of the >>=
operator, GHCi would hang after I provided some input. Then even after I interrupt via ^C
, GHCi would start acting strangely, hanging for a second or two while I'm typing. I'd have to kill GHCi to restart. When I replaced the >>=
definition with the tutorial definition, everything worked.
My full file is here.
So:
What is wrong with my definition?
Why would GHCi typecheck and then behave like it did? Is this a GHCi bug?
Upvotes: 4
Views: 178
Reputation: 680
join
in Control.Monad
is defined as follows:
join :: (Monad m) => m (m a) -> m a
join x = x >>= id
You see, that join
is defined in terms of >>=
. So the short answer is, that your definition of >>=
gets into an endless loop (i.e. does not terminate), because it uses join
, which in turn uses >>=
again.
If you think about it, you could use this definition of >>=
for every Monad
. So it cannot possibly work, because it makes no use of the internal structure of the type at all.
As for why this is not detected by ghc
, this is an endless loop, but not a type error. The type system of Haskell is not powerful enough to detect such loops.
Upvotes: 5
Reputation: 116139
My guess is that this is the culprit:
instance Monad (EitherIO e) where
return = EitherIO . return . Right
x >>= f = join $ fmap f x
However, join
is defined as:
join x = x >>= id
However, in this way join
and >>=
get defined in a mutually recursive way which leads to non termination.
Note that this type checks, much as the following does:
f, g :: Int -> Int
f x = g x
g x = f x
Bottom line: you should provide a definition for >>=
which does not involve join
.
Upvotes: 6