《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) 
复制代码
相关文章
相关标签/搜索