在 Haskell 中,类型构造函数IO
是一个带有语句的 monad,return
它可以将任何表达式提升到它的IO
版本。
没有什么能阻止我们将已经是IO
动作的东西提升到它的IO
版本——给我们一种形式IO (IO a)
。
所以我可以例如编写以下程序:
main = return . print $ "Hello world"
在执行时什么都不做。
我的问题是,当这个 main 执行时,引擎盖下会发生什么?
return
是否存在对IO 操作有意义的情况?
在后台,运行时有效地丢弃了action的结果,这就是为什么它通常被定义为. 这意味着如果实际上有一个类似的类型,那么就没有真正的问题。该动作被执行,结果(另一个动作)被丢弃,未执行。IO
main
IO ()
main
IO (IO Int)
IO
IO
在程序的更深处,您更有可能只是触发类型错误。例如,fmap doSomething (return . getLine)
如果您的意思是fmap doSomething getLine
.
IO
通常近似于State RealWorld
,即State
在“真实世界”上运行的 monad。return
forState
产生一个不改变包含状态的动作。因此,通过类比,return
ing 任何东西IO
都不会做任何事情。不仅当你return
一些IO a
,而且任何a
。
返回一个IO
动作可用于在一个地方建立一个计算(沿途将一些值捕获到一个闭包中)并在其他地方执行它。就像在 C 中传递回调一样。
实际上,要建立IO
计算并将其传递到其他地方,您可以使用let
do-block 的内部:
main :: IO ()
main = do
let act = readFile "somefile" -- action is not performed yet
foo act -- pass the action as a parameter
foo :: IO () -> IO ()
foo act = do
.. do something
result <- act -- actually perform the action
..
return
在这种情况下,您不需要IO a
值。
但是,如果构建该计算本身的过程需要您执行IO
操作,那么您就需要这样的东西。在我们的示例中,让我们询问要打开文件名的用户:
main :: IO ()
main = do
act <- do
filename <- getLine
return (readFile filename)
foo act
在这里,我们需要一个filename
来创建我们的动作,但getLine
也是IO
。这就是额外级别的IO
出现。当然,这个例子是合成的,你可以filename <- getLine
在main
.