在學習Haskell時,我想知道何時會執行IO操作。在幾個地方,我發現這樣的描述:新手:理解主要和IO()
「I/O操作的特殊之處在於,如果它們屬於主要功能,則會執行它們。」
但在下面的例子中,'greet'永遠不會返回,因此不應該打印任何內容。
import Control.Monad
main = greet
greet = forever $ putStrLn "Hello World!"
或者我應該問:「落入主要功能」意味着什麼?
在學習Haskell時,我想知道何時會執行IO操作。在幾個地方,我發現這樣的描述:新手:理解主要和IO()
「I/O操作的特殊之處在於,如果它們屬於主要功能,則會執行它們。」
但在下面的例子中,'greet'永遠不會返回,因此不應該打印任何內容。
import Control.Monad
main = greet
greet = forever $ putStrLn "Hello World!"
或者我應該問:「落入主要功能」意味着什麼?
首先,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系統起作用是至關重要的。
你的意思是「運行時系統能夠解決'永遠'嗎? – huon
在我看來,聲明「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
我將它已經完成從列表中的動作後,打印感嘆號的動作。
簡單的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
(此鏈接可能是更好的解釋比我的,但我只發現它後,我已經寫了幾乎所有的東西,它也相當長一些)
我認爲你的意思是'putStrLn',而不是'printStrLn',對吧? –
launchAMissile :: IO()
launchAMissile = do
openASilo
loadCoordinates
launchAMissile
main = do
let launch3missiles = launchAMissile >> launchAMissile >> launchAMissile
putStrLn "Not actually launching any missiles"
forever
並不像C的while (true)
循環。它是一個產生IO值(包含無限重複的動作序列)的函數,由調用者使用。(在這種情況下,調用者是main
,這意味着這些操作由運行時系統執行)。
您是否嘗試過運行它?它打印出「Hello world」並且是預期的。 – arrowd
措辭含糊,你不應該掛在上面。重點在於主動排序的IO動作,即。 「主叫」以某種方式被執行。我不明白你爲什麼會認爲不應該打印什麼東西。這是一個從主排序的IO動作,它打印出「Hello World!」直到時間結束。 – Sarah
'greet'確實返回 - 它返回一個IO動作,當它被執行時打印'Hello World!'在一個循環中。 – Lee