目标:取一个字符串并在控制台中打印相同的内容,就像需要永远完成相同的操作一样
我提出了类似这样的事情,没有编译错误但没有按预期工作。
greet_buggy :: String -> IO ()
greet_buggy = forever $ putStrLn
问题是在字符串下面没有在控制台中打印任何内容。
greet_buggy "something"
基于this post我尝试调试并更改了如下定义。它工作正常
greet :: IO ()
greet = forever $ putStrLn "Hello"
任何人都可以解释这里发生的事情吗?是否可以单独使用forever
来实现相同的效果?
编辑:找到了一个相关的post(感谢@Daniel Wagner),即使这个问题与我的不同,答案也解释了forever
。
答案 0 :(得分:8)
greet_buggy
正在处理另一个monad。
forever
采取monadic动作并无限期地重复它。它可以这样定义:
forever a = let loop = a >> loop in loop
也可以显示为
forever a = a >> a >> a >> a >> ... (infinitely many times)
(事实上,永远定义的是Applicative,而不是Monad;但现在这并不重要。)
所以forever greet
实际上是
putStrLn "Hello" >> putStrLn "Hello" >> putStrLn "Hello" >> ...
无需进一步解释。
OTOH forever greet_buggy
相当于
putStrLn >> putStrLn >> putStrLn >> ...
现在,由于(-> a)
是一个monad,>>
是为任何两个合适类型的函数定义的,f >> g
在这种情况下的含义是...... {{1} }}!所以
g
是取( putStrLn >> putStrLn >> putStrLn >> ... ) "Hello"
链中的最后一个函数并将其应用于>>
。当然,那里没有最后的功能,所以这只是永远运行。
答案 1 :(得分:4)
谁能解释一下这里发生了什么?
我们有
putStrLn :: String -> IO ()
顺便提一下,是(->) String
monad中的值。因此,
forever putStrLn :: (->) String b
其中b
是普遍量化的,因此我们也有(不幸的)
forever putStrLn :: (->) String (IO ())
使发布的代码进行类型检查。
要了解forever
monad中(->) String
的内容,请回忆一下:
m >>= g
= -- definition of >>=
\x -> g (m x) x
因此
m >> f
= -- definition of >> in terms of >>=
m >>= const f
= -- definition of >>=
\x -> const f (m x) x
= -- beta
\x -> f x
= -- eta
f
返回forever
:回想一下其定义
forever m = m >> forever m
与递归定义
等效(在(->) String
monad中)
forever m = forever m
导致无用的无限循环。
答案 2 :(得分:2)
forever
一遍又一遍地执行完全相同的IO动作(或一般来说,一元动作)。如果您的函数接受String作为输入,forever
将永远打印相同的字符串。相反,如果您希望每次都从用户读取字符串,则必须在IO操作中包含该字符串。
什么是简单的IO操作,从用户读取一行然后打印出来?将getLine
和putStrLn
:
echo :: IO ()
echo = getLine >>= putStrLn
然后将该操作传递给forever
:
cat :: IO ()
cat = forever echo
或者,事实证明Prelude中已经有一些内容可以为你完成这一切:我们所写的内容相当于:
cat :: IO ()
cat = interact id