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

sun*_*nwc 13 lisp reduce closures common-lisp paul-graham

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

该示例尝试解释use&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)))))
Run Code Online (Sandbox Code Playgroud)

用法是:

(mapcar (compose #'list #'round #'sqrt)
        '(4 9 16 25))
Run Code Online (Sandbox Code Playgroud)

输出是:

((2) (3) (4) (5))
Run Code Online (Sandbox Code Playgroud)

2号线和6号线对我来说看起来特别神奇.任何意见将不胜感激.

Ter*_*aug 9

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

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

解构绑定评估(reverse fns)表达式获得的参数compose以相反的顺序,并结合所产生的列表中的其第一项的fn1的局部变量和结果列表的所述其余部分其余部分局部变量.因此,FN1持有的最后一个项目FNS,#'sqrt.

减少每个调用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)))
Run Code Online (Sandbox Code Playgroud)


Gil*_*il' 7

destructuring-bind结合了析构函数和绑定.析构函数是一种允许您访问数据结构的一部分的函数.car并且cdr是简单的析构函数来提取列表的头部和尾部.getf是一个通用的析构函数框架.绑定通常由以下方式执行let.在这个例子中,fns(#'list #'round #'sqrt)(的参数compose),因此(reverse fns)(#'sqrt #'round #'list).然后

(destructuring-bind (fn1 . rest) '(#'sqrt #'round #'list)
  ...)
Run Code Online (Sandbox Code Playgroud)

相当于

(let ((tmp '(#'sqrt #'round #'list)))
  (let ((fn1 (car tmp))
        (rest (cdr tmp)))
    ...))
Run Code Online (Sandbox Code Playgroud)

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)))
Run Code Online (Sandbox Code Playgroud)

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

(reduce #'(lambda (v f) (funcall f v))
            '(#'round #'list)
            :initial-value 2)
Run Code Online (Sandbox Code Playgroud)

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

(funcall #'list (funcall #'round 2))
? (#'list (#'round 2))
? '(2)
Run Code Online (Sandbox Code Playgroud)


Chr*_*ung 5

好的,这里是:

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