在Haskell中永远理解

时间:2016-08-24 14:55:25

标签: haskell monads

目标:取一个字符串并在控制台中打印相同的内容,就像需要永远完成相同的操作一样

我提出了类似这样的事情,没有编译错误但没有按预期工作。

greet_buggy :: String -> IO ()
greet_buggy = forever $ putStrLn

问题是在字符串下面没有在控制台中打印任何内容。

greet_buggy "something"

基于this post我尝试调试并更改了如下定义。它工作正常

greet :: IO ()
greet = forever $ putStrLn "Hello"

任何人都可以解释这里发生的事情吗?是否可以单独使用forever来实现相同的效果?

编辑:找到了一个相关的post(感谢@Daniel Wagner),即使这个问题与我的不同,答案也解释了forever

3 个答案:

答案 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操作,从用户读取一行然后打印出来?将getLineputStrLn

组合在一起
echo :: IO ()
echo = getLine >>= putStrLn

然后将该操作传递给forever

cat :: IO ()
cat = forever echo

或者,事实证明Prelude中已经有一些内容可以为你完成这一切:我们所写的内容相当于:

cat :: IO ()
cat = interact id