IO 的角色和目的
Haskell 是一種純語言,意味著表示式不會產生副作用。副作用是表示式或函式除了產生值之外的任何其他作用,例如,修改全域性計數器或列印到標準輸出。
在 Haskell 中,使用 IO
對有效計算(特別是可以對現實世界產生影響的計算)進行建模。嚴格來說,IO
是一個型別建構函式,採用型別並生成型別。例如,IO Int
是產生 Int
值的 I / O 計算的型別。IO
型別是抽象的,並且為 IO
提供的介面通過確保執行 IO 的所有內建函式都具有包含在 IO
中的返回型別來確保某些非法值(即,具有非敏感型別的函式)不存在。
當執行 Haskell 程式時,執行由名為 main
的 Haskell 值表示的計算,其型別可以是任何型別 x
的 IO x
。
操作 IO 值
標準庫中有許多函式提供通用程式語言應執行的典型 IO
操作,例如讀取和寫入檔案控制代碼。一般 IO
動作建立並主要與兩個功能組合:
(>>=) :: IO a -> (a -> IO b) -> IO b
這個函式(通常稱為 bind )接受一個 IO
動作和一個返回 IO
動作的函式,併產生 IO
動作,這是將函式應用於第一個 IO
動作產生的值的結果。
return::a -> IO a
此函式接受任何值(即純值)並返回不執行 IO 並生成給定值的 IO 計算。換句話說,它是一個無操作的 I / O 操作。
還有一些常用的常用功能,但所有功能都可以根據上述兩種方式編寫。例如,(>>) :: IO a -> IO b -> IO b
類似於 (>>=)
,但第一個動作的結果被忽略。
一個簡單的程式,使用這些函式來問候使用者:
main::IO ()
main =
putStrLn "What is your name?" >>
getLine >>= \name ->
putStrLn ("Hello " ++ name ++ "!")
該程式還使用 putStrLn::String -> IO ()
和 getLine::IO String
。
注意:上面某些函式的型別實際上比給定的型別更通用(即 >>=
,>>
和 return
)。
IO 語義
Haskell 中的 IO
型別與指令式程式設計語言的語義非常相似。例如,當一個用命令語言寫 s1 ; s2
來表示執行語句 s1
,然後宣告 s2
時,可以寫一個 s1 >> s2
來模擬 Haskell 中的同一個東西。
然而,IO
的語義略微偏離了強制性背景所預期的內容。return
函式不會中斷控制流 - 如果按順序執行另一個 IO
操作,它對程式沒有影響。例如,return () >> putStrLn "boom"
正確地將 boom
列印到標準輸出。
IO
的形式語義可以通過涉及上一節函式的簡單均等來給出:
return x >>= f ≡ f x, ∀ f x
y >>= return ≡ return y, ∀ y
(m >>= f) >>= g ≡ m >>= (\x -> (f x >>= g)), ∀ m f g
這些通常分別稱為左側身份,右側身份和組成。它們可以在函式方面更自然地陳述
(>=>) :: (a -> IO b) -> (b -> IO c) -> a -> IO c
(f >=> g) x = (f x) >>= g
如下:
return >=> f ≡ f, ∀ f
f >=> return ≡ f, ∀ f
(f >=> g) >=> h ≡ f >=> (g >=> h), ∀ f g h
懶惰的 IO
執行 I / O 計算的函式通常是嚴格的,這意味著必須在下一個操作開始之前完成一系列操作中的所有先前操作。通常這是有用的和預期的行為 - putStrLn "X" >> putStrLn "Y"
應該列印 XY
。但是,某些庫函式會懶惰地執行 I / O,這意味著生成值所需的 I / O 操作僅在實際使用該值時執行。這些功能的例子是 getContents
和 readFile
。惰性 I / O 可以大大降低 Haskell 程式的效能,因此在使用庫函式時,應注意注意哪些函式是惰性的。
IO 和 do
表示法
Haskell 提供了一種將不同 IO 值組合成更大 IO 值的更簡單方法。這種特殊的語法被稱為 do
符號*,它只是用於 >>=
,>>
和 return
函式的語法糖。
上一節中的程式可以使用 do
表示法以兩種不同的方式編寫,第一種是佈局敏感,第二種是佈局不敏感:
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello " ++ name ++ "!")
main = do {
putStrLn "What is your name?" ;
name <- getLine ;
putStrLn ("Hello " ++ name ++ "!")
}
這三個程式完全相同。
*請注意,do
表示法也適用於更廣泛的型別建構函式 monad 。