Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 361
Ana Avatar asked Dec 19 '22 06:12

Ana


1 Answers

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.

like image 72
chi Avatar answered Apr 07 '23 15:04

chi