高阶函数的目的是减少代码中的样板,并减少循环(技术上它是递归,但它的目的是循环,所以我将其称为循环)和实际逻辑之间的耦合。这是一个示例(重新抓取所有奇数):手动循环如下所示:
(define (filter-odd lst)
(cond ((null? lst) '())
((odd? (car lst)) (cons (car lst) (filter-odd (cdr lst))))
(else (filter-odd (cdr lst)))))
但是请注意,您已经在一个函数中获得了循环和过滤。这使得弄清楚函数在做什么变得更加困难,因为这两个不相关的操作是耦合在一起的。使用更高级别的函数,您可以做不同的事情:
(define (filter pred lst)
(cond ((null? lst) '())
((pred (car lst)) (cons (car lst) (filter pred (cdr lst))))
(else (filter pred (cdr lst)))))
(define (filter-odd lst)
(filter odd? lst))
现在注意,如何odd?
从循环逻辑中分离出来,现在已经分离成filter
? filter
现在接受一个函数对象,它决定是否保留该项目,并且调用者filter
可以插入他们选择的任何函数。
这就是高阶函数的含义:它是一个以函数对象为参数的函数,用于自定义其操作。
正如您问题的原始编辑中提到的,map
是另一个高阶函数,但它不是从列表中过滤项目,而是返回原始列表中每个项目的转换,其中特定转换由map
调用者通过范围。
要回答您关于展平等的实际问题,(map filter-odd '((1 3 (9 5 7))))
将返回一个包含单个项目的列表,即调用的结果(filter-odd '(1 3 (9 5 7)))
。所以不,map
不会为您递归到子列表中(也不会filter
)。
但是您可以先展平输入(因为无论如何您都在展平输出),然后filter-odd
直接调用它。我希望这会给您带来您期望的结果。
(我将你重命名odd
为,因为它不太可能与(谓词)filter-odd
混淆。)odd?
奖金材料
顺便说一句,两者filter
和map
都是更通用的高阶函数的特化,称为折叠(或更具体地说,右折叠)。filter
折叠可以表达任何一个或都无法容纳的东西map
,但它以某种方式涉及遍历列表中的所有项目。这是一个length
函数的示例,表示为折叠:
(define (foldl func init lst)
(if (null? lst) init
(foldl func (func (car lst) init) (cdr lst))))
(define (length lst)
(foldl (lambda (elem count)
(+ count 1))
0 lst))
这里的好处是该length
函数不必担心遍历列表:这是由折叠处理的。它只需要担心每次迭代要做什么(这里只是简单地将 1 加到count
0 开始)。
在这种情况下,无论我们从左侧遍历还是从右侧遍历,长度都是相同的,而在 Scheme 中,从左侧遍历更节省空间,因此我们更喜欢这样。但是为了实现map
and filter
,右折叠是必要的(否则元素会颠倒 - 尝试在下面的函数中替换foldr
with foldl
,你会看到):
(define (foldr func init lst)
(if (null? lst) init
(func (car lst) (foldr func init (cdr lst)))))
(define (map func lst)
(foldr (lambda (elem result)
(cons (func elem) result))
'() lst))
(define (filter pred lst)
(foldr (lambda (elem result)
(if (pred elem)
(cons elem result)
result))
'() lst))