有几种方法可以做到这一点不使用可变单元格。您已经在第二次尝试中做到了,只是有一点错误。您需要将初始值传递给 tick' 函数,而不是“设置它”(haskell 不知道分配变量 - 只有定义。如果出现 x = y 行,x 将是 y整个生命周期)。
tick = tick' 0
where ...
counter = 0 行没有做任何事情;它正在定义一个从未使用过的名称。 tick' 函数中使用的 counter 被绑定为其参数之一(并隐藏定义为 0 的参数)。花点时间考虑一下,看看这是否有意义。
我们也可以通过一种很好的“高阶”方式来做到这一点。本质上我们想要运行无限长的代码块:
do
print 0
print 1
print 2
...
有一个名为sequence :: [IO a] -> IO [a] 的函数(请参阅下面的警告),它将获取一个动作列表并构造一个动作。因此,如果我们可以构造列表[print 0, print 1, print 2, ...],那么我们可以将其传递给sequence,以构建我们正在寻找的无限长块。
请注意,这是 Haskell 中一个非常重要的概念:[print 0, print 1, print 2] 不打印这三个数字然后构造列表[0,1,2]。相反,它本身是一个 actions 列表,其类型为 [IO ()]。列出清单什么都不做;只有当您将操作绑定到main 时,它才会被执行。例如,我们可能会说:
main = do
let xs = [putStrLn "hello", getLine >> putStrLn "world"]
xs !! 0
xs !! 0
xs !! 1
xs !! 1
xs !! 0
这将打印两次hello,两次得到一行并在每次之后打印world,然后再次打印hello。
有了这个概念,很容易用列表理解构建动作列表[print 0, print 1, ...]:
main = sequence [ print x | x <- [0..] ]
我们可以简化一下:
main = sequence (map (\x -> print x) [0..])
main = sequence (map print [0..])
所以map print [0..] 是我们正在寻找的[print 0, print 1, ...] 操作列表,然后我们只需将其传递给sequence,后者将它们链接在一起。
sequence这种模式很常见,并且有自己的mapM:
mapM :: (a -> IO b) -> [a] -> IO [b]
mapM f xs = sequence (map f xs)
因此:
main = mapM print [0..]
尽可能简单。
关于性能的一点说明:由于我们没有使用这些函数的输出,我们应该使用sequence_ 和mapM_,并带有尾随下划线,它们为此目的进行了优化。由于垃圾收集,通常这在 Haskell 程序中无关紧要,但在这个特定的用例中,由于各种微妙之处,这是一种特殊情况。您会发现,如果没有 _s,程序的内存使用量会随着结果列表(在本例中为 [(),(),(),...])的构建而逐渐增加,但从未使用过。
警告:我已经给出了sequence 和mapM 的类型签名,专门针对IO,而不是一般的monad,这样读者就不必学习正交概念了同时具有类型和类型类的动作。