返回 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 getLine
,fmap doSomething (return . getLine)
将不会进行类型检查。
在 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 getLine
,fmap doSomething (return . getLine)
将不会进行类型检查。