經(jīng)典I/O
--file callingpure.hs
name2reply::String->String
name2reply name="Hello "++name++"\n"++"Your name contains "++charcount++" characters."
where charcount=show (length name)
main::IO ()
main=do
putStrLn "What's your name?"
inp<-getLine
let out=name2reply inp
putStrLn out
關(guān)于IO陵且,
-
<-
綁定一個I/O動作的結(jié)果到一個名字媒怯,即從I/O動作中抽出結(jié)果,并且保存到變量中 -
putStrLn :: String -> IO ()
赂摆,接受一個String
參數(shù)经柴,映射到一個返回值為()
的IO動作 -
getLine :: IO String
狸窘, 保存一個返回值為String
的I/O動作,即當IO動作運行后得到String
- IO something 類型指返回值為something類型的IO動作
- I/O動作可以粘合在一起來形成更大的I/O動作
- I/O動作可以被創(chuàng)建坯认,賦值和傳遞到任何地方翻擒,但是它們只能在另一個I/O動作里面被執(zhí)行
- main的機制
main函數(shù)是類型為IO ()
的I/O動作,main函數(shù)是Haskell程序開始執(zhí)行的地方牛哺,程序中所有I/O動作都是由其頂部開始驅(qū)動陋气。 main函數(shù)給程序中副作用提供隔離:在I/O動作中運行I/O,并且在那調(diào)用純(非I/O)函數(shù)引润,即I/O動作運行I/O并且調(diào)用純代碼
關(guān)于do代碼塊巩趁,
- do 是用來定義一連串動作的方法,do 代碼塊的值是最后一個動作執(zhí)行的結(jié)果
- do 代碼塊中淳附,用 <- 去得到I/O動作的結(jié)果晶渠,用 let 得到純代碼的結(jié)果凰荚, let 聲明后不要放上 in
- do 代碼塊中的每一個聲明,除了 let 褒脯,都要產(chǎn)生一個I/O操作,這個操作在將來被執(zhí)行
Pure vs Impure
Pure | Impure |
---|---|
輸入相同時總是產(chǎn)生相同結(jié)果 | 相同的參數(shù)可能產(chǎn)生不同的結(jié)果 |
不會有副作用 | 可能有副作用 |
不修改狀態(tài) | 可能修改程序缆毁、系統(tǒng)或者世界的全局狀態(tài) |
文件和句柄(Handle)
--file toupper-imp.hs
import System.IO
import Data.Char (toUpper)
main::IO ()
main=do
inh<-openFile "input.txt" ReadMode--創(chuàng)建輸入文件句柄
outh<-openFile "output.txt" WriteMode--創(chuàng)建輸出文件句柄
mainloop inh outh
hClose inh
hClose outh--關(guān)閉文件句柄
mainloop::Handle->Handle->IO ()
mainloop inh outh=
do ineof<-hIsEOF inh--檢查是否在輸入文件的結(jié)尾(EOF)
if ineof
then return ()
else do inp<-hGetLine inh
hPutStrLn outh (map toUpper inp)
mainloop inh outh
-
return
是和<-
相反番川,return
接受一個純的值,把它包裝進IO動作
例如脊框,return 7
會創(chuàng)建一個返回值為7的IO動作颁督,即保存一個IO Int
類型的IO動作,在執(zhí)行該IO動作時浇雹,將會產(chǎn)生結(jié)果 7 - IOMode
IOMode | 可讀 | 可寫 | 開始位置 | 備注 |
---|---|---|---|---|
ReadMode | 是 | 否 | 文件開頭 | 文件必須存在 |
WriteMode | 否 | 是 | 文件開頭 | 如果存在沉御,文件內(nèi)容會被完全清空 |
ReadWriteMode | 是 | 是 | 文件開頭 | 如果不存在會新建文件,如果存在不會損害原來的數(shù)據(jù) |
AppendMode | 否 | 是 | 文件結(jié)尾 | 如果不存在會新建文件昭灵,如果存在不會損害原來的數(shù)據(jù) |
- 必須使用
hClose
函數(shù)手動關(guān)閉文件句柄 吠裆,程序會因為資源耗盡而崩潰
Seek and Tell
-
hTell :: Handle -> IO Integer
,hTell 函數(shù)用于顯示文件中讀寫的當前位置
例如烂完,當文件剛新建的時候试疙,位置為0;在寫入5個字節(jié)之后抠蚣,位置為5 -
hSeek :: Handle -> SeekMode -> Integer -> IO ()
祝旷,hSeek函數(shù)用于改變文件讀寫位置 -
hIsSeekable :: Handle -> IO Bool
查看給定的句柄是不是可定位
句柄通常對應文件嘶窄,但若句柄對應網(wǎng)絡(luò)連接、終端等柄冲,則不可定位
SeekMode
的三個可選項:
- AbsoluteSeek 表示這個位置是在文件中的精確位置,和 hTell 所給的是同樣的信息
- RelativeSeek 表示從當前位置開始尋找羊初,正數(shù)要求在文件中前進,負數(shù)要求后退
- SeekFromEnd 會尋找文件結(jié)尾之前特定數(shù)目的字節(jié)长赞,
hSeek handle SeekFromEnd 0
定位至文件結(jié)尾
System.IO 中預定義句柄
IO非句柄函數(shù)就是IO句柄函數(shù)的標準快捷方式,如getLine = hGetLine stdin
- 標準輸入 stdin 對應鍵盤
- 標準輸出 stdout 對應顯示器
- 標準錯誤 stderr 標準錯誤會輸出到顯示器
刪除和重命名文件
在System.Directory
中得哆,
-
removeFile :: FilePath -> IO ()
該函數(shù)接受一個文件路徑參數(shù)脯颜,然后刪除對應的文件。 -
renameFile :: FilePath -> FilePath -> IO ()
該函數(shù)接受舊和新兩個文件路徑參數(shù)贩据;如果新文件名在另外一個目錄中栋操,可以把它看作移動文件闸餐;調(diào)用函數(shù)之前舊文件必須存在; 如果新文件名已存在矾芙,它在重命名前會被刪除掉舍沙。
臨時文件
臨時文件可以用來存儲大量需要計算的數(shù)據(jù)或其他程序要使用的數(shù)據(jù)。
-
getTemporaryDirectory :: IO FilePath
該函數(shù)可用于尋找指定機器上存放臨時文件最好的地方剔宪。 -
openTempFile :: FilePath -> String -> IO (FilePath, Handle)
該函數(shù)接受兩個參數(shù):創(chuàng)建文件所在的目錄(可用一個“.”表示當前目錄)拂铡、一個命名文件的模板字符串(模板用做文件名的基礎(chǔ)并隨機添加字符以保證文件名是唯一的);IO (FilePath, Handle)
葱绒,FilePath
是創(chuàng)建的文件名感帅,Handle
是以 ReadWriteMode 打開所創(chuàng)建文件的句柄 (處理完這個文件,需要 調(diào)用hClose關(guān)閉句柄 地淀、removeFile 刪除文件)失球。
下面是一個函數(shù)式IO的例子,
--file tempfile.hs
import System.IO
import System.Directory(getTemporaryDirectory,removeFile)
import System.IO.Error(catchIOError)
import Control.Exception(finally)
main::IO ()
main=withTempFile "mytemp.txt" myAction
myAction::FilePath->Handle->IO ()
myAction tempname temph=do
putStrLn "Welcome to tempfile.hs"
putStrLn $ "I have a temporary file at "++tempname
pos<-hTell temph
putStrLn $ "My initial position is "++show pos
let tempdata=show [1..10]
putStrLn $ "Writing one line containing "++show (length tempdata)++" bytes: "++tempdata
hPutStrLn temph tempdata
--hPutStrLn 總是在結(jié)束一行的時候在結(jié)尾處寫上一個 \n
pos<-hTell temph
putStrLn $ "After writing, my new position is "++show pos
--Windows使用兩個字節(jié)序列 \r\n 作為行結(jié)束標記 pos==24
hSeek temph AbsoluteSeek 0
putStrLn "The file content is:"
c<-hGetContents temph
putStrLn c
--數(shù)據(jù)使用 hPutStrLn寫 c結(jié)尾處有一個換行符 putStrLn添加第二個換行符 結(jié)果會多顯示一條空行
putStrLn $ "Which could be expressed as this Haskell literal:"
print c
withTempFile::String->(FilePath->Handle->IO a)->IO a
withTempFile pattern func=do
tempdir<-catchIOError (getTemporaryDirectory) (\_->return ".")
(tempfile,temph)<-openTempFile tempdir pattern
finally (func tempfile temph)
(do hClose temph
removeFile tempfile)
惰性IO處理方法
hGetContents :: Handle -> IO String
-
readFile :: FilePath -> IO String
帮毁、writeFile :: FilePath -> String -> IO ()
這兩個函數(shù)是把文件當做字符串處理的快捷方式实苞,它們在內(nèi)部使用hGetContents
,并處理包括打開文件作箍、讀取文件硬梁、寫入文件和關(guān)閉文件等細節(jié)。 -
interact :: (String -> String) -> IO ()
例如interact過濾器interact (unlines . filter (elem 'a') . lines)
輸出所有包含字符“a”的行
The IO Monad
在Haskell中胞得,純函數(shù)(Pure Function)指不會被外部所影響的純粹計算過程荧止,動作(Action)可以理解為在被調(diào)用時執(zhí)行相應IO操作、造成磁盤或網(wǎng)絡(luò)副作用的過程阶剑。
--file actions.hs
str2action :: String -> IO ()
str2action input = putStrLn ("Data: " ++ input)
list2actions :: [String] -> [IO ()]
list2actions = map str2action
numbers :: [Int]
numbers = [1..10]
strings :: [String]
strings = map show numbers
actions :: [IO ()]
actions = list2actions strings
printitall :: IO ()
printitall = runall actions
-- printall的操作在其他地方被求值的時候才會執(zhí)行
--實際上跃巡,因為Haskell惰性求值,操作直到執(zhí)行時才會被生成
runall :: [IO ()] -> IO ()
runall [] = return ()
runall (firstelem:remainingelems) =
do firstelem
runall remainingelems
main = do str2action "Start of the program"
printitall
str2action "Done!"
簡化寫法如下牧愁,
str2message :: String -> String
str2message input = "Data: " ++ input
str2action :: String -> IO ()
str2action = putStrLn . str2message
numbers :: [Int]
numbers = [1..10]
main = do str2action "Start of the program"
mapM_ (str2action . show) numbers
str2action "Done!"
-- mapM_ 在 numbers . show 每個元素上應用 (str2action . show)
-- number . show 把每個數(shù)字轉(zhuǎn)換成一個 String 素邪, str2action 把每個 String 轉(zhuǎn)換成一個操作
-- mapM_ 把這些單獨的操作組合成一個更大的操作,然后打印出這些行
-- map 是返回一個列表的純函數(shù)猪半,它不能執(zhí)行操作
串聯(lián)化(Sequencing)
do實際上是把操作連接在一起的快捷記號兔朦,可用于代替do的運算符有,
-
(>>) :: (Monad m) => m a -> m b -> m b
串聯(lián)兩個操作磨确,并按順序運行沽甥;丟棄第一個操作的結(jié)果摆舟,保留第二個操作的結(jié)果恨诱。 -
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
運行一個操作照宝,然后把結(jié)果傳遞給一個返回操作的函數(shù);運行第二個操作后龙巨,整個表達式的結(jié)果就是第二個操作的結(jié)果。
{-
main = do
putStrLn "Greetings! What is your name?"
inpStr <- getLine
putStrLn $ "Welcome to Haskell, " ++ inpStr ++ "!"
-}
main =
putStrLn "Greetings! What is your name?" >>
getLine >>=
(\inpStr -> putStrLn $ "Welcome to Haskell, " ++ inpStr ++ "!")
Return
在Haskell中汗茄, return 用來包裝在Monad里面的數(shù)據(jù)洪碳。 在I/O時叼屠, return用來拿到純數(shù)據(jù)并把它帶入IO Monad镜雨。
import Data.Char(toUpper)
isYes :: String -> Bool
isYes inpStr = (toUpper . head $ inpStr) == 'Y'
isGreen :: IO Bool
isGreen =
do putStrLn "Is green your favorite color?"
inpStr <- getLine
return (isYes inpStr)
在上例中荚坞,
- 由于所有依賴I/O的結(jié)果都必須在一個IO Monad里面,所以對于由純計算產(chǎn)生的 Bool 各淀,需將其傳給 return 碎浇, 讓return把它放進IO Monad
- 因為return是 do 代碼塊的最后的IO操作奴璃,所以它變成 isGreen 的返回值旧找,而不是因為使用 return 函數(shù)。
--return 測試
returnTest :: IO ()
returnTest =
-- <-把東西從Monad里面拿出來剖膳,是 return 的反作用
do one <- return 1
let two = 2
putStrLn $ show (one + two)