似乎很奇怪,但运行IO操作实际上超出了普通的Haskell语言范畴!
1 Haskell内置库提供了"基本"IO操作,如
getLine :: IO String
,返回IO操作的函数,如
putStrLn :: String -> IO ()
,以及通过其他IO操作构建IO操作的方法(主要是通过提供
Monad
接口来完成的,因此任何适用于任何monad的代码(如
Control.Monad
中的所有内容)都是处理
IO
的方式)。所有这些都是纯粹和惰性的,就像非IO的Haskell代码一样。对于IO而言,“执行IO操作不会产生效果”是在说IO操作并不实际执行;它只是从其他IO操作构建新的IO动作。
"apple" ++ "banana"
这样类型为
String
的值可以由未计算的thunk表示;当它被计算为
"applebanana"
时,它仍然代表完全相同的值,系统只是将其记录为存储在内存中的数据,而不是指向某个可以运行它的代码的指针
1. 同样,
putStrLn "apple" >> putStrLn "banana"
这样类型为
IO ()
的值也可以由未计算的thunk表示,当它被计算时,这只意味着系统现在用数据结构代替代码指针来运行(纯、惰性)函数
>>
。但是我们只谈到了IO操作在内存中的表示,还没有讨论实际运行它们以产生某些副作用的问题。事实上,Haskell没有任何语言特性涉及如何执行IO操作。运行时系统“只知道”如何从
Main
模块执行
main
IO操作
3。Haskell语言无法说明如何或是否执行;这都由提供Haskell的系统(GHC或另一个Haskell系统)处理。Haskell语言唯一提供的选项就是将
main
定义为一个Haskell动作;将IO操作作为
main
定义的一部分进行合并后,这些IO操作将得到运行。
1 我们在这里讨论时将假装像unsafePerformIO
这样的东西不存在。正如其名称所示,它是故意违反正常规则的。它也不旨在将“执行IO操作”作为Haskell语言的正常部分引入,只用于在呈现“正常Haskell”接口的某些内容的内部使用。
2 通常情况下,这种情况仅部分发生:只有基本类型(如Int
)是“全部或无”的评估。大多数可以部分地评估为包含更深层次的惰性求值结构的数据结构(可能会稍后被评估)。
3 或者GHCi“知道”如何执行您在其提示符下输入的IO操作。