Haskell IO通常用整个程序来解释,它是一个main返回IO值(通常被描述为命令式IO程序)的纯函数(),然后由运行时执行.
这种心理优良样板工程为简单的例子,但只要跌倒了我,因为我看到了一个递归main在了解你的Haskell.例如:
main = do
line <- getLine
putStrLn line
main
Run Code Online (Sandbox Code Playgroud)
或者,如果您愿意:
main = getLine >>= putStrLn >> main
Run Code Online (Sandbox Code Playgroud)
因为main永远不会终止,它实际上从未返回一个IO值,但该程序不休读取和回送线就好了-所以上面的简单的解释不能做得比较工作.我错过了一些简单的东西,还是有更完整的解释(或者它只是'编译魔术'?
Tik*_*vis 15
在这种情况下,main是一个值类型的IO (),而不是一个函数.您可以将其视为一系列IO a值:
main = getLine >>= putStrLn >> main
Run Code Online (Sandbox Code Playgroud)
这使它成为一个递归值,与无限列表不同:
foo = 1 : 2 : foo
Run Code Online (Sandbox Code Playgroud)
我们可以返回这样的值而无需评估整个事物.事实上,这是一个相当普遍的习语.
foo 如果你试图使用整个东西,它将永远循环.但这也是如此main:除非你使用一些外部方法来打破它,它永远不会停止循环!但是你可以开始从中获取元素foo,或者在main不评估所有元素的情况下执行部分元素.
值main表示是一个无限的程序:
main = do
line <- getLine
putStrLn line
line <- getLine
putStrLn line
line <- getLine
putStrLn line
line <- getLine
putStrLn line
line <- getLine
putStrLn line
line <- getLine
putStrLn line
...
Run Code Online (Sandbox Code Playgroud)
但它在内存中表示为引用自身的递归结构.该表示是有限的,除非有人试图展开整个程序以获得整个程序的非递归表示 - 这将永远不会完成.
但就像你可以弄清楚如何开始执行我上面写的无限程序而不等我告诉你"全部"它一样,Haskell的运行时系统也可以想出如何在main不预先展开递归的情况下执行.
Haskell的惰性求值实际上与运行时系统执行mainIO程序交错,因此即使对于返回IO递归调用函数的动作的函数也是如此,例如:
main = foo 1
foo :: Integer -> IO ()
foo x = do
print x
foo (x + 1)
Run Code Online (Sandbox Code Playgroud)
这里foo 1不是递归值(它包含foo 2,不是foo 1),但它仍然是一个无限的程序.然而,这很好用,因为所表示的程序foo 1只是按需生成; 它可以随着运行时系统的执行而产生main.
默认情况下,Haskell的懒惰意味着在需要之前不会对任何内容进行评估,然后只需"足够"就可以通过当前块.最终,"直到它需要"的所有"需求"的来源都来自运行时系统,需要知道main程序的下一步是什么,以便它可以执行它.但它只有永远的下一步骤; 在此之后,程序的其余部分可以保持不被评估,直到下一步完全执行完毕.所以infininte程序可以执行并做有用的工作,只要它总是只有一定量的工作来产生"再多一步".