400 likes | 512 Views
高阶函数 Higher Order Functions. 阅读第九章、第十章. 计算模式的推广. 我们经常需要对列表的元素进行某种统一的操作 : doubleAll :: [Int] -> [Int] doubleAll [] = [] doubleAll (x:xs) = 2*x : doubleAll xs 或者 doubleAll xs = [2*x | x<- xs]. even :: Int -> Bool even n = n`mod` 2 == 0. 又如 : is Even xs = [even x | x <- xs]. 高阶函数.
E N D
高阶函数Higher Order Functions 阅读第九章、第十章
计算模式的推广 我们经常需要对列表的元素进行某种统一的操作: doubleAll :: [Int] -> [Int] doubleAll [] = [] doubleAll (x:xs) = 2*x : doubleAll xs 或者 doubleAll xs = [2*x | x<- xs] even :: Int -> Bool even n = n`mod` 2 == 0 又如: isEven xs = [even x | x <- xs]
高阶函数 一般的计算模式: [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] map :: (Int -> Bool) -> [Int] -> [Bool] map double [1,2,3,4,5] = [2,4,6,8,10] map :: (Int -> Int) -> [Int] -> [Int] map的类型是多态的 map :: (a -> b) -> [a] -> [b] 结果是b上的列表 任意一元函数 任何a上的列表
使用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] = “151986” isDigit :: Char -> Bool 一般地,我们定义如下过滤模式 filter p xs = [x | x <- xs, p x]
Filter的类型是什么? filter even [1, 2, 3, 4, 5] = [2, 4] even :: Int -> Bool filter :: (Int -> Bool) -> [Int] -> [Int] filter :: (a -> Bool) -> [a] -> [a] 一个表示性质的函数类型
为什么使用高阶函数? • 高阶函数是函数程序设计的灵魂。 • 许多相似的函数都可以使用一个高阶函数代替。 • 高阶函数比一阶函数更具有表现力,我们可以通过使用不同的函数参数解决不同的问题。 • 每当多个函数具有相似的模式时,定义一个高阶函数。
折叠的例子 sum [] = 0 sum (x:xs) = x + sum xs and [] = True and (x:xs) = x && (and xs) and [True, False, True] = False sum [2,4,6] = 12 计算模式:使用同一个运算将列表的元素结合起来。 具体到sum: 起始值0, 运算 + and: 起始值True, 运算 &&
折叠函数 sum [] = 0 sum (x:xs) = x + sum xs 将0和+分别用参数代替, 定义一个高阶函数: foldr op z [] = z foldr op z (x:xs) = x `op` foldr op z xs sum xs = foldr plus 0 xs where plus x y = x+y
使用foldr定义的sum sum xs = foldr plus 0 xs where plus x y = x+y 或者 sum xs = foldr (+) 0 xs
使用foldr的函数定义 将列表元素使用某种运算结合起来是一种常见运算,这种运算均可以使用foldr定义,而不必使用递归。 product xs = foldr (*) 1 xs concat xs = foldr (++) [] xs maximum (x:xs) = foldr max x xs
函数foldr的另一种解读 foldr op z [] = z foldr 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 当输入参数是x时,函数的结果是2*x 在Haskell中表示为 double = \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 takeWord [] = [] takeWord (x:xs) | notSpace x = x:takeWord xs | otherwise = [] where notSpace x = elem x “ ,;.” takeWhile p [] = [] takeWhile p (x:xs) | p x = x:takeWhile p xs | otherwise = [] 新定义 takeWord xs = takeWhile (x -> elem x “ ,.;”) xs
函数的表示法: 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
部分应用 sum xs = foldr (+) 0 xs Haskell 允许如下定义 sum = foldr (+) 0 foldr 只带有三个参数中的 两个。
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: 1 hello: 2 sky: 1
第一步:将输入分解成词的列表 “hello clouds \n hello sky” words [“hello”, “clouds”, “hello”, “sky”]
第二步:排序 [“hello”, “clouds”, “hello”, “sky”] sort [“clouds”, “hello”, “hello”, “sky”]
第三步:将相同的词并入一组 [“clouds”, “hello”, “hello”, “sky”] group [[“clouds”], [“hello”, “hello”], [“sky”]]
第四步:计算每组词数 [[“clouds”], [“hello”, “hello”], [“sky”]] map (ws -> (head ws, length ws)) [(“clouds”,1), (“hello”, 2), (“sky”,1)]
第五步:格式化每一组 [(“clouds”,1), (“hello”, 2), (“sky”,1)] map ((w,n) -> w++show n) [“clouds: 1”, “hello: 2”, “sky: 1”]
第六步:将列表格式化 [“clouds: 1”, “hello: 2”, “sky: 1”] unlines “clouds: 1\nhello: 2\nsky: 1\n” clouds: 1 hello: 2 sky: 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提供了许多高阶函数。 • 解决问题的一般步骤:将问题分解成能够用现有函数解决的问题,然后将这些函数粘合起来。