• Haskell 是纯函数式语言,函数无副作用,但是 IO 操作肯定会有副作用, Haskell 将 IO 操作分离了出来
  • Haskell 程序的编译
  • putStrLn :: String -> IO ()
  • putStrLn 取一个字符串作为参数,返回一个 I/O action ,而IO操作的返回类型为 ()
-- hello.hs:
main = putStrLn "Hello World!"
$ ghc --make hello
$ ./hello
Hello World!
  • do 可以将多个IO操作合并成一个,并返回最后一个操作的值
  • getLine :: IO String
  • getLine 是一个产生字符串的IO操作
  • <- 用于执行IO操作,并为IO操作的结果绑定名字
  • let 用于普通的值绑定
  • return 和其他语言中的含义不同,return 取一个值,包装到 IO 中,如 return "hello" :: IO String
-- reverse.hs:
main = do
    line <- getLine
    if null line
        then return ()
        else do
            putStrLn $ reverseWords line
            main

reverseWords :: String -> String
reverseWords = unwords . map reverse . words
$ ghc --make hello
[1 of 1] Compiling Reverse             ( reverse.hs, reverse.o )
Linking reverse ...
$ ./reverse
hello world
olleh dlrow
madam eye
madam eye
  • putStrLn 输出字符串并换行
  • putStr 输出字符串不换行
  • putChar 输出单个字符
  • printshow 的结果输出
  • when 定义在 Control.Monad ,取一个布尔值和IO操作,布尔值为真则返回那个IO操作,否则返回 return ()
  • sequence 取一个IO操作列表,依次执行,返回执行结果的列表
  • mapM 类似于 map ,把IO操作作用到一个列表的每个元素并对结果执行 sequence
  • mapM_mapM 几乎相同,只是不会返回 sequence 的结果
  • forever 定义在 Control.Monad ,取一个IO操作,返回一个永远重复执行该IO操作的IO操作
  • forM 定义在 Control.Monad ,和 mapM 类似,参数顺序相反:第一个参数是列表,第二个参数是映射到列表上的函数,可以灵活地配合 lambda 表达式
-- putStr / putStrLn / putChar / print:
myPutStr :: String -> IO ()
myPutStr []     = return ()
myPutStr (x:xs) = do
    putChar x
    myPutStr xs

main = do
    putStr "Hello, "
    putStrLn "World!"
    myPutStr "Hello, "
    putStrLn "World, too!"
    print True
    print 2
    print "haha"
    print [1,2,3]
-- Hello, World!
-- Hello, World, too!
-- True
-- 2
-- "haha"
-- [1,2,3]
-- when:
import Control.Monad
    input <- getLine
    when (input /= "") $ do
        putStrLn $ "You said: " ++ input
-- (Input) interesting
-- You said: interesting
-- sequence:
main = do
    rs <- sequence [getLine, getLine]
    print rs
    sequence $ map print [1,2,3]
-- (Input) hello
-- (Input) world
-- ["hello","world"]
-- 1
-- 2
-- 3
-- mapM / mapM_:
main = do
    res1 <- mapM print [1,2,3]
    print res1
    res2 <- mapM_ print [4,5]
    print res2
-- 1
-- 2
-- 3
-- [(),(),()]
-- 4
-- 5
-- ()
-- forever:
import Control.Monad
import Data.Char

main = forever $ do
    putStr "Give me some input: "
    line <- getLine
    putStrLn $ map toUpper line

-- forM:
import Control.Monad

main = do
    colors <- forM [1,2,3] (\num -> do
        putStrLn $ "Which color do you associate with the number" ++ show num ++ ": "
        getLine)
    putStrLn "The colors that you associate with 1, 2 and 3 are: "
    mapM putStrLn colors
-- Which color do you associate with the number1:
-- (Input) red
-- Which color do you associate with the number2:
-- (Input) green
-- Which color do you associate with the number3:
-- (Input) blue
-- The colors that you associate with 1, 2 and 3 are:
-- red
-- green
-- blue
  • getContents 读取 stdin 的所有输入直到遇到 EOF ,并且是惰性的
  • interact 取一个 String -> String 的函数作为参数,返回一个IO操作:接受输入、把函数应用在输入上、输出结果。
-- getContents:
main = do
    contents <- getContents
    putStr $ shortLinesOnly contents

shortLinesOnly :: String -> String
shortLinesOnly = unlines . filter (\line -> length line < 10) . lines

-- Input:
-- I'm short
-- So am I
-- I am a long line
-- oops
--
-- Output:
-- I'm short
-- So am I
-- oops
-- interact:
respondPalindromes :: String -> String
respondPalindromes =
    unlines .
    map (\xs -> if isPal xs then "palindrome" else "not a palindrome") .
    lines

isPal :: String -> Bool
isPal xs = xs == reverse xs

main = interact respondPalindromes

-- (Input) hehe
-- not a palindrome
-- (Input) ABCBA
-- palindrome
-- (Input) cookie
-- not a palindrome
  • openFile :: FilePath -> IOMode -> IO Handle 打开文件并返回句柄
  • type FilePath = String
  • data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
  • hClose :: Handle -> IO () 关闭句柄
import System.IO

main = do
    handle <- openFile "input.txt" ReadMode
    contents <- hGetContents handle
    putStr contents
    hClose handle
  • withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r 打开文件并对其进行操作,最后再关闭文件。如果出现了任何错误,保证句柄被关闭
  • bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c 定义在 Control.Exception 可以在出现异常时保证资源得到释放
  • 所有针对标准输入输出的函数前面加一个 h 就变为针对句柄操作的函数了
import System.IO
import Control.Exception

main = do
    myWithFile "file.txt" ReadMode (\handle -> do
        contents <- hGetContents handle
        putStr contents)

myWithFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
myWithFile name mode func = bracket
    (openFile name mode)
    (\handle -> hClose handle)
    (\handle -> func handle)
  • readFile :: FilePath -> IO String 打开文件并惰性地读取文件
  • writeFile :: FilePath -> String -> IO () 打开文件并用传入的字符串覆盖
  • appendFile :: FilePath -> String -> IO () 打开文件并追加传入的字符串
import Data.Char
import System.IO

main = do
    contents <- readFile "file.txt"
    writeFile "upperfile.txt" (map toUpper contents)
  • getProgName :: IO String 定义在 System.Environment 用来获取程序名
  • getArgs :: IO [String] 定义在 System.Environment 用来获取命令行参数
import System.Environment

main = do
    args <- getArgs
    name <- getProgName
    putStrLn "The arguments are:"
    mapM_ putStrLn args
    putStrLn "The program name is:"
    putStrLn name
$ ./hello first second "with space"
The arguments are:
first
second
with space
The program name is:
hello
  • 和随机数有关的东西都定义在 System.Random
  • random :: (Random a, RandomGen g) => g -> (a, g) 可以产生随机值,接受一个随机数生成器,返回一个随机值和一个新的随机数生成器
  • StdGenRandomGen 类型类的一个实例
  • mkStdGen :: Int -> StdGen 可以接受一个 Int 返回一个 StdGen
  • randoms :: (Random a, RandomGen g) => g -> [a] 接受一个随机数生成器,返回一个无限长的随机列表
  • randomR :: (Random a, RandomGen g) => (a, a) -> g -> (a, g) 接受一个上下界的二元组和一个随机数生成器,返回上下界内的随机数和二元组
  • randomRs :: (Random a, RandomGen g) => (a, a) -> g -> [a]randomRrandomRs 的结合体
import System.Random

random (mkStdGen 100) :: (Int, StdGen)    -- (-3650871090684229393,693699796 2103410263)
random (mkStdGen 100) :: (Int, StdGen)    -- (-3650871090684229393,693699796 2103410263)
random (mkStdGen 200) :: (Int, StdGen)    -- (-7184536474933282626,41011827 2103410263)
random (mkStdGen 300) :: (Float, StdGen)  -- (0.75473887,900860884 1655838864)

threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
    let (coin1, gen1) = random gen
        (coin2, gen2) = random gen1
        (coin3, gen3) = random gen2
    in (coin1, coin2, coin3)

threeCoins $ mkStdGen 21 -- (True,True,True)
threeCoins $ mkStdGen 22 -- (True,False,True)

randomR (1,6) (mkStdGen 2333)                           -- (6,93392676 40692)
take 10 $ randomRs ('a','z') (mkStdGen 23333) :: [Char] -- "ncbwibauyn"
  • getStdGen :: IO StdGen 得到全局生成器
  • newStdGen :: IO StdGen 将当前的全局生成器分裂成两个新的生成器,以一个更新全局生成器,返回另一个
import System.Random

main = do
    gen1 <- getStdGen
    putStrLn $ take 32 (randomRs ('a','z') gen1)
    gen2 <- getStdGen
    putStrLn $ take 32 (randomRs ('a','z') gen2)
    gen3 <- newStdGen
    putStrLn $ take 32 (randomRs ('a','z') gen3)
    gen4 <-getStdGen
    putStrLn $ take 32 (randomRs ('a','z') gen4)

-- nayncqsydwlevouchvlhpaneogtwiznf
-- nayncqsydwlevouchvlhpaneogtwiznf
-- xwlcpolurjaqpgxowmyisjohvbyhgjrj
-- burvfzpiuxmpzgxqbvstcasufhxferti
  • 列表 [1,2,3] 只是 1:2:3:[] 的语法糖,当 1 被强制求值时,后面的 2:3:[] 只是一个将会产生列表的承诺,叫做 thunk ,大致上就是延迟计算
  • 延迟计算的额外开销大多数时候不必要担心,但是读取并处理大文件就会是负担了
  • 严格字节串 Data.ByteString 完全废除惰性,且不可能是无限长的
  • 惰性字符串 Data.ByteString.Lazy 惰性程度不如列表,每次对一个 chunk 求值而不是像列表一样对每个元素求值
  • packWord8 (即一个字节)列表打包为字节串
  • unpack 用于把字节串解开成为列表
  • Data.ByteString.Lazy.fromChunks 接受严格字节串列表,转换为惰性字节串
  • Data.ByteString.Lazy.toChunks 接受惰性字节串,转换为严格字节串列表
  • cons 在一个字节串的前部插入一个字节
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString as S

let abcd = B.pack [97..100]
abcd                            -- "abcd"
B.unpack abcd                   -- [97,98,99,100]
B.fromChunks [S.pack [97..100], S.pack [101..104], S.pack[104..107]]
                                -- "abcdefghhijk"
B.toChunks $ B.pack [108..111]  -- ["lmno"]
85 `B.cons` B.pack [86..89]     -- "UVWXY"
  • 当一个程序需要把很多数据读取成字符串时,可以考虑使用字节串
  • 下面实现一个拷贝文件的程序
import System.IO
import System.Directory
import System.Environment
import Control.Exception
import qualified Data.ByteString.Lazy as B

main = do
    (src:dest:_) <- getArgs
    copy src dest

copy src dest = do
    contents <- B.readFile src
    bracketOnError
        (openTempFile "." "temp")
        (\(tempName, tempHandle) -> do
            hClose tempHandle
            removeFile tempName)
        (\(tempName, tempHandle) -> do
            B.hPutStr tempHandle contents
            hClose tempHandle
            renameFile tempName dest)