《Haskell趣學指南》筆記之I/O

系列文章bash


Hello World

  1. 在 helloworld.hs 裏寫入 main = putStrLn "hello, world!
  2. 運行 ghc --make helloworld
  3. 運行 ./helloworld
  4. 獲得輸出 hello, world!

而後來看看函數的類型dom

ghci> :t putStrLn 
putStrLn :: String -> IO () 
ghci> :t putStrLn "hello, world" 
putStrLn "hello, world" :: IO () 
ghci> :k IO
IO :: * -> *
ghci> :k IO()
IO() :: *
複製代碼

IO () 返回的類型爲 (),即空元組,也叫單元。下一節會出現的 IO String 返回的類型爲 String。函數

() 即便一個類型,也是對應類型的值。post

do 語法

do 語法能夠將多個 I/O 操做合成一個。spa

main = do   
    putStrLn "Hello, what' s your name?"   
    name <- getLine &emsp; 
    putStrLn ("Hey " ++ name ++ ", you rock!") 
複製代碼
  • getLine 的類型爲 IO String
  • getLine 是一個產生字符串的 I/O 操做
  • 注意 name <- 並無寫成 name =
  • 只有在 I/O 操做的上下文中才能讀取 I/O 操做的內容,這就是 Haskell 隔離『純代碼』和『不純的代碼』的方式
  • do語法會自動將最後一個操做的值做爲本身的返回值

IO String 與 String 的區別

nameTag = "Hello, my name is " ++ getLine 
複製代碼

++ 左邊的類型是 String,右邊的類型爲 IO String,因此上面的代碼會報錯。必須經過 name <- getLine 取出這個 String,才能繼續。只能在不純的環境中處理不純的數據。否則不純的代碼會像污水那樣污染其他的代碼,保持 I/ O 相關的代碼儘量小,這對咱們的健康有好處。命令行

myLine = getLine

若是代碼寫成了這樣3d

myLine = getLine 
複製代碼

這只是給 getLine 增長了一個別名!從 I/O 操做獲取值的惟一方法是使用 <- 。每次咱們在 GHCi 中按下回車鍵,GHCi 都會對返回值應用 show,再將生成的字符串交給 putStrLn,從而輸出到終端。code

return 是不同的

Haskell 的 return 跟其餘語言不同,return 可以基於一個純的值來構造 I/O 操做。並且 return 不會中斷代碼。因此,在 I/O 上下文中,return "hi" 的類型就是 IO String。那麼將一個純值轉換爲一個什麼都不作的 I/ O 操做又有什麼意義呢?做用之一是 return () 可以構建一個什麼都不作的 I/O 操做。ci

幾個 I/O 函數

  • putStr
  • putStr
  • print (至關於 putStrLn . show)
  • when <condition> $ do IO操做
  • sequence [getLine getLine]<- 取多個 I/O 操做的結果組成一個列表
  • mapM print [1, 2, 3] 等價於 sequence $ map print [1, 2, 3]
  • mapM_ 則是不保留返回值版本的 mapM
  • forever $ do IO操做
  • forM 則是把 mapM 的參數位置對換,某些時候比較方便
  • getContents 從標準輸入裏讀取全部的東西直到遇到一個 end-of-file 字符,並且 getContents 是惰性的
  • interact fn 取一個類型爲 String -> String 的函數 fn 做爲參數,返回這樣一個 I/ O 操做:接受輸入,把一個函數做用在輸入上,而後輸出函數運行結果
  • getArgs 獲取命令行參數
  • getProgName 獲取程序名

讀入文件流是隨着時間連續地進入、離開程序的一組數據片。

  1. 建立文件 test.txt,內容以下字符串

    Hi! How are you?
     I'm Fine. Thank you. And you?
     I'm Fine too.
    複製代碼
  2. 建立文件 capslocker.hs,內容以下

    import Control.Monad 
     import Data.Char 
     main = forever $ do
          l <- getLine
          putStrLn $ map toUpper l 
    複製代碼
  3. 編譯:運行 ghc --make capslocker

  4. 將 test.txt 的內容傳給 capslocker:運行 ./capslocker < test.txt

  5. 而後你就會看到全部字母變成了大寫上面代碼也能用 getContents 簡化

import Data.Char 
main = do
    contents <- getContents
    putStr $ map toUpper contents 
複製代碼

也能夠不傳入 test.txt 文件,直接運行 ./capslocker,而後輸入一行行文本,可是注意,最終你要按 Ctrl+D 來表示內容結束。

stdout 和 stdin

一種理解從終端讀入數據的方式是設想咱們在讀取一個文件。輸出到終端也能夠一樣理解——它就像在寫文件。咱們能夠把這兩個文件叫作 stdout 和 stdin,分別表示標準輸出和標準輸入。

用 openFile 打開文件

  1. 建立 gf.txt,內容以下:

    Hey! Hey! You! You! 
     I don' t like your girlfriend! 
     No way! No way! 
     I think you need a new one! 
    複製代碼
  2. 建立 gf.hs,內容以下:

    import System.IO 
     main = do
         handle <- openFile "gf. txt" ReadMode
         contents <- hGetContents handle
         putStr contents
         hClose handle
     -- 注意 openFile ReadMode hGetContents hClose 這幾個函數,其中的 h 前綴表示它接收 handle
    複製代碼
  3. 編譯並運行如何知道 openFile 的各個參數的意思呢?

λ> :t openFile
openFile :: FilePath -> IOMode -> IO Handle
λ> :info FilePath
type FilePath = String -- Defined in ‘GHC.IO’λ> :info IOMode
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
        -- Defined in ‘GHC.IO.IOMode’λ> :info Handle
data Handle
  = GHC.IO.Handle.Types.FileHandle FilePath...
複製代碼

用 withFile 打開文件

import System.IO 
main = do
     withFile "girlfriend.txt" ReadMode (\handle -> do
         contents <- hGetContents handle
         putStr contents) 
複製代碼

bracket 函數

bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c 
複製代碼

怎麼用

bracket (openFile name mode)-- 打開文件
    (\handle -> hClose handle) -- 失敗了怎麼辦
    (\handle -> fn handle)         -- 成功了怎麼辦
複製代碼

用 bracket 很容易實現 withFile。

生成隨機數對於一個函數,若是兩次調用它時使用相同的參數,它會把一樣的結果返回兩次。這很酷,由於它讓咱們能更好地理解程序,它還讓咱們可以延遲求值。可是,這也使得產生隨機數這件事變成困難。

random :: (RandomGen g, Random a) => g -> (a, g) 
複製代碼

random 接受一個隨機數生成器,返回一個隨機數和一個新的隨機數生成器。而後你能夠用新的生成器再去生成一個新的隨機數和一個新的生成器。以此類推。對於同一個生成器,獲得的隨機數是固定的。

ghci>import System.Random
ghci> random (mkStdGen 100) :: (Int, StdGen) 
(-1352021624, 651872571 1655838864) 
ghci> random (mkStdGen 100) :: (Int, StdGen) 
(-1352021624, 651872571 1655838864)
ghci> random (mkStdGen 949494) :: (Int, StdGen) 
(539963926, 466647808 1655838864)
ghci> random (mkStdGen 949488) :: (Float, StdGen) 
(0. 8938442, 1597344447 1655838864) 
ghci> random (mkStdGen 949488) :: (Bool, StdGen) 
(False, 1485632275 40692) 
ghci> random (mkStdGen 949488) :: (Integer, StdGen) 
(1691547873, 1597344447 1655838864) 
複製代碼

randoms 接受一個生成器,返回一個無限長的隨機值列表

ghci> take 5 $ randoms (mkStdGen 11) :: [Int] 
[-1807975507, 545074951,- 1015194702,- 1622477312,- 502893664] 
複製代碼

並無返回一個新的生成器,由於這個生成器在列表的末尾,而這個列表是無限長的……

randomR 在一個範圍內生成隨機數

ghci> randomR (1, 6) (mkStdGen 359353) 
(6, 149428957840692) 
複製代碼

getStdGen

以前咱們每次生成隨機數都要本身先寫一個數字,這很傻……因此 System.Random 提供了 getStdGen,它會向系統索要初始的全局生成器。但 getStdGen 是一個 IO 操做,它返回的類型是 IO stdGen。

import System.Random 
main = do
    gen <- getStdGen
    putStr $ take 20 (randomRs ('a',' z') gen) 
複製代碼

可是若是你調用 getStdGen 兩次,你會得到同一個 gen。第二次應該使用 newStdGen。這個函數除了返回一個 IO stdGen 類型的值,還會把全局生成器給更新了。你再調用 getStdGen 就能獲得不一樣的隨機數這時你會疑惑,getStdGen 爲何能返回不一樣的結果呢?由於它是一個 I/O 操做!

import System.Random

main = do
  gen <- getStdGen
  let a = fst ((random gen) :: (Int, StdGen))
  print a
  gen' <- newStdGen
  let b = fst ((random gen') :: (Int, StdGen))
  print b
  gen'' <- getStdGen
  let c = fst ((random gen'') :: (Int, StdGen))
  print c
  print "end"
複製代碼

這是我瞎寫的代碼。

字節串 bytestring

形如[ 1, 2, 3, 4] 的列表只是 1: 2: 3: 4:[] 的語法糖。當第一個元素被強制求值時(好比說輸出它),列表的其他部分 2: 3: 4:[] 只是一個許諾將會產生列表的承諾。咱們把這個承諾叫作 thunk。 thunk 大體上是一個延遲的計算。字節串有兩種風格:嚴格的(strict)和惰性的(lazy)。

strict bytestring 廢除了惰性,沒有 thunk。在 Data.ByteString 中實現了。 lazy bytestring 是惰性的,但比列表效率高。在 Data.ByteString.Lazy 中實現了。惰性的字節串的數據存儲在一些塊(chunk)裏(不要和 thunk 混淆了),每一個塊的大小是 64 KB。因此,若是你要對惰性的字節串的一個字節求值(好比說輸出它),最開頭的 64 KB 都會被求值。在那以後,其他塊實現爲一個承諾(thunk)。使用示例

import qualified Data. ByteString. Lazy as B 
import qualified Data. ByteString as S

ghci> B. pack [99, 97, 110] 
Chunk "can" Empty 
ghci> B. pack [98.. 120] 
Chunk "bcdefghijklmnopqrstuvwx" Empty 
ghci> let by = B. pack [98, 111, 114, 116] 
ghci> by 
Chunk "bort" Empty 
ghci> B. unpack by 
[98, 111, 114, 116] 
ghci> B. fromChunks [S. pack [40, 41, 42], S. pack [43, 44, 45], S. pack [46, 47, 48]] 
Chunk "()*" (Chunk "+,-" (Chunk "./0" Empty)) 
ghci> B. cons 85 $ B. pack [80, 81, 82, 84] 
Chunk "U" (Chunk "PQRT" Empty) 
複製代碼
相關文章
相關標籤/搜索