在Haskell中使用什么来代替主循环?

在Haskell中使用什么来代替主循环?,haskell,Haskell,我需要在Haskell中为main创建一个循环。我试过这个: main :: IO () main = do putStrLn "do something" main 上述代码是正确的方法吗?这种无限递归会导致溢出吗?这很好;不会发生堆栈溢出。Haskell(以及任何非严格语言)中的堆栈溢出与其他语言中的堆栈溢出不同;它们产生于积累巨大的价值而从未对其进行评估,但你在这里没有积累任何东西;只是对一系列的行动进行排序。您可以这样想:一旦打印了行,操作就被丢弃,控件直接传递到m

我需要在Haskell中为
main
创建一个循环。我试过这个:

main :: IO ()
main =
  do
    putStrLn "do something"
    main

上述代码是正确的方法吗?这种无限递归会导致溢出吗?

这很好;不会发生堆栈溢出。Haskell(以及任何非严格语言)中的堆栈溢出与其他语言中的堆栈溢出不同;它们产生于积累巨大的价值而从未对其进行评估,但你在这里没有积累任何东西;只是对一系列的行动进行排序。您可以这样想:一旦打印了行,操作就被丢弃,控件直接传递到
main
;堆栈上没有保留任何内容,因为没有需要返回的内容

这与您可以在不耗尽内存的情况下遍历无限列表的原因是一样的:随着程序在列表中进一步深入,以前的列表单元格将被垃圾收集器回收,因为它们不再需要。在这种情况下,以前的顺序将被回收,因为没有理由保留已执行的操作

也就是说,编写此特定示例的更好方法是:

import Control.Monad

main :: IO ()
main = forever $ putStrLn "do something"

当然,如果循环打算终止,这将不起作用
forever
本身是通过递归实现的,因此这样做除了可读性之外没有其他好处。

您观察到的是“尾部调用消除/优化”的结果,这对Haskell来说没有什么特别的。请参见例如

如果我这样做,为什么不会发生堆栈溢出?Haskell的递归与C的策略有什么不同,因为Haskell(像其他几种语言一样)保证“尾部调用”不分配堆栈帧:@ChrisKuklewicz:实际上,Haskell不保证尾部调用优化,事实上,编写一个导致堆栈溢出的尾部递归Haskell程序是完全可能的。但有一种方法确实适用,通常称之为。尽管如此,这只是一个实现细节;Haskell报告根本没有指定评估策略的任何细节。此外,
main
调用
main
不在尾部位置,因为定义是
main=(>>=)(putStrLn“do something”)main
。所以它是
(>>=)
,由
main
调用。IO monad中
(>>=)
的定义可能会也可能不会尾部调用其第二个参数。也就是说,我希望递归在不增加堆栈的情况下运行。augustss,应该是
(>>)
?实际上,由于懒惰,Haskell中的递归有点不同。有些在严格的语言中无法优化的东西可以在Haskell中优化。但是,如果您有一个严格的Haskell函数,那么TCO的行为与实现此功能的严格语言中的行为大致相同。