在Haskell中,类型构造函数IO
是一个配有return
语句的monad,它将任何表达式提升到IO
版本。
没有什么可以阻止我们将已经IO
的{{1}}动作提升到IO
版本 - 为我们提供IO (IO a)
形式的类型。
所以我可以编写以下程序:
main = return . print $ "Hello world"
执行时什么都没做。
我的问题是,执行此主程序时会发生什么?
是否存在return
IO操作有意义的情况?
答案 0 :(得分:3)
在幕后,运行时有效地丢弃IO
动作main
的结果,这就是为什么它通常被定义为IO ()
。这意味着如果main
实际上具有类似IO (IO Int)
的类型,则没有真正的问题。执行IO
操作,结果(另一个IO
操作)被丢弃,未执行。
在您的程序中,您更有可能只是触发类型错误。例如,如果您的意思是fmap doSomething (return . getLine)
,则fmap doSomething getLine
不会进行类型检查。
答案 1 :(得分:2)
IO
通常近似于State RealWorld
,即State
monad,可在"现实世界"上运行。 return
State
会产生一个不会改变包含状态的动作。因此,通过类比,return
任何事物IO
都不会做任何事情。不仅仅是return
某些IO a
,而是a
。
返回IO
动作可用于在一个地方建立计算(沿途捕获一些值到闭包中)并在其他地方执行。就像在C中传递回调一样。
实际上,要构建IO
计算并将其传递到其他位置,您可以在do-block中使用let
:
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
{。}}。