2

While learning Haskell I am wondering when an IO action will be performed. In several places I found descriptions like this:

"What’s special about I/O actions is that if they fall into the main function, they are performed."

But in the following example, 'greet' never returns and therefore nothing should be printed.

import Control.Monad

main = greet

greet = forever $ putStrLn "Hello World!"

Or maybe I should ask: what does it mean to "fall into the main function"?

4

5 回答 5

11

First of all, main is not a function. It is indeed just a regular value and its type is IO (). The type can be read as: An action that, when performed, produces a value of type ().

Now the run-time system plays the role of an interpreter that performs the actions that you have described. Let's take your program as example:

main = forever (putStrLn "Hello world!")

Notice that I have performed a transformation. That one is valid, since Haskell is a referentially transparent language. The run-time system resolves the forever and finds this:

main = putStrLn "Hello world!" >> MORE1

It doesn't yet know what MORE1 is, but it now knows that it has a composition with one known action, which is executed. After executing it, it resolves the second action, MORE1 and finds:

MORE1 = putStrLn "Hello world!" >> MORE2

Again it executes the first action in that composition and then keeps on resolving.

Of course this is a high level description. The actual code is not an interpreter. But this is a way to picture how a Haskell program gets executed. Let's take another example:

main = forever (getLine >>= putStrLn)

The RTS sees this:

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 >>

When understanding this you understand how an IO String is not "a string with side effects" but rather the description of an action that would produce a string. You also understand why laziness is crucial for Haskell's I/O system to work.

于 2012-09-01T13:32:28.300 回答
5

In my opinion the point of the statement "What’s special about I/O actions is that if they fall into the main function, they are performed." is that IO actions are first class citizens. That is, IO-actions can occur at all places where values of other data types like Int can occur. For example, you can define a list that contains IO actions as follows.

actionList = [putStr "Hello", putStr "World"]

The list actionList has type [IO ()]. That is, the list contains actions that interact with the world, for example, print on the console or read in input from the user. But, in defining this list we do not execute the actions, we simply put them in a list for later use.

If an IO can occur somewhere in your program, the question arrises when these actions are performed and here main comes into play. Consider the following definition of main.

main = do
  actionList !! 0
  actionList !! 1

This main function projects to the first and the second component of the list and "executes" the corresponding actions by using them within its definition. Note that it does not necessarily have to be the main function itself that executes an IO action. Any function that is called from the main function can execute actions as well. For example, we can define a function that calls the actions from actionList and let main call this function as follows.

main = do
  caller
  putStr "!"

caller = do
  actionList !! 0
  actionList !! 1

To highlight that it does not have to be a simple renaming like in main = caller I have added an action that prints an exclamation mark after it has performed the actions from the list.

于 2012-09-01T10:18:19.423 回答
2

Simple IO actions can be combined into more advanced ones by using do notation.

main = do
    printStrLn "Hello"
    printStrLn "World"

combines the IO action printStrLn "Hello" with the IO action printStrLn "World". Main is now an IO action first printing a line that says "Hello" and then a line that says "World". Written without do-notation (which is just syntactic suger) it looks like this:

main = printStrLn "Hello" >> printStrLn "World"

Here you can see the >> function combining the two actions.

You can create an IO action that reads a line, passes it to a function(that does awesome stuff to it :)) and the prints the result like this:

main = do
    input <- getLine
    let result = doAwesomeStuff input
    printStrLn result

or without binding the result to a variable:

main = do
    input <- getLine
    printStrLn (doAwesomeStuff input)

This can ofcourse also be written as IO actions and functions that combine them like this:

main = getLine >>= (\input -> printStrLn (doAwesomeStuff input))

When you run the program the main IO action is executed. This is the only time any IO actions are actually executed. (well technically you can also execute them within you program, but it is not safe. The function that does the is called unsafePerformIO.)

You can read more here: http://www.haskell.org/haskellwiki/Introduction_to_Haskell_IO/Actions

(This link is probably a better explaination than mine, but I only found it after I had written nearly everything. It is also quite a bit longer)

于 2012-09-01T13:32:04.640 回答
1
launchAMissile :: IO ()
launchAMissile = do
    openASilo
    loadCoordinates
    launchAMissile

main = do
    let launch3missiles = launchAMissile >> launchAMissile >> launchAMissile
    putStrLn "Not actually launching any missiles"
于 2012-09-02T05:46:27.870 回答
1

forever isn't a loop like C's while (true). It is a function that produces an IO value (which contains an infinitely repeated sequence of actions), which is consumed by the caller. (In this case, the caller is main, which means that the actions get executed by the runtime system).

于 2014-01-04T18:41:19.717 回答