Haskell 学习笔记 (5)
- 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
输出单个字符print
将show
的结果输出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)
可以产生随机值,接受一个随机数生成器,返回一个随机值和一个新的随机数生成器StdGen
是RandomGen
类型类的一个实例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]
即randomR
和randomRs
的结合体
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
求值而不是像列表一样对每个元素求值 pack
把Word8
(即一个字节)列表打包为字节串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)