在此示例中,我对绑定运算符的 Haskell 定义有什么问题?
What is wrong with my Haskell definition of the bind operator in this example?
我正在学习 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 错误吗?
我猜这是罪魁祸首:
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
.
的定义
join
在Control.Monad
中的定义如下:
join :: (Monad m) => m (m a) -> m a
join x = x >>= id
你看,join
是根据 >>=
定义的。所以简短的回答是,你对 >>=
的定义进入了一个无限循环(即不终止),因为它使用了 join
,而后者又再次使用了 >>=
。
如果您考虑一下,您可以对每个 Monad
使用 >>=
的这个定义。所以它不可能工作,因为它根本没有使用类型的内部结构。
至于为什么ghc
检测不到,这是死循环,不是类型错误。 Haskell 的类型系统不够强大,无法检测到此类循环。
我正在学习 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 错误吗?
我猜这是罪魁祸首:
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
.
join
在Control.Monad
中的定义如下:
join :: (Monad m) => m (m a) -> m a
join x = x >>= id
你看,join
是根据 >>=
定义的。所以简短的回答是,你对 >>=
的定义进入了一个无限循环(即不终止),因为它使用了 join
,而后者又再次使用了 >>=
。
如果您考虑一下,您可以对每个 Monad
使用 >>=
的这个定义。所以它不可能工作,因为它根本没有使用类型的内部结构。
至于为什么ghc
检测不到,这是死循环,不是类型错误。 Haskell 的类型系统不够强大,无法检测到此类循环。