Upload
tuan
View
144
Download
0
Embed Size (px)
DESCRIPTION
高阶函数 Higher Order Functions. 阅读第九章、第十章. 计算模式的推广. 我们经常需要对列表的元素进行某种统一的操作 : doubleAll :: [Int] -> [Int] doubleAll [] = [] doubleAll (x:xs) = 2*x : doubleAll xs 或者 doubleAll xs = [2*x | x Bool even n = n`mod` 2 == 0. 又如 : is Even xs = [even x | x
Citation preview
高阶函数Higher Order Functions
阅读第九章、第十章
计算模式的推广
我们经常需要对列表的元素进行某种统一的操作: doubleAll :: [Int] -> [Int]
doubleAll [] = []
doubleAll (x:xs) = 2*x : doubleAll xs
或者 doubleAll xs = [2*x | x<- xs]
又如:
isEven xs = [even x | x <- xs]
even :: Int -> Booleven n = n`mod` 2 == 0
高阶函数
一般的计算模式: [x, y, z, …] [ f x, f y, f z, …]
我们把这种模式称作映射 map :
map f xs = [f x | x <- xs]
map 的第一个参数是函数,这种以函数为输入 的函数称为高阶函数 (higher order functions) 。
map 的另一种定义方法map f [] = [] .
map f (x:xs) = f x : map f xs
例如
map double [1,2,3,4,5] = [2,4,6,8,10]
map even [1, 2, 3, 4, 5] = [False, True, False, True, False]
使用高阶函数可以书写更简洁的函数定义:
doubleAll xs = map double xs
isEven xs = map even xs
map 的类型是什么?map even [1, 2, 3, 4, 5] = [False, True, False, True, False]
任意一元函数 任何 a 上的列表
结果是 b 上的列表map 的类型是多态的map :: (a -> b) -> [a] -> [b]
map double [1,2,3,4,5] = [2,4,6,8,10]
map :: (Int -> Bool) -> [Int] -> [Bool]
map :: (Int -> Int) -> [Int] -> [Int]
使用 map 定义水平翻转
flipH
[” # ”, ” # ”, ”######”, ” # ”, ” # ”]
[” # ”, ” # ”, ”######”, ” # ”, ” # ”]
flipH :: Picture -> Picture
flipH pic = [reverse line | line <- pic]
filpH pic = map reverse pic
过滤函数
例如,选出列表中的偶数: [x | x<- [1..5], even x] = [2,4]
又如,选取一个串中的数字 [x | x <- “born on 15 April 1986”, isDigit x] = “151
986”
一般地,我们定义如下过滤模式
filter p xs = [x | x <- xs, p x]
isDigit :: Char -> Bool
Filter 的类型是什么 ?
filter even [1, 2, 3, 4, 5] = [2, 4]
even :: Int -> Bool
filter :: (Int -> Bool) -> [Int] -> [Int]
filter :: (a -> Bool) -> [a] -> [a]
一个表示性质的函数类型
为什么使用高阶函数?
• 高阶函数是函数程序设计的灵魂。• 许多相似的函数都可以使用一个高阶函数代替。• 高阶函数比一阶函数更具有表现力,我们可以通过使用不同的函数参数解决不同的问题。• 每当多个函数具有相似的模式时,定义一个高阶函数。
折叠的例子
sum [] = 0sum (x:xs) = x + sum xs
计算模式:使用同一个运算将列表的元素结合起来。
具体到 sum: 起始值 0, 运算 +
and: 起始值 True, 运算 &&
and [] = True and (x:xs) = x && (and xs)
sum [2,4,6] = 12 and [True, False, True] = False
折叠函数
sum [] = 0sum (x:xs) = x + sum xs
将 0 和 + 分别用参数代替 , 定义一个高阶函数 :
foldr op z [] = zfoldr op z (x:xs) = x `op` foldr op z xs
sum xs = foldr plus 0 xs
where plus x y = x+y
sum xs = foldr plus 0 xs
where plus x y = x+y
sum xs = foldr (+) 0 xs
使用 foldr 定义的 sum
或者
使用 foldr 的函数定义
将列表元素使用某种运算结合起来是一种常见运算,这种运算均可以使用 foldr 定义,而不必使用递归。
product xs = foldr (*) 1 xsconcat xs = foldr (++) [] xsmaximum (x:xs) = foldr max x xs
函数 foldr 的另一种解读
foldr op z [] = zfoldr op z (x:xs) = x `op` foldr op z xs
例如foldr op z (a:(b:(c:[]))) = a `op` foldr op z (b:(c:[]))
= a `op` (b `op` foldr op z (c:[]))
= a `op` (b `op` (c `op` foldr op z []))
= a `op` (b `op` (c `op` z))
运算 “ :” 被 `op` 代替 , [] 被 z 代替 .
问题
解释下列表达式的语义foldr (:) [] xs
foldr (:) [] xs = xs
问题
解释foldr (:) ys xs
foldr (:) ys [a,b,c]
= foldr (:) ys (a:(b:(c:[])))
= (a:(b: (c: ys))
= [a,b,c] ++ ys xs++ys = foldr (:) ys xs
问题
解释 foldr snoc [] xs
where snoc y ys = ys++[y]
foldr snoc [] (a:(b:(c:[])))
= a `snoc` (b `snoc` (c `snoc` []))
= (([] ++ [c]) ++ [b] ++ [a]
结果是 xs的逆 !reverse xs = foldr snoc [] xs where snoc y ys = ys++[y]
表达式
表达式是函数的一种表示方法。例如 double x = 2*x
函数 double 可以表示为 double = x. 2*x
在 Haskell 中表示为 double = \x -> 2*x
当输入参数是 x 时,函数的结果是 2*x
例 max x y = if (x<y) then y else x使用 lambda 表达式 max = \x\y -> if (x<y) then y else x 或者 max = \x y -> if (x<y) then y lese x
一般地,下列定义等价:
f x y z = e
f = \x y z -> e
使用表达式定义函数
reverse xs = foldr snoc [] xs where snoc y ys = ys++[y]
可以直接使用 lambda表达式代替只出现一次的函数
reverse xs = foldr (\y ys -> ys++[y]) [] xs
定义函数 unlines
unlines [“abc”, “def”, “ghi”] = “abc\ndef\nghi\n”
unlines [xs,ys,zs] = xs ++ “\n” ++ (ys ++ “\n” ++ (zs ++ “\n” ++ []))
unlines xss = foldr (xs ys -> xs++“\n”++ys) [] xss
等价于
unlines xss = foldr join [] xss
where join xs ys = xs ++ “\n” ++ ys
又一个计算模式
在一个串中取出一个单词:takeWord “Hello, world!” = “Hello”
takeWord [] = []takeWord (x:xs) | x/=‘ ‘ && x/=‘,’ = x:takeWord xs
| otherwise = []
模式:
当某个条件不成立时取得列表元素,直至条件为True 。
推广 takeWord
takeWhile p [] = []takeWhile p (x:xs) | p x = x:takeWhile p xs
| otherwise = []
新定义 takeWord xs = takeWhile (x -> elem x “ ,.;”) xs
takeWord [] = []takeWord (x:xs) | notSpace x = x:takeWord xs
| otherwise = [] where notSpace x = elem x “ ,;.”
函数的表示法 : Sections
一个二元运算只带有一个参数时表示一个函数,称为部分运算( sections):
(+1) :: Int -> Int
(1+) :: Int -> Int
•map (+1) [1,2,3] = [2,3,4]
•filter (<0) [1,-2,3] = [-2]
•takeWhile (0<) [1,-2,3] = [1,3]
(a¤) b = a¤b(¤a) b = b¤a
部分应用
Haskell 允许如下定义sum = foldr (+) 0
foldr 只带有三个参数中的两个。
sum xs = foldr (+) 0 xs
sum = foldr (+) 0
计算 sum [1,2,3]
= {replacing sum by its definition}
foldr (+) 0 [1,2,3]
= {by the behaviour of foldr}
1 + (2 + (3 + 0))
= 6
foldr 有三个参数
部分应用
任何函数都可以应用于部分输入参数,结果是剩余参数的函数:
如果
f ::Int -> Bool -> Int -> Bool
则 f 3 :: Bool -> Int -> Bool
f 3 True :: Int -> Bool
f 3 True 4 :: Bool
一个函数得到一个输入后成为等待其
他输入的函数
函数应用和类型的括号结合方法
函数应用是左结合的,函数类型是右结合的:例如 f ::Int -> (Bool -> (Int -> Bool))
或者 f :: Int -> Bool -> Int -> Bool
则 f 3 :: Bool -> (Int -> Bool)
(f 3) True :: Int -> Bool
((f 3) True) 4 :: Bool
使用高阶函数
• 将问题分解成一系列能够使用高阶函数编程的步骤;• 逐渐将输入转换为期望的输出;• 将这些转换函数组合起来。
例:计算词数输入:
一个由许多词构成、表示文本的串。
例如“hello clouds \n hello sky”
输出:
一个文本中出现的所有词及其出现次数的列表,单词按照字典序排列。如
“clouds: 1\nhello: 2\nsky: 1”clouds: 1hello: 2sky: 1
第一步:将输入分解成词的列表
“hello clouds \n hello sky”
[“hello”, “clouds”, “hello”, “sky”]
words
第二步:排序
[“clouds”, “hello”, “hello”, “sky”]
sort
[“hello”, “clouds”, “hello”, “sky”]
第三步:将相同的词并入一组
[[“clouds”], [“hello”, “hello”], [“sky”]]
group
[“clouds”, “hello”, “hello”, “sky”]
第四步:计算每组词数
[(“clouds”,1), (“hello”, 2), (“sky”,1)]
map (ws -> (head ws, length ws))
[[“clouds”], [“hello”, “hello”], [“sky”]]
第五步:格式化每一组
[“clouds: 1”, “hello: 2”, “sky: 1”]
map ((w,n) -> w++show n)
[(“clouds”,1), (“hello”, 2), (“sky”,1)]
第六步:将列表格式化
“clouds: 1\nhello: 2\nsky: 1\n”
unlines
[“clouds: 1”, “hello: 2”, “sky: 1”]
clouds: 1hello: 2sky: 1
完整的程序
countWords :: String -> String
countWords =
unlines .
map ((w,n) -> w++show n) .
map (ws -> (head ws, length ws)) .
groupBy (==) .
sort .
words
函数 groupBy
group = groupBy (==)
groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy p xs -- 将 xs 分解成 段的列表 [x1,x2…], 其中每一段上的所有元素均满足某个性质
小结
• 高阶函数是计算模式的抽象。高阶函数的参数是函数。• 高阶函数使得许多函数的更简洁、灵活,而且大大减少了编程工作量。• 表达式、部分运算和部分应用可用于表示函数,作为参数时无需另行定义。• Haskell提供了许多高阶函数。• 解决问题的一般步骤:将问题分解成能够用现有函数解决的问题,然后将这些函数粘合起来。
• 习题: 9.9, 9.10, 9.14, 9.16, 10.4, 10.7, 10.9