【问题标题】:Compose example in Paul Graham's ANSI Common Lisp在 Paul Graham 的 ANSI Common Lisp 中编写示例
【发布时间】:2011-05-08 14:45:49
【问题描述】:

谁能解释 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 行对我来说特别神奇。

【问题讨论】:

    标签: closures lisp common-lisp reduce


    【解决方案1】:

    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)))
    

    【讨论】:

    • (reduce ... (reverse fns) ... 应该写成(reduce ... fns :from-end t ...
    • :from-end t 变成简单的(reduce 'funcall fns :from-end t :initial-value arg)
    【解决方案2】:

    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)))
        ...))
    

    当然,它不绑定tmpdestructuring-bind 的思想是它是一个模式匹配结构:它的第一个参数是数据必须匹配的模式,模式中的符号绑定到相应的数据片段。

    所以现在fn1#'sqrtrest(#'round #'list)compose 函数返回一个函数:(lambda (&rest args) ...)。现在考虑将 that 函数应用于某个参数(例如 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#'list,从2 开始。这相当于

    (funcall #'list (funcall #'round 2))
    → (#'list (#'round 2))
    → '(2)
    

    【讨论】:

      【解决方案3】:

      好的,开始吧:

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

      【讨论】:

      • 所以,我认为超级创意 :initial-value 让我很困惑 :) 不过非常好。谢谢。
      猜你喜欢
      • 2020-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-18
      • 1970-01-01
      • 2019-11-16
      • 2011-02-04
      • 2011-03-08
      相关资源
      最近更新 更多