IO的无限循环如何在haskell中工作

时间:2017-03-22 18:06:49

标签: haskell io monads

在haskell中,我们编写的所有IO代码只是一个动作(很多人建议将其视为正在生成的脚本)。它是最终执行它们的主要方法(执行构造的脚本)。那么以下程序如何工作? infi函数永远不会返回。那么为什么字符串会被无限打印?

infi = 
    do
     print "hello"
     infi

main = 
    do
     infi

4 个答案:

答案 0 :(得分:3)

您似乎对Haskell如何实际实现IO有误解。关于这个主题还有很多其他文献以及本网站上的其他几个答案,所以我将专注于你的具体例子,而不是一般的。

infi = 
  do
   print "hello"
   infi

main = 
  do
    infi

首先,您可以简化主要操作(它不需要do):

main = infi 

在haskell中,“返回”与命令式“返回”不同。它只是意味着注入一元行动,即return :: Monad m => a -> m a。因此,让我们谈谈在这里评估的内容,而不是事情的回归。

您的所有main函数都会调用infi类型的值infi :: IO ()。由于infiIO操作,因此可以执行打印。与任何其他值一样,它也可以引用其他值(在这种情况下,它是递归的,因此它自己调用)。如果没有基本情况,infi将继续执行以下序列(就像它在do区块中的布局一样!):

  1. 打印“你好”到STDOUT
  2. 评估值infi
    1. 打印“你好”到STDOUT
    2. 评估值infi
      ... 堆栈永远持续,因为递归中没有基本情况。
  3. 这可以起作用的主要原因是因为Haskell的懒惰评估。在你需要它之​​前,Haskell实际上从未计算过值。这就是为什么你也可以用无限列表做纯粹的动作:

    let x = [1..] -- x is an infinite list. If you told haskell to print every element, it would run forever since it would evaluate the whole thing. 
    let y = x !! 3 -- y = 2. This is not infinite because you are only evaluating the first three elements, instead of the whole value.
    

    你的无限价值infi也是如此。 Haskell可以创建一个包含无限动作的“运行时脚本”,因为infi的具有有限的表示(其名称),但它无限评估,因为它没有基本情况。

答案 1 :(得分:2)

infi本质上是同一IO动作的无限流,与monadic排序(>>)而非cons(:)相关联:

fives = 5                :  fives
infi  = putStrLn "hello" >> infi

事实上,我们可以使用动作列表infi'来抽象出monadic绑定:

infi' :: [IO ()]
infi' = putStrLn "hello" : infi'

然后使用infi恢复sequence_,可以foldr (>>) (return ())实现。

infi = sequence_ infi'
infi = (foldr (>>) (return ()) infi')
infi = putStrLn "hello" >> (foldr (>>) (return ()) infi')
infi = putStrLn "hello" >> (putStrLn "hello" >> (foldr (>>) (return ()) infi'))
infi = putStrLn "hello" >> (putStrLn "hello" >> (putStrLn "hello" >> ...))

将动作存储在这样的流中也可以将它们作为一等值进行操作:

> sequence_ (take 3 infi')
hello
hello
hello

当Haskell运行时执行main操作时,它会评估infi,找到>>表达式,计算其左侧参数以生成操作putStrLn "hello",< em>执行该操作,然后继续执行右手参数 - 恰好是infi。评估由Monad IO实例中的内部模式匹配延迟驱动。

答案 2 :(得分:1)

它非常类似于程序

ones = 1 : ones

以上是递归的,是的。它无限次地称自己为是,是的。但确实返回。它返回一个无限列表。相比之下,

noList = noList

将永远循环而不返回列表。 (实际上,GHC运行时检测到这一点并引发异常,但这与讨论无关。)

类似地,

printOnes = print 1 >> printOnes
-- or, equivalently
printOnes = do
   print 1
   printOnes

构建一个IO动作,永远打印1,即使它无数次无限地递归。取而代之的是,

noPrint = noPrint

会永远循环,永远不会返回IO动作。

答案 3 :(得分:-1)

因为infi被设置为递归调用它自己。 &#39;返回&#39;因为对infi的第一次调用永远不会回来,所以从主要的预期永远不会得到满足。

# The first call to infi will never return as the calls to infi
# will just continue to add more calls to the stack, until you exceed
# the size :)

main ->
  infi ->
    infi ->
      infi -> ..