2012-09-01 35 views
2

在學習Haskell時,我想知道何時會執行IO操作。在幾個地方,我發現這樣的描述:新手:理解主要和IO()

「I/O操作的特殊之處在於,如果它們屬於主要功能,則會執行它們。」

但在下面的例子中,'greet'永遠不會返回,因此不應該打印任何內容。

import Control.Monad 

main = greet 

greet = forever $ putStrLn "Hello World!" 

或者我應該問:「落入主要功能」意味着什麼?

+1

您是否嘗試過運行它?它打印出「Hello world」並且是預期的。 – arrowd

+0

措辭含糊,你不應該掛在上面。重點在於主動排序的IO動作,即。 「主叫」以某種方式被執行。我不明白你爲什麼會認爲不應該打印什麼東西。這是一個從主排序的IO動作,它打印出「Hello World!」直到時間結束。 – Sarah

+5

'greet'確實返回 - 它返回一個IO動作,當它被執行時打印'Hello World!'在一個循環中。 – Lee

回答

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系統起作用是至關重要的。

+0

你的意思是「運行時系統能夠解決'永遠'嗎? – huon

5

在我看來,聲明「I/O操作的特殊之處在於,如果它們屬於主函數,它們就會被執行。」那IO行動是一等公民。也就是說,IO-行爲可能發生在其他數據類型的值可能出現的所有地方,如Int。例如,您可以按如下方式定義一個包含IO操作的列表。

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

名單actionList具有類型[IO()]。也就是說,列表包含與世界交互的動作,例如,在控制檯上打印或從用戶讀入輸入。但是,在定義這個列表時,我們不會執行這些操作,我們只需將它們放入列表中供以後使用。

如果某個IO可能發生在您的程序的某處,則在執行這些操作時會出現問題,並且此處出現main。考慮main的以下定義。

main = do 
    actionList !! 0 
    actionList !! 1 

main功能項目,以在第一和列表的第二組分,並通過在其定義內使用它們「執行」的對應動作。請注意,它不一定必須是執行IO操作的main函數本身。從main函數調用的任何函數都可以執行操作。例如,我們可以定義一個函數,調用actionList中的動作,並讓main按如下方式調用該函數。

main = do 
    caller 
    putStr "!" 

caller = do 
    actionList !! 0 
    actionList !! 1 

要強調的是它並沒有成爲一個簡單的更名像main = caller我將它已經完成從列表中的動作後,打印感嘆號的動作。

2

簡單的IO操作可以通過使用符號組合成更高級的操作。

main = do 
    printStrLn "Hello" 
    printStrLn "World" 

結合IO動作printStrLn "Hello"與IO動作printStrLn "World"。 Main現在是一個IO動作,首先打印一個表示「Hello」的行,然後是一個表示「World」的行。寫離不開表示法(這只是語法糖份)它看起來像這樣:

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

在這裏你可以看到>>功能相結合的兩個動作。

您可以創建一個IO動作,讀取一條線,把它傳遞給一個函數(即不真棒東西吧:))和打印的結果是這樣的:

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

或無結合的結果給一個變量:

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

這ofcourse也可以寫成,結合他們這樣的IO操作和功能:

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

當您運行程序時,執行主IO操作。這是任何IO操作實際執行的唯一時間。 (以及技術上還可以,你程序中執行它們,但它是不是安全的,做的功能稱爲unsafePerformIO。)

你可以在這裏閱讀更多:http://www.haskell.org/haskellwiki/Introduction_to_Haskell_IO/Actions

(此鏈接可能是更好的解釋比我的,但我只發現它後,我已經寫了幾乎所有的東西,它也相當長一些)

+1

我認爲你的意思是'putStrLn',而不是'printStrLn',對吧? –

1
launchAMissile :: IO() 
launchAMissile = do 
    openASilo 
    loadCoordinates 
    launchAMissile 

main = do 
    let launch3missiles = launchAMissile >> launchAMissile >> launchAMissile 
    putStrLn "Not actually launching any missiles" 
1

forever並不像C的while (true)循環。它是一個產生IO值(包含無限重複的動作序列)的函數,由調用者使用。(在這種情況下,調用者是main,這意味着這些操作由運行時系統執行)。