保罗格雷厄姆的ANSI Common Lisp中的组合示例

时间:2011-05-08 14:45:50

标签: lisp closures common-lisp reduce paul-graham

任何人都可以在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行对我来说看起来特别神奇。任何意见将不胜感激。

3 个答案:

答案 0 :(得分:9)

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

调用(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)))

答案 1 :(得分:7)

destructuring-bind将析构函数与绑定相结合。析构函数是一种允许您访问数据结构的一部分的函数。 carcdr是简单的析构函数,用于提取列表的头部和尾部。 getf是一个通用的析构函数框架。绑定通常由let执行。在此示例中,fns(#'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)

答案 2 :(得分: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)采用多个参数。其余函数仅使用单个参数调用,即使链中的任何特定函数执行多值返回也是如此。