Skip to main content

附录 A:精选习题解答(Selected solutions)

原文:Graham Hutton, Programming in Haskell, Second Edition, Appendix A。维护者已确认本书可翻译并发布用于学习研究。

本附录给出各章部分练习的参考解答。如果在 GHCi 中测试这些解答,请注意某些函数可能需要改名,以避免与标准 Prelude 中的内置函数冲突。例如,可以把 product 改名为 myproduct

A.1 引言(Introduction)

练习 1

double (double 2)
= { 应用内部的 double }
double (2 + 2)
= { 应用 double }
(2 + 2) + (2 + 2)
= { 应用第一个 + }
4 + (2 + 2)
= { 应用第二个 + }
4 + 4
= { 应用 + }
8

或者:

double (double 2)
= { 应用外部的 double }
(double 2) + (double 2)
= { 应用第二个 double }
(double 2) + (2 + 2)
= { 应用第二个 + }
(double 2) + 4
= { 应用 double }
(2 + 2) + 4
= { 应用第一个 + }
4 + 4
= { 应用 + }
8

还有许多其他可能的答案。

练习 2

sum [x]
= { 应用 sum }
x + sum []
= { 应用 sum }
x + 0
= { 应用 + }
x

练习 3

product []     = 1
product (n:ns) = n * product ns

例如:

product [2,3,4]
= { 应用 product }
2 * (product [3,4])
= { 应用 product }
2 * (3 * product [4])
= { 应用 product }
2 * (3 * (4 * product []))
= { 应用 product }
2 * (3 * (4 * 1))
= { 应用 * }
24

A.2 初步实践(First steps)

练习 2

(2^3)*4
(2*3)+(4*5)
2+(3*(4^5))

练习 3

n = a `div` length xs
where
a = 10
xs = [1,2,3,4,5]

练习 4

last xs = head (reverse xs)

或者:

last xs = xs !! (length xs - 1)

A.3 类型与类(Types and classes)

练习 1

['a','b','c'] :: [Char]
('a','b','c') :: (Char,Char,Char)
[(False,'O'),(True,'1')] :: [(Bool,Char)]
([False,True],['0','1']) :: ([Bool],[Char])
[tail, init, reverse] :: [[a] -> [a]]

练习 2

bools = [False,True]
nums = [[1,2],[3,4],[5,6]]
add x y z = x+y+z
copy x = (x,x)
apply f x = f x

对于 boolsnumsadd,还有许多其他可能的答案。

A.4 定义函数(Defining functions)

练习 1

halve xs = (take n xs, drop n xs)
where n = length xs `div` 2

或者:

halve xs = splitAt (length xs `div` 2) xs

练习 2

third xs = head (tail (tail xs))
third xs = xs !! 2
third (_:_:x:_) = x

练习 3

safetail xs = if null xs then [] else tail xs

safetail xs | null xs = []
| otherwise = tail xs

safetail [] = []
safetail (_:xs) = xs

练习 4

False || False = False
False || True = True
True || False = True
True || True = True

False || False = False
_ || _ = True

False || b = b
True || _ = True

b || c | b == c = b
| otherwise = True

A.5 列表推导式(List comprehensions)

练习 1

sum [x^2 | x <- [1..100]]

练习 2

grid m n = [(x,y) | x <- [0..m], y <- [0..n]]

练习 3

square n = [(x,y) | (x,y) <- grid n n, x /= y]

练习 4

replicate n x = [x | _ <- [1..n]]

练习 5

pyths n = [(x,y,z) | x <- [1..n],
y <- [1..n],
z <- [1..n],
x^2 + y^2 == z^2]

A.6 递归函数(Recursive functions)

练习 1

该函数不会终止,因为每次应用 fac 都会把参数减一,因此永远无法到达基本情形。

fac 0         = 1
fac n | n > 0 = n * fac (n-1)

练习 2

sumdown 0 = 0
sumdown n = n + sumdown (n-1)

练习 3

(^) :: Int -> Int -> Int
m ^ 0 = 1
m ^ n = m * (m ^ (n-1))

例如:

2 ^ 3
= { 应用 ^ }
2 * (2 ^ 2)
= { 应用 ^ }
2 * (2 * (2 ^ 1))
= { 应用 ^ }
2 * (2 * (2 * (2 ^ 0)))
= { 应用 ^ }
2 * (2 * (2 * 1))
= { 应用 * }
8

练习 4

euclid x y | x == y = x
| x < y = euclid x (y-x)
| y < x = euclid (x-y) y

A.7 高阶函数(Higher-order functions)

练习 1

map f (filter p xs)

练习 2

all p = and . map p
any p = or . map p

takeWhile _ [] = []
takeWhile p (x:xs) | p x = x : takeWhile p xs
| otherwise = []

dropWhile _ [] = []
dropWhile p (x:xs) | p x = dropWhile p xs
| otherwise = x:xs

练习 3

map f = foldr (\x xs -> f x : xs) []

filter p = foldr (\x xs -> if p x then x:xs else xs) []

练习 4

dec2int = foldl (\x y -> 10*x + y) 0

练习 5

curry :: ((a,b) -> c) -> (a -> b -> c)
curry f = \x y -> f (x,y)

uncurry :: (a -> b -> c) -> ((a,b) -> c)
uncurry f = \(x,y) -> f x y

A.8 声明类型与类(Declaring types and classes)

练习 1

mult m Zero     = Zero
mult m (Succ n) = add m (mult m n)

练习 2

occurs x (Leaf y) = x == y
occurs x (Node l y r) = case compare x y of
LT -> occurs x l
EQ -> True
GT -> occurs x r

这个版本更高效,因为对每个节点只需要在 xy 之间做一次比较,而前一个版本可能需要两次。

练习 3

leaves (Leaf _)   = 1
leaves (Node l r) = leaves l + leaves r

balanced (Leaf _) = True
balanced (Node l r) = abs (leaves l - leaves r) <= 1
&& balanced l && balanced r

练习 4

halve xs = splitAt (length xs `div` 2) xs

balance [x] = Leaf x
balance xs = Node (balance ys) (balance zs)
where (ys,zs) = halve xs

A.9 倒计时问题(The countdown problem)

练习 1

choices xs = [zs | ys <- subs xs, zs <- perms ys]

练习 2

removeone x [] = []
removeone x (y:ys) | x == y = ys
| otherwise = y : removeone x ys

isChoice [] _ = True
isChoice (x:xs) [] = False
isChoice (x:xs) ys = elem x ys && isChoice xs (removeone x ys)

练习 3

这会导致不终止,因为对 exprs 的递归调用不再保证会减小列表长度。

A.10 交互式编程(Interactive programming)

练习 1

putStr xs = sequence_ [putChar x | x <- xs]

练习 2

putBoard = putBoard' 1

putBoard' r [] = return ()
putBoard' r (n:ns) = do putRow r n
putBoard' (r+1) ns

练习 3

putBoard b = sequence_ [putRow r n | (r,n) <- zip [1..] b]

A.11 不可战胜的井字棋(Unbeatable tic-tac-toe)

练习 1

使用定义:

nodes :: Tree a -> Int
nodes (Node _ ts) = 1 + sum (map nodes ts)

mydepth :: Tree a -> Int
mydepth (Node _ []) = 0
mydepth (Node _ ts) = 1 + maximum (map mydepth ts)

有:

> let tree = gametree empty O
> nodes tree
549946
> mydepth tree
9

练习 2

import System.Random hiding (next)

bestmoves :: Grid -> Player -> [Grid]
bestmoves g p = [g' | Node (g',p') _ <- ts, p' == best]
where
tree = prune depth (gametree g p)
Node (_,best) ts = minimax tree

play' :: Grid -> Player -> IO ()
play' g p
| wins O g = putStrLn "Player O wins!\n"
| wins X g = putStrLn "Player X wins!\n"
| full g = putStrLn "It's a draw!\n"
| p == O = do i <- getNat (prompt p)
case move g i p of
[] -> do putStrLn "ERROR: Invalid move"
play' g p
[g'] -> play g' (next p)
| p == X = do putStr "Player X is thinking... "
let gs = bestmoves g p
n <- randomRIO (0,length gs - 1)
play (gs !! n) (next p)

注意,从导入库中隐藏函数 next,是为了避免与我们在玩家值上定义的 next 函数冲突。

A.12 单子及更多内容(Monads and more)

练习 1

instance Functor Tree where
-- fmap :: (a -> b) -> Tree a -> Tree b
fmap g Leaf = Leaf
fmap g (Node l x r) = Node (fmap g l) (g x) (fmap g r)

练习 2

instance Functor ((->) a) where
-- fmap :: (b -> c) -> (a -> b) -> (a -> c)
fmap = (.)

练习 3

instance Applicative ((->) a) where
-- pure :: b -> (a -> b)
pure = const

-- (<*>) :: (a -> b -> c) -> (a -> b) -> (a -> c)
g <*> h = \x -> g x (h x)

练习 4

instance Functor ZipList where
-- fmap :: (a -> b) -> ZipList a -> ZipList b
fmap g (Z xs) = Z (fmap g xs)

instance Applicative ZipList where
-- pure :: a -> ZipList a
pure x = Z (repeat x)

-- <*> :: ZipList (a -> b) -> ZipList a -> ZipList b
(Z gs) <*> (Z xs) = Z [g x | (g,x) <- zip gs xs]

A.13 单子解析(Monadic parsing)

练习 1

comment = do string "--"
many (sat (/= '\n'))
return ()

练习 2

表达式 2+3+4 使用第二版文法时有两种可能解析,对应于两种结合方式:

(2+3)+4
2+(3+4)

也就是说,一棵解析树先把左侧 2+3 构造成表达式,再与 4 相加;另一棵解析树先把右侧 3+4 构造成表达式,再与 2 相加。

练习 3

使用第三版文法,解析树对应下面这些结构:

2+3       ==> 2 + 3
2*3*4 ==> 2 * (3 * 4)
(2+3)+4 ==> (2 + 3) + 4

换言之,乘法优先于加法,并且加法和乘法都按文法给定的方式向右结合;括号会显式改变分组。

练习 4

如果不进行左因子化(left-factorising),所得解析器会发生大量回溯,并且在表达式大小上花费指数时间。例如,一个数字在被识别为表达式之前,会被解析四次。

A.14 Foldable 及相关抽象(Foldables and friends)

练习 1

instance (Monoid a, Monoid b) => Monoid (a,b) where
-- mempty :: (a,b)
mempty = (mempty, mempty)

-- mappend :: (a,b) -> (a,b) -> (a,b)
(x1,y1) `mappend` (x2,y2) =
(x1 `mappend` x2, y1 `mappend` y2)

练习 2

instance Monoid b => Monoid (a -> b) where
-- mempty :: a -> b
mempty = \_ -> mempty

-- mappend :: (a -> b) -> (a -> b) -> (a -> b)
f `mappend` g = \x -> f x `mappend` g x

A.15 惰性求值(Lazy evaluation)

练习 1

1+(2*3) 中唯一的 redex 是 2*3,它既是最内层也是最外层。

(1+2)*(2+3) 中的 redex 是 1+22+3,其中第一个是最内层。

fst (1+2,2+3) 中的 redex 是 1+22+3fst (1+2,2+3),其中第一个是最内层,最后一个是最外层。

(\x -> 1 + x) (2*3) 中的 redex 是 (\x -> 1 + x) (2*3)2*3,其中第一个是最外层,第二个是最内层。

练习 2

最外层求值:

fst (1+2, 2+3)
= { 应用 fst }
1+2
= { 应用 + }
3

最内层求值:

fst (1+2, 2+3)
= { 应用第一个 + }
fst (3, 2+3)
= { 应用 + }
fst (3, 5)
= { 应用 fst }
3

最外层求值更好,因为它避免了对第二个参数求值,因此少用一步化简。

练习 3

mult 3 4
= { 应用 mult }
(\x -> (\y -> x * y)) 3 4
= { 应用外层 lambda }
(\y -> 3 * y) 4
= { 应用 lambda }
3 * 4
= { 应用 * }
12

A.16 程序推理(Reasoning about programs)

练习 1

基本情形:

add Zero (Succ m)
= { 应用 add }
Succ m
= { 反应用 add }
Succ (add Zero m)

归纳情形:

add (Succ n) (Succ m)
= { 应用 add }
Succ (add n (Succ m))
= { 归纳假设 }
Succ (Succ (add n m))
= { 反应用 add }
Succ (add (Succ n) m)

练习 2

基本情形:

add Zero m
= { 应用 add }
m
= { add 的性质 }
add m Zero

归纳情形:

add (Succ n) m
= { 应用 add }
Succ (add n m)
= { 归纳假设 }
Succ (add m n)
= { add 的性质 }
add m (Succ n)

练习 3

基本情形:

all (== x) (replicate 0 x)
= { 应用 replicate }
all (== x) []
= { 应用 all }
True

归纳情形:

all (== x) (replicate (n+1) x)
= { 应用 replicate }
all (== x) (x : replicate n x)
= { 应用 all }
x == x && all (== x) (replicate n x)
= { 应用 == }
True && all (== x) (replicate n x)
= { 应用 && }
all (== x) (replicate n x)
= { 归纳假设 }
True

练习 4

基本情形:

[] ++ []
= { 应用 ++ }
[]

归纳情形:

(x : xs) ++ []
= { 应用 ++ }
x : (xs ++ [])
= { 归纳假设 }
x : xs

基本情形:

[] ++ (ys ++ zs)
= { 应用 ++ }
ys ++ zs
= { 反应用 ++ }
([] ++ ys) ++ zs

归纳情形:

(x : xs) ++ (ys ++ zs)
= { 应用 ++ }
x : (xs ++ (ys ++ zs))
= { 归纳假设 }
x : ((xs ++ ys) ++ zs)
= { 反应用 ++ }
(x : (xs ++ ys)) ++ zs
= { 反应用 ++ }
((x : xs) ++ ys) ++ zs

练习 5

基本情形:

take 0 xs ++ drop 0 xs
= { 应用 take, drop }
[] ++ xs
= { 应用 ++ }
xs

基本情形:

take (n+1) [] ++ drop (n+1) []
= { 应用 take, drop }
[] ++ []
= { 应用 ++ }
[]

归纳情形:

take (n+1) (x:xs) ++ drop (n+1) (x:xs)
= { 应用 take, drop }
(x : take n xs) ++ (drop n xs)
= { 应用 ++ }
x : (take n xs ++ drop n xs)
= { 归纳假设 }
x : xs

A.17 推导编译器(Calculating compilers)

练习 1

解答见文献 [39],本章也基于该文写成。