《Real World Haskell》筆記(7):I/O

經(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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吱晒,一起剝皮案震驚了整個濱河市仑濒,隨后出現(xiàn)的幾起案子墩瞳,更是在濱河造成了極大的恐慌喉酌,老刑警劉巖泵喘,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件相速,死亡現(xiàn)場離奇詭異鲜锚,居然都是意外死亡烹棉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伏社,“玉大人摘昌,你說我怎么就攤上這事『比荩” “怎么了锦秒?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵惭笑,是天一觀的道長生真。 經(jīng)常有香客問我柱蟀,道長,這世上最難降的妖魔是什么派歌? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮斤斧,結(jié)果婚禮上撬讽,老公的妹妹穿的比我還像新娘悬垃。我一直安慰自己尝蠕,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布廊佩。 她就那樣靜靜地躺著标锄,像睡著了一般茁计。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鬼譬,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天拧簸,我揣著相機與錄音男窟,去河邊找鬼。 笑死牺六,一個胖子當著我的面吹牛汗捡,可吹牛的內(nèi)容都是我干的扇住。 我是一名探鬼主播艘蹋,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宅荤!你這毒婦竟也來了冯键?” 一聲冷哼從身側(cè)響起庸汗,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤夫晌,失蹤者是張志新(化名)和其女友劉穎晓淀,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體燥爷,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了港华。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片立宜。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡橙数,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出崖技,到底是詐尸還是另有隱情迎献,我是刑警寧澤腻贰,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布银受,位于F島的核電站宾巍,受9級特大地震影響渔伯,放射性物質(zhì)發(fā)生泄漏锣吼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一古徒、第九天 我趴在偏房一處隱蔽的房頂上張望读恃。 院中可真熱鬧代态,春花似錦蹦疑、人聲如沸歉摧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膛檀。三九已至,卻和暖如春泳炉,著一層夾襖步出監(jiān)牢的瞬間嚎杨,已是汗流浹背枫浙。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工箩帚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留紧帕,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像站绪,于是被迫代替她去往敵國和親丽柿。 傳聞我的和親對象是個殘疾皇子掂僵,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容