Chap 5. 基本输入输出

对于纯函数语言来说,很难用一种清晰自然的方式来集成输入输出操作。对于一个IO库来说,基本上有四种操作:

  • 向屏幕输出字符串
  • 从键盘读取字符串
  • 向文件写数据
  • 从文件读数据

考虑下这两种方向的操作的“函数”类型会是什么, 对于输出需要一个String 类型的参数,并且产生一个 unit () ,没有其他的返回。对于输入,返回一个 String ,没有参数。
这两种操作都不是 Function, 因为读入每一次的返回都是不确定的,不符合函数的定义。而对于输出,如果每次只是返回一个 () , 那么可以用任意一个函数 f _ = () 来替代这个输出函数。(基于函数的引用透明性)这显然不是我们想要的。

处理真实世界

现实世界是恐怖的。---- AlbertLee

我们之所以会在输入输出碰到这样莫名其妙的问题,是因为我们在和“RealWorld” 打交道。如果我们把整个 RealWorld 当作一个 Type ,那么我们可以:

printAString :: RealWorld -> String -> RealWorld
readAString :: RealWorld -> (RealWorld, String)

把现实世界当作函数的一个参数。printAString 的输入是现实世界(当前的状态)和一个String,输出是一个的现实世界。(因为有些东西被改变了,世界不同了)。
但是这个 真实世界 参数实在太讨厌了,很难以管理,一不小心就会搞乱世界。对于胸怀构建社会和谐秩序的程序员来说当然是不可接受的。

Actions

Phil Wadler 老大意识到 Monad是解决上述问题的王道!实际上, Monad被用来表达并发, 异常, IO, 非确定性计算 等等。Monad 可以在 haskell中被定义,而不需要借助于编译器(尽管在编译的时候经常会特殊优化他们)。

前文书讲过,打印和读取键盘一类的东西不算函数,所以换了个名字叫 Action,他们具有特别的类型。 putStrLn 就是一个特别有用的 Action:
putStrLn :: String -> IO ()
这个的意思是说, putStrLn 是一个 Action 带有一个 IO monad。
getLine :: IO String
getLine 是一个 IO Action ,执行时返回一个 String。

我们没有办法去亲自执行一个 Action,需要编译器来搞,一个可以执行的 haskell程序,必须有一个 main Action ,就像 C 中的 main 函数。 main :: IO()
我们通过 do 标记来组合不同的 Action:

main = do
putStrLn "Name:"
name <- getLine
putStrLn ("Hello " ++ name)

do : 顺序组合一系列的 Action
<- : 从一个 Action 中获得输出

IO Library

读文件,写文件,读键盘,写屏幕,用过C的很容易理解,看例子吧

import Data.Time
import System.Locale
import System.Time
import System.IO

fact :: Int -> Int
fact n = if n == 0 then 1 else n * fact(n-1)

main = do
  outfile <- openFile "f.txt" WriteMode
  hSetBuffering stdin LineBuffering
  putStrLn "Your name:"
  name <- getLine
  hPutStr outfile ("hello " ++ name ++ " ")
  now <- getCurrentTime
  hPutStrLn outfile $ formatTime defaultTimeLocale "%Y/%m/%d %H:%M:%S" now
  hPutStrLn outfile ("fact 5 = " ++ (show $ fact 5))
  hClose outfile

在打开文件和关闭文件中间, 我们执行了很多操作,而这有可能会导致错误而无法正常关闭文件,为此,我们可以使用 bracket 函数:

bracket
  (openFile "somefile" ReadMode)
  hClose
  (\h -> hPutChar h c)

这个操作会出错,因为以 ReadMode打开,却执行了 hPutChar,这个时候第二个参数 hClose 将被执行。这种方式让我们不必太担心异常处理的事情。
看例子:

import System.IO
import IO

main :: IO ()
main = do
  bracket
       (openFile "f.txt" ReadMode)
       (\h -> do
          putStrLn "I catch the error, and will close the file"
          hClose h)
       (\h -> hPutStrLn h "hehe, this is wrong")

输出结果:

albert@albert-laptop:~/learn/unix$ runhaskell iowrong.hs
I catch the error, and will close the file
*** Exception: f.txt: hPutStr: illegal operation (handle is not open for writing)

读写文件的例子

书上的例子,比较有趣的是它的

case command of
  'q':_ -> return ()
  'r':filename -> ....
  'w':filename -> ...
  _  -> ....

这一部分的 Pattern Matching。