新手:了解主要和IO()

时间:2012-09-01 09:36:27

标签: haskell

在学习Haskell时,我想知道何时会执行IO操作。我在几个地方找到了这样的描述:

“I / O操作的特殊之处在于,如果它们属于主要功能,则执行它们。”

但在下面的示例中,'greet'永远不会返回,因此不应打印任何内容。

import Control.Monad

main = greet

greet = forever $ putStrLn "Hello World!"

或许我应该问:“落入主要功能”是什么意思?

5 个答案:

答案 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,这意味着操作由运行时系统执行。)