返回 IO 操作的后果是什么?

What are the consequences of returning an IO action?

在 Haskell 中,类型构造函数 IO 是一个配备了 return 语句的 monad,可以将任何表达式提升到它的 IO 版本。

没有什么能阻止我们将已经是 IO 的动作提升到它的 IO 版本 - 给我们一种形式 IO (IO a).

所以我可以编写如下程序:

main = return . print $ "Hello world"

执行时什么都不做。

我的问题是,当这个 main 被执行时,幕后发生了什么?

是否存在return IO 操作有意义的情况?

IO 通常近似于 State RealWorld,即在 "real world" 上运行的 State monad。 return for State 产生一个不改变包含状态的动作。因此,通过类比,return 将任何内容发送到 IO 也不会做任何事情。不仅当你 return 一些 IO a,而且任何 a.

返回一个 IO 动作可用于在一个地方建立计算(沿途将一些值捕获到闭包中)并在其他地方执行它。很像在 C 中传递回调。

实际上,要构建 IO 计算并将其传递到其他地方,您可以在 do 块中使用 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 的附加级别出现的地方。当然,这个例子是合成的,因为你可以直接在 main.

中做 filename <- getLine

在幕后,运行时有效地丢弃了 IO 操作 main 结果 ,这就是为什么它通常被定义为 IO ().这意味着如果 main 实际上有像 IO (IO Int) 这样的类型,则没有真正的问题。 IO动作被执行,结果(另一个IO动作)被丢弃,未执行。

在你的程序中越深入,你更有可能触发类型错误。例如,如果您的意思是 fmap doSomething getLinefmap doSomething (return . getLine) 将不会进行类型检查。