Haskell 函数式编程复习
以下几张表涵盖了一些 Haskell 中的一些常用函数;除末行 ord/chr 外均来自 Prelude。列表相关函数使用列表特化签名,省去 Foldable/Traversable 泛化;String = [Char]。之后还有一些平时做作业时零零散散记下来的踩过的坑。
列表:查询、切分、组合
| 函数 | 类型签名 | 用途 / 记忆 |
|---|---|---|
elem |
Eq a => a -> [a] -> Bool |
成员判断:elem x xs |
zip |
[a] -> [b] -> [(a,b)] |
对应位置配对;到较短列表结束 |
splitAt |
Int -> [a] -> ([a],[a]) |
一次得到前 n 个与其余部分 |
take / drop |
Int -> [a] -> [a] |
取 / 丢前 n 个;n≤0 时取空 / 不丢 |
takeWhile / dropWhile |
(a -> Bool) -> [a] -> [a] |
取 / 丢满足条件的连续前缀 |
map |
(a -> b) -> [a] -> [b] |
每个元素做变换 |
filter |
(a -> Bool) -> [a] -> [a] |
保留所有满足条件的元素 |
concat |
[[a]] -> [a] |
展平一层;concat [[1,2],[3]] = [1,2,3] |
all / any |
(a -> Bool) -> [a] -> Bool |
全部 / 至少一个满足;空列表为 True / False |
replicate |
Int -> a -> [a] |
重复 n 次;replicate 3 'a' = "aaa" |
iterate |
(a -> a) -> a -> [a] |
无限 [x,f x,f(f x),…],通常配合 take |
reverse |
[a] -> [a] |
逆序 |
sum / product |
Num a => [a] -> a |
求和 / 求积;空列表结果为 0 / 1 |
minimum / maximum |
Ord a => [a] -> a |
最小 / 最大元素;要求非空 |
函数组合与折叠
| 函数 | 类型签名 | 用途 / 记忆 |
|---|---|---|
flip |
(a -> b -> c) -> b -> a -> c |
交换前两个参数:flip f x y = f y x |
(.) |
(b -> c) -> (a -> b) -> a -> c |
f . g:先 g 后 f |
($) |
(a -> b) -> a -> b |
f $ x 等于 f x;最低优先级、右结合 |
curry |
((a,b) -> c) -> a -> b -> c |
把元组参数拆成两个参数 |
uncurry |
(a -> b -> c) -> (a,b) -> c |
把两个参数合成元组;map (uncurry (+)) |
const |
a -> b -> a |
保留第一参数,忽略第二参数 |
foldr |
(a -> b -> b) -> b -> [a] -> b |
foldr f z [x,y] = f x (f y z) |
foldl |
(b -> a -> b) -> b -> [a] -> b |
foldl f z [x,y] = f (f z x) y |
计算的组合:Maybe、列表、IO、Parser
| 函数 | 类型签名 | 用途 / 记忆 |
|---|---|---|
fmap / (<$>) |
Functor f => (a -> b) -> f a -> f b |
将纯函数用于上下文中的结果 |
pure |
Applicative f => a -> f a |
包装纯值;Maybe 为 Just,列表为单元素列表 |
(<*>) |
Applicative f => f (a -> b) -> f a -> f b |
g <$> x <*> y:提升二参数函数 |
(>>=) |
Monad m => m a -> (a -> m b) -> m b |
取前一步结果,决定下一步计算 |
sequence |
Monad m => [m a] -> m [a] |
组合计算并收集结果;[IO a] -> IO [a] |
sequenceA |
Applicative f => [f a] -> f [a] |
同类操作,但只要求 Applicative |
sequence [Just 1,Just 2] = Just [1,2];出现 Nothing 则失败。列表实例枚举组合:sequence [[1,2],[3,4]] = [[1,3],[1,4],[2,3],[2,4]]。
转换
| 函数 | 类型签名 | 用途 / 记忆 |
|---|---|---|
fromIntegral |
(Integral a, Num b) => a -> b |
整数转目标数值类型;整数长度参与小数除法 |
read / show |
Read a => String -> a / Show a => a -> String |
文字↔值;read 需确定目标类型,格式错误会异常 |
ord / chr |
Char -> Int / Int -> Char |
字符↔码点;例外:需 import Data.Char (ord,chr) |
高频组合:zip xs [0..] 编号;zip xs (drop 1 xs) 相邻对;concat . map f 映射后展平;sum . map (uncurry (*)) $ zip xs ys 点积。
fold 模板:sum = foldr (+) 0;concat = foldr (++) [];reverse = foldl (flip (:)) [];length = foldr (\_ n -> n+1) 0。
别混淆:takeWhile 遇到第一个不满足就停,filter 会继续筛选;foldl 不是严格折叠;return 不会提前退出(作用同 pure,但要求 Monad);(-1) 是负数,减一用 \x -> x-1。
注意模式匹配
以下错:empty 不会作为匹配第一个参数是否为“empty”,而被当成被绑定的变量名。同时函数是否相同无法直接比较。
(<|>) empty q = q
(<|>) p _ = p
以下正确。
(<|>) p q = P $ \x -> case app p x of
[] -> app q x
res -> res
关于“数”类型
- Num:最基础的数值运算,支持 + - *
- Integral:整数类型的运算,支持 quot rem div mod
- Fractional:支持除法,有典型实例 Float Double Rational(用p/q表示精确有理数) Complex Double(负数)
- Floating:支持一般数学函数,有典型实例 Float Double,支持 exp、log、sqrt、**...
- 整数路径:Integral => Real => Num
- 字面量 10 是多态的,但 x :: Int x = 10 之后,x 不能参加与浮点数的运算。fromIntegral 可以将 Int 转成任意 Num,比如 Double、Rational、Integer
Type declaration
val3 :: [(Bool, Char)]
val3 = [(False, '0'), (True, '1')]
val4 :: ([Bool], [Char])
val4 = ([False, True], ['0', '1'])
second :: [a] -> a
second xs = head (tail xs)
Merge sort
-- Problem #2: merge ascending lists
merge :: Ord a => [a] -> [a] -> [a]
merge a [] = a
merge [] b = b
merge (a:as) (b:bs) | a < b = a : merge as (b:bs)
| b < a = b : merge (a:as) bs
| b == a = a : b : merge as bs
-- End Problem #2
-- Problem #3: merge sort
msort :: Ord a => [a] -> [a]
msort [] = []
msort [a] = [a]
msort a = merge (msort $ take ((length a) `div` 2) a) (msort $ drop ((length a) `div` 2) a)
-- End Problem #3
函数复合与函数应用
odd = not . even
(++) = flip $ foldr (:)
前者中,not接受的参数是一个值,而flip接受的第一个参数是一个函数
区分函数对象和函数值对象!
count x y = length $ filter (== x) y
count x = length . filter (== x)
takeWhile函数
ghci> takeWhile (/= ' ') "abc def"
"abc"
@
使用 @ 可以直接引用原来的数据结构,而不需要重新构造它。
dropWhile :: (a -> Bool) -> [a] -> [a]
dropWhile _ [] = []
dropWhile p xs@(x:xs')
| p x = dropWhile p xs'
| otherwise = xs
惰性求值若干则
套路:zip + 无限iterate
type Bit = Int
-- 将 Bit 作为类型 Int 的别名
bin2int :: [Bit] -> Int
bin2int bits = sum [ w * b | (w, b) <- zip weights bits ]
where weights = iterate (* 2) 1
-- iterate is defined in Prelude
iterate :: (a -> a) -> a -> [a]
iterate f x = x : iterate f (f x)
筛法求素数
primes = sieve [2..]
sieve (p:xs) = p : sieve [ x | x <- xs, x `mod` p /= 0 ]
Newton's square root
sqroot :: Double -> Double
sqroot n = snd $ head $ dropWhile (\(a, a') -> a - a' > 0.00001 || a - a' < -0.00001) aa's
where aa's = (0, 1) : [ (a' , (a' + n / a') / 2) | (a, a') <- aa's ]
例子:投票程序
ghci> votes = ["Red", "Blue", "Green", "Blue", "Blue", "Red"]
ghci> result votes
[(1,"Green"),(2,"Red"),(3,"Blue")]
ghci> winner votes
"Blue"
result :: Ord a => [a] -> [(Int, a)]
result vs = sort [ (count v vs, v) | v <- rmdups vs ]
-- The sort function is defined in Data.List
rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) = x : filter (/= x) (rmdups xs)
count :: Eq a => a -> [a] -> Int
count x = length . filter (== x)
winner :: Ord a => [a] -> a
winner = snd . last . result
生成所有bool:活用map
bools :: Int -> [[Bool]]
bools 0 = [[]]
bools n = map (False :) bss ++ map (True :) bss
where bss = bools $ n - 1
生成幂集
powerset :: [a] -> [[a]]
powerset [] = [[]]
powerset (x:xs) = xss ++ map (x:) xss
where
xss = powersetRec xs
-- 或:
import Control.Monad (filterM)
powerset :: [a] -> [[a]]
powerset = filterM (\_ -> [True, False])
-- 或:
powerset :: [a] -> [[a]]
powerset [] = [[]]
powerset (x:xs) =
[zs | ys <- powerset xs, zs <- [ys, x:ys]]
去重函数:rmdups
rmdups (x:xs) = x : filter (/= x) (rmdups xs)
-- 下面这种就显得非常笨拙了
rmdups a = [ fst x | x <- zip a (iterate (+1) 0), not $ elem (fst x) (take (snd x) a)]
定义一棵树
data Tree a = Leaf a | Node (Tree a) (Tree a)
-- !! 不是 Node Tree Tree !!!!!
例子:计算将一个元素插入一个 list 中的所有可能方式:
interleave :: a -> [a] -> [[a]]
interleave x [] = [[x]]
interleave x (y:ys) = (x:y:ys) : map (y:) (interleave x ys)
-- 注意不是:(x:y:ys) ++ y:interleave x ys。后者犯了几个错误?
-- 错误式把 [a] 与 [[a]] 混淆;应以 (:) 把一个完整列表放入结果列表,用 map (y:) 给每个递归结果加前缀。
ghci> interleave 1 [2, 3, 4]
[ [1,2,3,4], [2,1,3,4], [2,3,1,4], [2,3,4,1] ]
技巧:利用列表是否为空来表示"Maybe"
eval :: Expr -> [Int]
eval (Val n) = [n | n > 0]
eval (App o l r) =
[apply o x y | x <- eval l, y <- eval r, valid o x y]
这里的列表不表示“一个表达式有很多计算结果”,而表示: [] :表达式不合法 [n] :表达式合法,结果是 n 这个设计的好处是:失败会自然传播。 如果左子表达式非法,x <- eval l 就变成 x <- []。没有任何可选的 x,整个推导式自然得到 []。无需在每层写一套错误处理分支。
Foldable相关
toList = foldMap (\x -> [x])
> concat (Node (Leaf [1,2]) (Leaf [3]))
[1,2,3]
其它
函数应用优先级最高,改变顺序别忘了加括号!
定义函数时,别忘了写参数!
递归勿忘边界条件!(又 惰性求值递归,边界为一开始的"0:1:":fibs = 0 : 1 : [ x + y | (x, y) <- zip fibs (tail fibs) ])
不是 != ,是 /= !
不是 a and b,是 a && b 或 and a b!
where 只能附加在 函数定义后面(也允许带 guards 的函数,但是附加到整个定义而不是一条匹配)、case ... of 的某条分支后面、class、instance、module
不要漏写/漏看 Data Constructor!
注意 <*> 实现的过程中先解析左侧!