我正在关注monad变形金刚教程here。
At this point in the tutorial,它要求我尝试为EitherIO
数据类型实现Monad实例,定义为:
data EitherIO e a = EitherIO {
runEitherIO :: IO (Either e a)
}
所以我试过了:
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
教程的版本有点不同:
instance Monad (EitherIO e) where
return = pure -- the same as EitherIO . return . Right
x >>= f = EitherIO $ runEitherIO x >>= either (return . Left) (runEitherIO . f)
现在,所有类型都适合,所以我认为我很好,并祝贺自己终于找到join
的用途。
事实证明,further down the tutorial,我被要求在以下内容中运行runEitherIO getToken
:
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)
使用我的>>=
运算符版本,GHCi会在我提供一些输入后挂起。然后,即使我通过^C
中断,GHCi也会开始奇怪地行动,在我打字的时候挂了一两秒钟。我必须杀死GHCi重启。当我用教程定义替换>>=
定义时,一切正常。
我的完整档案是here。
所以:
我的定义有什么问题?
为什么GHCi会进行类型检查然后表现得像?这是GHCi的错误吗?
答案 0 :(得分:6)
我的猜测是,这是罪魁祸首:
instance Monad (EitherIO e) where
return = EitherIO . return . Right
x >>= f = join $ fmap f x
但是,join
定义为:
join x = x >>= id
但是,以这种方式join
和>>=
以相互递归的方式定义,导致非终止。
请注意,此类型会检查,如下所示:
f, g :: Int -> Int
f x = g x
g x = f x
结论:您应该为>>=
提供一个不涉及join
的定义。
答案 1 :(得分:5)
Control.Monad
中的 join
定义如下:
join :: (Monad m) => m (m a) -> m a
join x = x >>= id
您看,join
是根据>>=
定义的。所以简短的回答是,你对>>=
的定义进入无限循环(即不终止),因为它使用join
,而>>=
又使用>>=
。
如果你考虑一下,你可以对每Monad
使用ghc
的这个定义。因此它不可能工作,因为它根本不使用该类型的内部结构。
至于为什么{{1}}没有检测到这个,这是一个无限循环,但不是类型错误。 Haskell的类型系统不足以检测这样的循环。