在haskell中,我们编写的所有IO代码只是一个动作(很多人建议将其视为正在生成的脚本)。它是最终执行它们的主要方法(执行构造的脚本)。那么以下程序如何工作? infi
函数永远不会返回。那么为什么字符串会被无限打印?
infi =
do
print "hello"
infi
main =
do
infi
答案 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 ()
。由于infi
是IO
操作,因此可以执行打印。与任何其他值一样,它也可以引用其他值(在这种情况下,它是递归的,因此它自己调用)。如果没有基本情况,infi将继续执行以下序列(就像它在do
区块中的布局一样!):
这可以起作用的主要原因是因为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 -> ..