fmap putStrLn getLine不执行IO操作

4
标题已经说明了一切。实际上,我不明白为什么以下代码并没有打印出“Hello World”,而是与>>=的输出不同。
main = fmap putStrLn getLine

目前,这是我的推理思路,请检查是否有任何谬误。

如果我们将fmap>>=进行比较

(>>=) :: Monad m => m a -> (a -> m b) -> m b
fmap :: Functor f => (a -> b) -> f a -> f b

在bind中,上下文或者说在IO术语中的“World”,第一个m和第二个m除了类型以外完全不同。(a -> m b)本质上重新创建了一个新的“World”。但是在Functor中,上下文f是相同的,因此副作用是不可能的。
那么,既然是这样,为什么当我们尝试将具有效果的IO映射到现有的IO Monad时编译器不会发出警告呢?

12
添加 main :: IO (),你会注意到错误。在所有顶层定义中添加显式的类型签名是一个好的实践。 - user1804599
1
一个相关的问题:http://stackoverflow.com/questions/24549610/fmap-print-value-doesnt-print-anything - Sibi
1个回答

14
你已经接近成功了。 fmap putStrLn 的类型是什么?
putStrLn      ::              String -> IO ()
fmap          :: Functor f => (a -> b) -> f a -> f b    
fmap putStrLn :: Functor f => f String -> f (IO ())

作为结果,fmap putStrLn getLine将变成IO (IO ()),也就是说,一个包含另一个IO操作的IO动作。毕竟,这可能是您想要的,所以不需要警告*。编译器无法确定您是想要 m (m a) 还是 m a
这实际上就是单子的威力,它有一种操作可以将这些动作连接起来:
join :: Monad m => m (m a) -> m a
-- join x = x >>= id

* 除了可能缺少的类型签名外。 您可以使用-fwarn-missing-signatures命令告诉GHC警告您这些问题。请参见 warnings and sanity-checking


一个返回另一个IO动作的IO动作通常不太可能是正确的选择。但正如您所说,这不是编译器要评判的。 - leftaroundabout
3
在并发编程中,我曾经看到过类型为IO (IO a)的子表达式自然地出现。当一个锁被持有(或者在其他一些关键区域内),你会计算一个动作,在锁被释放(或离开关键区域)之后执行该动作。 - Boyd Stephen Smith Jr.
1
-fwarn-wrong-do-bind: 是什么意思? - Sebastian Wagner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接