user1002430
user1002430

Reputation:

What is wrong with my Haskell definition of the bind operator in this example?

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:

  1. What is wrong with my definition?

  2. Why would GHCi typecheck and then behave like it did? Is this a GHCi bug?

Upvotes: 4

Views: 178

Answers (2)

ipsec
ipsec

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

chi
chi

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

Related Questions