跳转到内容

高阶函数

来自维基教科书,开放的书籍,面向开放的世界

试卷 2 - ⇑ 函数式编程基础 ⇑

← 列表处理 高阶函数



高阶函数 - 一个函数,它将函数作为参数或返回一个函数作为结果,或者两者都做


高阶函数 是将一个或多个函数作为参数或返回一个函数作为结果的函数。 三个常见的高阶函数是 map、filter 和 reduce/fold 函数

扩展:函子与非列表映射

虽然这里面的例子是关于使用 map、filter 和 reduce/fold 函数处理列表,但其他数据类型可以用来代替列表。例如,你可以将一个函数映射到树的元素上。在 Haskell 中,任何 函子 类型类的东西都可以用于 map 函数中。

map 函数

[编辑 | 编辑源代码]
map 函数 - 一个高阶函数,其中一个给定的函数被应用于一个给定列表的每个元素,从而产生一个新的输出列表


map 函数被赋予一个列表和一个要应用于该列表的函数。 它将这个给定的函数应用于给定列表中的每个元素,创建一个新的列表,然后返回该列表。 另一种定义 map 的方法是,它将给定列表的每个项映射到输出列表

.> map (+10) [1,2,3,4,5]
[11, 12, 13, 14, 15]
.> map (^2) [20, 3, 51, 6, 3, 7]
[400,9,2601,36,9,49]

.> map (++ "!") ["scary", "weird", "spooky", "kooky"]
["scary!", "weird!", "spooky!", "kooky!"]
.> map ("NOT " ++) ["scary", "weird", "spooky", "kooky"]
["NOT scary", "NOT weird", "NOT spooky", "NOT kooky"]

.> map (replicate 2) ['a', 'b', 'c']
["aa","bb","cc"]
.> map (replicate 2) ["a", "b", "c"]
[["a","a"],["b","b"],["c","c"]]

map 也可以作用于一个列表的列表,其中被映射的函数依次被应用于每个子列表。 例如,如果你想从一组列表中找到最小值,你可以写

.> map minimum [[1, 3], [2, 7, 5],[9, 6]]
[1,2,6]

这里要注意,我们似乎得到一个比开始时更小的列表。 虽然就每个现在只有一个项目的内部列表的长度而言这是真的,但传递给 map 函数的原始列表有三个项目(列表 [1, 3][2, 7, 5][9, 6]),返回的列表也有三个项目([1,2,6])。

filter 函数

[编辑 | 编辑源代码]
filter 函数 - 一个高阶函数,它返回一个给定列表中满足给定条件的元素


filter 函数被传递一个列表和过滤条件,它将过滤条件应用于列表,返回一个匹配过滤条件的项目列表。 过滤条件有时被称为 谓词函数,其中将 TRUE 或 FALSE 返回从将条件应用于原始列表的每个元素。 在 Haskell 中,这是使用 filter 命令执行的,例如

.> let list1 = [1,2,3,4,5,6,7,8,9]
.> filter (>5) list1

这查看 list1 中的每个项目,并检查它们是否大于 5。 将那些大于 5 的设置为True,而那些小于 5 的设置为False

[1,2,3,4,5,6,7,8,9]
[F,F,F,F,F,T,T,T,T]

它返回所有为True 的项目列表

[6,7,8,9]

使用 oddeven 命令的其他 filter 示例

.> let list1 = [1,2,3,4,5,6,7,8,9]
.> let list2 = filter even list1 
.> list2
[2, 4, 6, 8]
.> filter odd list1 
[1, 3, 5, 7, 9]
.> filter odd list2
[]

其他编程语言可能没有 filter 命令,但可能通过使用 select caseswitchif then else 来实现类似的输出。

reduce 或 fold 函数

[编辑 | 编辑源代码]
reduce/fold 函数 - 一个高阶函数,它递归地将一个函数应用于一个列表的元素,直到该列表为空


reduce 或 fold 函数作为输入接受一个列表、一个要应用于列表的函数和一个起始值。 有两种类型的 fold 命令,foldlfoldr,这里面的例子使用的是 foldl

fold 将给定的函数应用于列表的第一个元素(从左到右开始)和起始值,然后将 fold 命令应用于此的结果和列表的其余部分。 它递归地执行此操作,直到 fold 命令被应用于一个空列表,此时它返回计算出的值。 另一种描述方式,来自 Haskell wiki

-- [] represents the empty list, 
-- and (x:xs) represents the list starting with x and where the rest of the list is xs. 
-- if the list is empty, the result is the initial value; else
-- we recurse immediately, making the new initial value the result
-- of combining the old initial value with the first element.
rule 1. foldl f z []     = z 
rule 2. foldl f z (x:xs) = foldl f (f z x) xs

为了看到它的实际效果,请查看 haskell 命令 foldl (*) 1 [1,2,3,4],此代码将递归地将列表中的元素乘在一起

foldl (*) 1 [1,2,3,4]

这与规则 2. 匹配。 f = *,z = 1,x = 1,xs = [2,3,4]。 由于命令右侧的列表 xs 不是空的,因此我们将执行以下操作

foldl f z (x:xs) = foldl f (f z x) xs

这将进行下一次调用

foldl (*) (* 1 1) [2,3,4]

列表 xs 不是空的,因此我们将再次应用规则 2.,其中 f = *,z = * 1 1,x = 2,xs = [3,4]。 下一次调用是

foldl (*) (* (* 1 1) 2) [3,4]

列表 xs 不是空的,因此我们将再次应用规则 2.,其中 f = *,z = * (* 1 1) 2,x = 3,xs = [4]。 下一次调用是

foldl (*) (* (* (* 1 1) 2) 3) [4]

列表 xs 仍然不是空的,因此我们将再次应用规则 2.,其中 f = *,z = * (* (* 1 1) 2) 3,x = 4,xs = []。 因此,下一次 foldl 调用是

foldl (*) (* (* (* (* 1 1) 2) 3) 4) []

列表 xs 现在是空列表 []。 这与规则 1. 匹配,并返回 z

(* (* (* (* 1 1) 2) 3) 4)

我们可以将 波兰表示法(例如 * 1 1)改写为 中缀表示法(例如 1 * 1),并计算出括号,从最里面的括号开始

((((1*1)*2)*3)*4)
(((1*2)*3)*4)
((2*3)*4)
(6*4)
24

使用中缀表示法的更简洁的加总示例

foldl (+) 0 [1,2,3,4]
foldl (+) (0+1) [2,3,4]
foldl (+) ((0+1)+2) [3,4]
foldl (+) (((0+1)+2)+3) [4]
foldl (+) ((((0+1)+2)+3)+4) []
((((0+1)+2)+3)+4)
((((1)+2)+3)+4)
(((3)+3)+4)
((6)+4)
10


考试问题

什么是高阶函数?

答案

一个函数,它将函数作为参数或返回一个函数作为结果,或者两者都做


Haskell 命令 filter (>5) [32, 32, 1, 3, 5, 31, 3, 5, 212] 的输出是什么?

答案

[32, 32, 31, 212]

Haskell 命令 filter (odd) [32, 32, 1, 3, 5, 31, 3, 5, 212] 的输出是什么?

答案

[1, 3, 5, 31, 3, 5]


Haskell 命令 map ('+' :) ["good", "excellent", "brill"] 的输出是什么?

答案

["+good","+excellent","+brilliant"] 注意:在 '+' 运算符上使用单引号意味着我们可以使用前置命令。 它将 '+' 视为单个项目。 此代码不能与 "+" 一起使用,因为它将 "+" 视为一个列表,而前置命令无法处理列表作为第一个项目。


Haskell 命令 map (+5) (filter (<30) [29, 31, 30]) 的输出是什么?

答案

[34]


foldl (+) 6 [9, 2, 13] 的结果是什么?

答案

30


foldl (*) 1 [2, 3, 4] 的结果是什么?

答案

24


foldl (^) 1 [2, 3, 4] 的结果是什么?

答案

1

foldl (^) 2 [2, 1, 2, 1] 的结果是什么?

答案

16


写下执行 foldl (^) 2 [3, 2, 1] 所涉及的所有 foldl 函数调用

答案

foldl (^) 2 [3, 2, 1]
foldl (^) (2^3) [2, 1]
foldl (^) ((2^3)^2) [1]
foldl (^) (((2^3)^2)^1) []
(((2^3)^2)^1)
(((8)^2)^1)
((64)^1)
64


描述一等公民和高阶函数之间的区别

答案

一等公民是一个可以作为参数传递或由函数返回的对象。 高阶函数可以接受另一个函数作为参数。


华夏公益教科书