高阶函数的意义在于减少代码中的样板,并减少循环(技术上是递归,但它的目的是循环,所以我将其称为循环)和循环之间的耦合实际逻辑。这是一个示例(重新抓取所有奇数):手动循环如下所示:
(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 和filter,右折叠是必要的(否则元素会颠倒——尝试在下面的函数中用foldl 替换foldr,你会看到):
(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))