13

任何人都可以解释 Paul Graham 的 ANSI Common Lisp 第 110 页中的示例吗?

该示例试图解释使用 &rest 和 lambda 创建函数式编程工具。其中之一是组成函数参数的函数。我找不到任何解释它是如何工作的。代码如下:

(defun compose (&rest fns)
  (destructuring-bind (fn1 . rest) (reverse fns)
    #'(lambda (&rest args)
        (reduce #'(lambda (v f) (funcall f v))
                rest
                :initial-value (apply fn1 args)))))

用法是:

(mapcar (compose #'list #'round #'sqrt)
        '(4 9 16 25))

输出是:

((2) (3) (4) (5))

第 2 行和第 6 行对我来说特别神奇。

4

3 回答 3

10

compose函数返回一个闭包,该闭包从最后一个到第一个调用每个函数,并将每个函数调用的结果传递给下一个函数。

调用产生的闭包(compose #'list #'round #'sqrt)首先计算其参数的平方根,将结果四舍五入为最接近的整数,然后创建结果列表。以 say 3 作为参数调用闭包等效于评估(list (round (sqrt 3)))

destructuring-bind对表达式求值以以相反的顺序(reverse fns)获取参数compose,并将结果列表的第一项绑定到fn1局部变量,并将结果列表的其余部分绑定到rest局部变量。因此fn1保存fns的最后一项,#'sqrt

reduce使用fns累积的结果调用每个函数。为函数:initial-value (apply fn1 args)提供初始值reduce并支持使用多个参数调用闭包。在不需要多个参数的情况下,compose可以简化为:

(defun compose (&rest fns)
  #'(lambda (arg)
      (reduce #'(lambda (v f) (funcall f v))
              (reverse fns)
              :initial-value arg)))
于 2011-05-08T15:07:27.180 回答
7

destructuring-bind将析构函数与绑定相结合。析构函数是一个函数,可让您访问数据结构的一部分。car并且cdr是提取列表头部和尾部的简单析构函数。getf是一个通用的析构框架。绑定最常由 执行let。在这个例子中,fnsis (#'list #'round #'sqrt)(的参数compose),所以(reverse fns)(#'sqrt #'round #'list)。然后

(destructuring-bind (fn1 . rest) '(#'sqrt #'round #'list)
  ...)

相当于

(let ((tmp '(#'sqrt #'round #'list)))
  (let ((fn1 (car tmp))
        (rest (cdr tmp)))
    ...))

当然,除了它不绑定tmp。的想法destructuring-bind是它是一个模式匹配构造:它的第一个参数是数据必须匹配的模式,并且模式中的符号绑定到相应的数据片段。

所以现在fn1#'sqrtrest(#'round #'list)。该compose函数返回一个函数:(lambda (&rest args) ...). 现在考虑当您将该函数应用于某些参数时会发生什么,例如4. 可以应用 lambda,产生

(reduce #'(lambda (v f) (funcall f v))
            '(#'round #'list)
            :initial-value (apply #'sqrt 4)))

apply函数适用fn1于参数;因为这个参数不是一个列表,所以(#'sqrt 4)这就是2. 换句话说,我们有

(reduce #'(lambda (v f) (funcall f v))
            '(#'round #'list)
            :initial-value 2)

现在该reduce函数完成了它的工作,即从 开始#'(lambda (v f) (funcall f v))依次应用于#'round和。这相当于#'list2

(funcall #'list (funcall #'round 2))
→ (#'list (#'round 2))
→ '(2)
于 2011-05-08T15:27:18.960 回答
5

好的,这里开始:

  1. 它采用给定的函数,将其反转(在您的示例中,它变为(#'sqrt #'round #'list)),然后将第一项粘贴到fn1中,其余的粘贴到rest. 我们有:fn1=#'sqrtrest= (#'round #'list)
  2. 然后它执行折叠,使用(apply sqrt args)args给定结果 lambda 的值在哪里)作为初始值,并且每次迭代都会抓取下一个rest要调用的函数。
    1. 对于第一次迭代,你最终得到(round (apply sqrt args)),第二次迭代你最终得到(list (round (apply sqrt args)))
  3. 有趣的是,仅sqrt允许初始函数(在您的情况下)采用多个参数。其余函数仅使用单个参数调用,即使链中的任何特定函数执行多值返回。
于 2011-05-08T15:13:52.443 回答