Haskell学习笔记:Input and Output

Input and Output

让我们来研究如何写一个跟真实世界交互的 Haskell 程序!

Hello, world!

  • 在一个 do block 中,最后一个 action 不能绑定任何名字。
  • 从一个 I/O action 中取出值,你必须要在另一个 I/O action 中将他用 <- 绑定给一个名字。
  • 在 I/O do block 中需要 return 的原因大致上有两个:一个是我们需要一个什么事都不做的 I/O action,或是我们不希望这个 do block 形成的 I/O action 的结果值是这个 block 中的最后一个 I/O action,我们希望有一个不同的结果值,所以我们用 return 来作一个 I/O action 包了我们想要的结果放在 do block 的最后。+

处理 I/O的函数

  • putStr:接受一个字符串当作参数,并回传一个 I/O action 打印出字符串到终端上。其type signature 是 putStr :: String -> IO (),所以是一个包在 I/O action 中的 unit。也就是空值,没有办法绑定他。
  • putStrLn:与putStr差别在于可以换行
main = do putStr "Hey, "
          putStr "I'm "
          putStrLn "Andy!"

runhaskell putstr_test.hs
Hey, I'm Andy!
  • putChar:接受一个字符,并回传一个 I/O action 将他打印到终端上。putStr 实际上就是 putChar 递归定义出来的:
putStr :: String -> IO ()
putStr [] = return ()
putStr (x:xs) = do
   putChar x
   putStr xs
  • print:接受任何是 Show typeclass 的 instance 的型态的值,等于putStrLn . show。首先调用 show 然后把结果喂给 putStrLn,回传一个 I/O action 打印出我们的值。
main = do print True
        print 2

runhaskell print_test.hs
True
2 
  • when:首先要import Contorl.Monad才能使用。接受一个 boolean 值跟一个 I/O action。如果 boolean 值是 True,便回传我们传给他的 I/O action。如果 boolean 值是 False,便回传 return ()。
import Control.Monad

main = do
   c <- getChar
   when (c /= ' ') $ do
       putChar c
       main
  • sequence :接受一串 I/O action,并回传一个会依序运行他们的 I/O action。运算的结果是包在一个 I/O action 的一连串 I/O action 的运算结果。他的 type signature 是 sequence :: [IO a] -> IO [a]。
main = do
   rs <- sequence [getLine, getLine, getLine]
   print rs

ghci> sequence (map print [1,2,3,4,5])
1
2
3
4
5
[(),(),(),(),()]

最后一行魔幻的[(),(),(),(),()]是因为putStrLn函数结果为(),终端是不显示的。但这里的结果为列表的空数组,所有被print出来了。(记得print等于putStrLn . show)

  • mapM:sequence.map
  • mapM_: sequence.map但丢掉最后的结果
ghci> mapM print [1,2]
1
2
[(),(),()]
ghci> mapM_ print [1,2]
1
2
  • forever:接受一个 I/O action 并回传一个永远作同一件事的 I/O action。要import Contorl.Monad才能使用。
  • forM:跟 mapM 的作用一样,只是参数的顺序相反而已。第一个参数是串列,而第二个则是函数。
import Control.Monad

main = do
   colors <- forM [1,2,3,4] (\a -> do
       putStrLn $ "Which color do you associate with the number " ++ show a ++ "?"
       color <- getLine
       return color)
   putStrLn "The colors that you associate with 1, 2, 3 and 4 are: "
   mapM putStrLn colors

runhaskell from_test.hs
Which color do you associate with the number 1?
white
Which color do you associate with the number 2?
blue
Which color do you associate with the number 3?
red
Which color do you associate with the number 4?
orange
The colors that you associate with 1, 2, 3 and 4 are:
white
blue
red
orange

(\a -> do …) 整体看成接受一个数字并回传一个 I/O action 的函数,forM 产生一个 I/O action,我们把结果绑定到 colors 这名称。colors 是一个普通包含字符串的串列。

文件与字符流

  • getContents:标准输入读取直到 end-of-file 字符的 I/O action。型态是 getContents :: IO String。
    最酷的是 getContents 是惰性 I/O (Lazy I/O)。当我们写了 foo <- getContents,他并不会马上读取所有输入,将他们存在 memory 里面。他只有当你真的需要输入数据的时候才会读取。
main = do
   contents <- getContents
   putStr (shortLinesOnly contents)

shortLinesOnly :: String -> String
shortLinesOnly input =
   let allLines = lines input
       shortLines = filter (\line -> length line < 10) allLines
       result = unlines shortLines
   in result

以上程序读取输入,并打印出少于十个字符的行。

  • interact: interact 接受一个 String -> String 的函数,并回传一个 I/O action。那个 I/O action 会读取一些输入,调用提供的函数,然后把函数的结果打印出来。
    因此,上面那个程序也可以这么写:
main = interact shortLinesOnly

shortLinesOnly :: String -> String
shortLinesOnly input =
    let allLines = lines input
        shortLines = filter (\line -> length line < 10) allLines
        result = unlines shortLines
    in result

读写文件

让我们来研究一下以下程序如何读写文件?

import System.IO

main = do
    handle <- openFile "Full of nonsense.txt" ReadMode
    contents <- hGetContents handle
    putStr contents
    hClose handle
    
runhaskell readandwrite.hs
This is a document full of nonsense.
  • Full of nonsense.txt是读取的文本文件,内容即最后一行。
  • readandwrite.hs是此命令文件名。
  • openFile: 其type signature 是 openFile :: FilePath -> IOMode -> IO Handle。
    openFile 接受一个文件路径跟一个 IOMode,并回传一个 I/O action,他会打开一个文件并把文件关联到一个 handle。
  • FilePath: 某个String的类型别名,type FilePath = String。
  • IOMode:一个定义如下的类型
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
  • Handle: 型态为 Handle 的值代表我们的文件在哪里。有了 handle 我们才知道要从哪个文件读取内容。

  • hGetContents:从Handle处读取内容并回传一个 IO String。除此以外其他的和函数getContents一样(函数getContents从终端处读取内容)。

  • hClose:接受一个 handle 然后回传一个关掉文件的 I/O action。

  • withFile:另一种实现方式。其中withFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a。

import System.IO

main = do
    withFile "Full of nonsense.txt" ReadMode (\handle -> do
            contents <- hGetContents handle
            putStr contents)
  • hGetLine、hPutStr、hPutStrLn、hGetChar 等函数。他们分别是少了 h 的那些函数的对应。只不过他们要多拿一个 handle 当参数,并且是针对特定文件而不是标准输出或标准输入。
  • readFile:readFile :: FilePath -> IO String readFile 接受一个文件路径,回传一个惰性读取我们文件的 I/O action。然后将文件的内容绑定到某个字符串。
  • writeFile: writefile :: FilePath -> String -> IO ()。他接受一个文件路径,以及一个要写到文件中的字符串,并回传一个写入动作的 I/O action。如果这个文件已经存在了,他会先把文件内容都砍了再写入。
    下面示范了如何把 Full of nonsense.txt 的内容转成大写然后写入到 FFull of nonsense.txt 中
import System.IO
import Data.Char

main = do
    contents <- readFile "Full of nonsense.txt"
    writeFile "FFull of nonsense.txt" (map toUpper contents)
  • appendFile: 作用很像 writeFile,只是 appendFile 并不会在文件存在时把文件内容砍掉而是接在后面。
  • getArgs:System.Environment 模块中,getArgs :: IO [String],他是一个拿取命令行参数的 I/O action,并把结果放在包含的一个串列中。
  • getProgName: getProgName :: IO String,他则是一个 I/O action 包含了程序的名称。

Bytestrings

  • Haskell有两种类型的Bytestring: strict and lazy ones。
  • 前一种在module Data.ByteString中,a strict bytestring represents a series of bytes in an array。
    The upside is that there’s less overhead because there are no thunks (the technical term for promise) involved. The downside is that they’re likely to fill your memory up faster because they’re read into memory at once.
  • 后一种在module Data.ByteString.Lazy中,They’re lazy, but not quite as lazy as lists. If you evaluate a byte in a lazy bytestring (by printing it or something), the first 64K will be evaluated. After that, it’s just a promise for the rest of the chunks.
    This is cool because it won’t cause the memory usage to skyrocket and the 64K probably fits neatly into your CPU’s L2 cache.

一些处理bytestring的函数

  • pack:pack :: [Word8] -> ByteString
    it takes a list of bytes of type Word8 and returns a ByteString. You can think of it as taking a list, which is lazy, and making it less lazy, so that it’s lazy only at 64K intervals.
  • unpack: the inverse function of pack. It takes a bytestring and turns it into a list of bytes.
  • fromChunks:takes a list of strict bytestrings and converts it to a lazy bytestring.
  • toChunks:takes a lazy bytestring and converts it to a list of strict ones.
  • cons:The bytestring version of **:**It’s lazy though, so it will make a new chunk even if the first chunk in the bytestring isn’t full.
  • cons’:the strict version of cons.
ghci> B.cons 85 $ B.pack [80,81,82,84]  
Chunk "U" (Chunk "PQRT" Empty)  
ghci> B.cons' 85 $ B.pack [80,81,82,84]  
Chunk "UPQRT" Empty  

ghci> foldr B.cons B.empty [50..60]  
Chunk "2" (Chunk "3" (Chunk "4" (Chunk "5" (Chunk "6" (Chunk "7" (Chunk "8" (Chunk "9" (Chunk ":" (Chunk ";" (Chunk "<"  
Empty))))))))))  
ghci> foldr B.cons' B.empty [50..60]  
Chunk "23456789:;<" Empty  
  • empty:makes an empty bytestring
  • Otherwise, the bytestring modules have a load of functions that are analogous to those in Data.List, including, but not limited to, head, tail, init, null, length, map, reverse, foldl, foldr, concat, takeWhile, filter, etc.
  • Many times, you can convert a program that uses normal strings to a program that uses bytestrings by just doing the necessary imports and then putting the qualified module names in front of some functions. Sometimes, you have to convert functions that you wrote to work on strings so that they work on bytestrings, but that’s not hard.如下所示:
import System.Environment  
import qualified Data.ByteString.Lazy as B  
  
main = do  
    (fileName1:fileName2:_) <- getArgs  
    copyFile fileName1 fileName2  
  
copyFile :: FilePath -> FilePath -> IO ()  
copyFile source dest = do  
    contents <- B.readFile source  
    B.writeFile dest contents  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值