在学习Haskell时,我想知道何时会执行IO操作。我在几个地方找到了这样的描述:
“I / O操作的特殊之处在于,如果它们属于主要功能,则执行它们。”
但在下面的示例中,'greet'永远不会返回,因此不应打印任何内容。
import Control.Monad
main = greet
greet = forever $ putStrLn "Hello World!"
或许我应该问:“落入主要功能”是什么意思?
答案 0 :(得分:10)
首先,main
不是函数。它确实只是一个常规值,其类型为IO ()
。该类型可以读作:执行时执行类型为()
的值的操作。
现在,运行时系统扮演执行您所描述的操作的解释器的角色。我们以你的程序为例:
main = forever (putStrLn "Hello world!")
请注意,我已执行转换。那个是有效的,因为Haskell是一种引用透明的语言。运行时系统解析forever
并找到:
main = putStrLn "Hello world!" >> MORE1
它还不知道MORE1
是什么,但它现在知道它有一个带有一个已知动作的组合,它被执行。执行后,它会解析第二个操作MORE1
并找到:
MORE1 = putStrLn "Hello world!" >> MORE2
它再次执行该合成中的第一个动作,然后继续解析。
当然这是一个高级别的描述。实际代码不是解释器。但这是一种描述Haskell程序如何执行的方法。让我们再看一个例子:
main = forever (getLine >>= putStrLn)
RTS看到了这一点:
main = forever MORE1
<< resolving forever >>
MORE1 = getLine >>= MORE2
<< executing getLine >>
MORE2 result = putStrLn result >> MORE1
<< executing putStrLn result (where 'result' is the line read)
and starting over >>
了解这一点时,您了解IO String
不是“带有副作用的字符串”,而是描述将产生字符串的操作。您也理解为什么懒惰对于Haskell的I / O系统起作用至关重要。
答案 1 :(得分:5)
在我看来,声明“I / O动作的特殊之处在于,如果它们属于主要功能,它们就会被执行。”是IO
行动是一等公民。也就是说,IO
- 可以在其他数据类型(如Int
)的值发生的所有位置发生操作。例如,您可以定义包含IO
操作的列表,如下所示。
actionList = [putStr "Hello", putStr "World"]
列表actionList
的类型为[IO ()]
。也就是说,列表包含与世界交互的动作,例如,在控制台上打印或从用户读取输入。但是,在定义此列表时,我们不执行操作,只需将它们放在列表中供以后使用。
如果程序中的某个位置可能出现IO
,则问题会在执行这些操作时产生,main
会在此处发挥作用。请考虑main
的以下定义。
main = do
actionList !! 0
actionList !! 1
此main
函数投影到列表的第一个和第二个组件,并通过在其定义中使用它们来“执行”相应的操作。请注意,它不一定必须是执行main
操作的IO
函数本身。从main
函数调用的任何函数也可以执行操作。例如,我们可以定义一个函数来调用actionList
中的操作,让main
调用此函数,如下所示。
main = do
caller
putStr "!"
caller = do
actionList !! 0
actionList !! 1
要强调它不必像main = caller
中那样进行简单的重命名,我添加了一个动作,在从列表中执行动作后打印感叹号。
答案 2 :(得分:2)
使用do notation可以将简单的IO操作组合成更高级的操作。
main = do
printStrLn "Hello"
printStrLn "World"
将IO操作printStrLn "Hello"
与IO操作printStrLn "World"
结合起来。 Main现在是一个IO动作,首先打印一行“Hello”,然后是一行“World”。写的没有do-notation(这只是语法上的suger),它看起来像这样:
main = printStrLn "Hello" >> printStrLn "World"
在这里,您可以看到>>
功能组合这两个动作。
你可以创建一个读取一行的IO动作,将它传递给一个函数(对它做了很棒的东西:))并打印结果如下:
main = do
input <- getLine
let result = doAwesomeStuff input
printStrLn result
或不将结果绑定到变量:
main = do
input <- getLine
printStrLn (doAwesomeStuff input)
这也可以写成IO动作和函数,它们将它们组合在一起:
main = getLine >>= (\input -> printStrLn (doAwesomeStuff input))
运行程序时,将执行主IO操作。这是实际执行任何IO操作的唯一时间。 (从技术上讲,你也可以在你的程序中执行它们,但它并不安全。执行此操作的函数称为unsafePerformIO
。)
您可以在此处阅读更多内容:http://www.haskell.org/haskellwiki/Introduction_to_Haskell_IO/Actions
(这个链接可能比我的解释更好,但我只写了几乎所有内容之后才找到它。它也相当长一点)
答案 3 :(得分:1)
launchAMissile :: IO ()
launchAMissile = do
openASilo
loadCoordinates
launchAMissile
main = do
let launch3missiles = launchAMissile >> launchAMissile >> launchAMissile
putStrLn "Not actually launching any missiles"
答案 4 :(得分:1)
forever
不是像C while (true)
这样的循环。它是一个产生IO值(包含无限重复的动作序列)的函数,由调用者使用。 (在这种情况下,调用者是main
,这意味着操作由运行时系统执行。)