《Real World Haskell》笔记(7):I/O

经典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

关于IO,

  • <-绑定一个I/O动作的结果到一个名字,即从I/O动作中抽出结果,并且保存到变量中
  • putStrLn :: String -> IO (),接受一个 String 参数,映射到一个返回值为()的IO动作
  • getLine :: IO String, 保存一个返回值为String的I/O动作,即当IO动作运行后得到String
  • IO something 类型指返回值为something类型的IO动作
  • I/O动作可以粘合在一起来形成更大的I/O动作
  • I/O动作可以被创建,赋值和传递到任何地方,但是它们只能在另一个I/O动作里面被执行
  • main的机制
    main函数是类型为IO ()的I/O动作,main函数是Haskell程序开始执行的地方,程序中所有I/O动作都是由其顶部开始驱动。 main函数给程序中副作用提供隔离:在I/O动作中运行I/O,并且在那调用纯(非I/O)函数,即I/O动作运行I/O并且调用纯代码

关于do代码块,

  • do 是用来定义一连串动作的方法,do 代码块的值是最后一个动作执行的结果
  • do 代码块中,用 <- 去得到I/O动作的结果,用 let 得到纯代码的结果, let 声明后不要放上 in
  • do 代码块中的每一个声明,除了 let ,都要产生一个I/O操作,这个操作在将来被执行
Pure vs Impure
Pure Impure
输入相同时总是产生相同结果 相同的参数可能产生不同的结果
不会有副作用 可能有副作用
不修改状态 可能修改程序、系统或者世界的全局状态
文件和句柄(Handle)
--file toupper-imp.hs
import System.IO
import Data.Char (toUpper)
main::IO ()
main=do
    inh<-openFile "input.txt" ReadMode--创建输入文件句柄
    outh<-openFile "output.txt" WriteMode--创建输出文件句柄
    mainloop inh outh
    hClose inh
    hClose outh--关闭文件句柄
 
mainloop::Handle->Handle->IO ()
mainloop inh outh=
    do ineof<-hIsEOF inh--检查是否在输入文件的结尾(EOF)
       if ineof
       then return ()
       else do inp<-hGetLine inh  
               hPutStrLn outh (map toUpper inp)
               mainloop inh outh
  • return是和<-相反,return 接受一个纯的值,把它包装进IO动作
    例如,return 7会创建一个返回值为7的IO动作,即保存一个IO Int类型的IO动作,在执行该IO动作时,将会产生结果 7
  • IOMode
IOMode 可读 可写 开始位置 备注
ReadMode 文件开头 文件必须存在
WriteMode 文件开头 如果存在,文件内容会被完全清空
ReadWriteMode 文件开头 如果不存在会新建文件,如果存在不会损害原来的数据
AppendMode 文件结尾 如果不存在会新建文件,如果存在不会损害原来的数据
  • 必须使用hClose函数手动关闭文件句柄 ,程序会因为资源耗尽而崩溃
Seek and Tell
  • hTell :: Handle -> IO Integer,hTell 函数用于显示文件中读写的当前位置
    例如,当文件刚新建的时候,位置为0;在写入5个字节之后,位置为5
  • hSeek :: Handle -> SeekMode -> Integer -> IO (),hSeek函数用于改变文件读写位置
  • hIsSeekable :: Handle -> IO Bool 查看给定的句柄是不是可定位
    句柄通常对应文件,但若句柄对应网络连接、终端等,则不可定位

SeekMode的三个可选项:

  • AbsoluteSeek 表示这个位置是在文件中的精确位置,和 hTell 所给的是同样的信息
  • RelativeSeek 表示从当前位置开始寻找,正数要求在文件中前进,负数要求后退
  • SeekFromEnd 会寻找文件结尾之前特定数目的字节,hSeek handle SeekFromEnd 0 定位至文件结尾
System.IO 中预定义句柄

IO非句柄函数就是IO句柄函数的标准快捷方式,如getLine = hGetLine stdin

  • 标准输入 stdin 对应键盘
  • 标准输出 stdout 对应显示器
  • 标准错误 stderr 标准错误会输出到显示器
删除和重命名文件

System.Directory中,

  • removeFile :: FilePath -> IO ()
    该函数接受一个文件路径参数,然后删除对应的文件。
  • renameFile :: FilePath -> FilePath -> IO ()
    该函数接受旧和新两个文件路径参数;如果新文件名在另外一个目录中,可以把它看作移动文件;调用函数之前旧文件必须存在; 如果新文件名已存在,它在重命名前会被删除掉。
临时文件

临时文件可以用来存储大量需要计算的数据或其他程序要使用的数据。

  • getTemporaryDirectory :: IO FilePath
    该函数可用于寻找指定机器上存放临时文件最好的地方。
  • openTempFile :: FilePath -> String -> IO (FilePath, Handle)
    该函数接受两个参数:创建文件所在的目录(可用一个“.”表示当前目录)、一个命名文件的模板字符串(模板用做文件名的基础并随机添加字符以保证文件名是唯一的);IO (FilePath, Handle)FilePath是创建的文件名,Handle是以 ReadWriteMode 打开所创建文件的句柄 (处理完这个文件,需要 调用hClose关闭句柄 、removeFile 删除文件)。

下面是一个函数式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 总是在结束一行的时候在结尾处写上一个 \n
    pos<-hTell temph
    putStrLn $ "After writing, my new position is "++show pos
--Windows使用两个字节序列 \r\n 作为行结束标记 pos==24
    hSeek temph AbsoluteSeek 0
    putStrLn "The file content is:"
    c<-hGetContents temph
    putStrLn c
--数据使用 hPutStrLn写 c结尾处有一个换行符 putStrLn添加第二个换行符 结果会多显示一条空行
    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 StringwriteFile :: FilePath -> String -> IO ()
    这两个函数是把文件当做字符串处理的快捷方式,它们在内部使用hGetContents,并处理包括打开文件、读取文件、写入文件和关闭文件等细节。
  • interact :: (String -> String) -> IO ()
    例如interact过滤器 interact (unlines . filter (elem 'a') . lines) 输出所有包含字符“a”的行
The IO Monad

在Haskell中,纯函数(Pure Function)指不会被外部所影响的纯粹计算过程,动作(Action)可以理解为在被调用时执行相应IO操作、造成磁盘或网络副作用的过程。

--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的操作在其他地方被求值的时候才会执行
--实际上,因为Haskell惰性求值,操作直到执行时才会被生成
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 把每个数字转换成一个 String , str2action 把每个 String 转换成一个操作
-- mapM_ 把这些单独的操作组合成一个更大的操作,然后打印出这些行
-- map 是返回一个列表的纯函数,它不能执行操作
串联化(Sequencing)

do实际上是把操作连接在一起的快捷记号,可用于代替do的运算符有,

  • (>>) :: (Monad m) => m a -> m b -> m b
    串联两个操作,并按顺序运行;丢弃第一个操作的结果,保留第二个操作的结果。
  • (>>=) :: (Monad m) => m a -> (a -> m b) -> m b
    运行一个操作,然后把结果传递给一个返回操作的函数;运行第二个操作后,整个表达式的结果就是第二个操作的结果。
{-
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里面的数据。 在I/O时, return用来拿到纯数据并把它带入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的结果都必须在一个IO Monad里面,所以对于由纯计算产生的 Bool ,需将其传给 return , 让return把它放进IO Monad
  • 因为return是 do 代码块的最后的IO操作,所以它变成 isGreen 的返回值,而不是因为使用 return 函数。
--return 测试
returnTest :: IO ()
returnTest =
-- <-把东西从Monad里面拿出来,是 return 的反作用
    do one <- return 1
       let two = 2
       putStrLn $ show (one + two)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容