在 Paul Graham 的 ANSI Common Lisp 中编写示例

发布于 2024-11-06 06:26:58 字数 583 浏览 5 评论 0原文

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

Can anybody explain an example in Paul Graham's ANSI Common Lisp page 110?

The example try to explain the use &rest and lambda to create functional programming facilities. One of them is a function to compose functional arguments. I cannot find anything explaining how it worked. The code is as follows:

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

The usage is:

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

The output is:

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

Line 2 and 6 look especially like magic to me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

太阳哥哥 2024-11-13 06:26:58

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

调用 (compose #'list #'round #'sqrt) 产生的闭包首先计算其参数的平方根,将结果四舍五入到最接近的整数,然后创建结果列表。以 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)))

The compose function returns a closure that calls each of the functions from last to first, passing on the result of each function call to the next.

The closure resulting from calling (compose #'list #'round #'sqrt) first calculates the square root of its argument, rounds the result to the nearest integer, then creates a list of the result. Calling the closure with say 3 as argument is equivalent to evaluating (list (round (sqrt 3))).

The destructuring-bind evaluates the (reverse fns) expression to get the arguments of compose in reverse order, and binds its first item of the resulting list to the fn1 local variable and the rest of the resulting list to the rest local variable. Hence fn1 holds the last item of fns, #'sqrt.

The reduce calls each the fns functions with the accumulated result. The :initial-value (apply fn1 args) provides the initial value to the reduce function and supports calling the closure with multiple arguments. Without the requirement of multiple arguments, compose can be simplified to:

(defun compose (&rest fns)
  #'(lambda (arg)
      (reduce #'(lambda (v f) (funcall f v))
              (reverse fns)
              :initial-value arg)))
故人爱我别走 2024-11-13 06:26:58

destructuring-bind 结合具有绑定的析构函数。析构函数是一个允许您访问数据结构的一部分的函数。 carcdr 是简单的析构函数,用于提取列表的头部和尾部。 getf 是一个通用的析构函数框架。绑定最常由 let 执行。在此示例中,fns(#'list #'round #'sqrt)compose 的参数),因此 (反向 fns)(#'sqrt #'round #'list)。 当然, Then

(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< /a> 函数将 fn1 应用于参数;由于此参数不是列表,因此它只是 (#'sqrt 4),即 2。换句话说,我们

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

现在有了 reduce< /a> 函数完成其工作,即依次将 #'(lambda (vf) (funcall fv)) 应用于 #'round#'list,从2 开始。这相当于

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

destructuring-bind combines destructors with binding. A destructor is a function that lets you access a part of a data structure. car and cdr are simple destructors to extract the head and tail of a list. getf is a general destructor framework. Binding is most commonly performed by let. In this example, fns is (#'list #'round #'sqrt) (the arguments to compose), so (reverse fns) is (#'sqrt #'round #'list). Then

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

is equivalent to

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

except that it doesn't bind tmp, of course. The idea of destructuring-bind is that it's a pattern matching construct: its first argument is a pattern that the data must match, and symbols in the pattern are bound to the corresponding pieces of the data.

So now fn1 is #'sqrt and rest is (#'round #'list). The compose function returns a function: (lambda (&rest args) ...). Now consider what happens when you apply that function to some argument such as 4. The lambda can be applied, yielding

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

The apply function applies fn1 to the argument; since this argument is not a list, this is just (#'sqrt 4) which is 2. In other words, we have

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

Now the reduce function does its job, which is to apply #'(lambda (v f) (funcall f v)) successively to the #'round and to #'list, starting with 2. This is equivalent to

(funcall #'list (funcall #'round 2))
→ (#'list (#'round 2))
→ '(2)
鹿港小镇 2024-11-13 06:26:58

好的,这里是:

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

Okay, here goes:

  1. It takes the functions given, reverses it (in your example, it becomes (#'sqrt #'round #'list)), then sticks the first item into fn1, and the rest into rest. We have: fn1 = #'sqrt, and rest = (#'round #'list).
  2. Then it performs a fold, using (apply sqrt args) (where args are the values given to the resulting lambda) as the initial value, and with each iteration grabbing the next function from rest to call.
    1. For the first iteration you end up with (round (apply sqrt args)), and the second iteration you end up with (list (round (apply sqrt args))).
  3. Interestingly, only the initial function (sqrt in your case) is allowed to take multiple arguments. The rest of the functions are called with single arguments only, even if any particular function in the chain does a multiple-value return.
往事随风而去 2024-11-13 06:26:58

这个例子难倒了我一天。我终于可以通过重命名一些参数并在每一行有意义之前对其进行注释来理解它。以下是帮助我向自己解释的内容。

在书中使用调用的示例中:

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

参数functions变为(#'LIST #'ROUND #'SQRT)

(defun compose (&rest functions)
  (destructuring-bind (fx . fxs) (reverse functions)
    ;; fx becomes #'SQRT
    ;; fxs becomes '(#'ROUND #'LIST)
    #'(lambda (&rest args) ; This is the function returned as result.
    ;; The args parameter will be (4) on the mapcar's first
    ;; iteration on the (4 9 16 25) list passed in the call:
    ;; (mapcar #'(compose #'List #'round #'sqrt) '(4 9 16 25)) => ((2) (3) (4) (5))
    ;; or e.g. the (4) in (funcall (compose #'list #'sqrt '(4)) => (2.0) 
    ;; Note that args is not ((#'ROUND #'LIST)).
    (reduce #'(lambda (x y) (funcall y x))
        ;; fxs is (#'ROUND #'LIST) - captuted as closure since it is now
        ;; locally unbound.
        fxs
        ;; Initial value is: (apply #'SQRT '(4) => 2.0.
        ;; In Paul Graham's example, the mapcar passes
        ;; each square number individually.
        ;; The reverse order of parameters in the second lambda
        ;; first invokes: (ROUND 2.0) => 2
        ;; and then invokes: (LIST 2) => (2)
        :initial-value (apply fx args)))))

This example stumped me for a day. I could finally understand it by renaming some of the arguments and commenting each line before it made sense. Below is what helped me explain it to myself.

In the book example using the call:

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

The parameter functions becomes (#'LIST #'ROUND #'SQRT)

(defun compose (&rest functions)
  (destructuring-bind (fx . fxs) (reverse functions)
    ;; fx becomes #'SQRT
    ;; fxs becomes '(#'ROUND #'LIST)
    #'(lambda (&rest args) ; This is the function returned as result.
    ;; The args parameter will be (4) on the mapcar's first
    ;; iteration on the (4 9 16 25) list passed in the call:
    ;; (mapcar #'(compose #'List #'round #'sqrt) '(4 9 16 25)) => ((2) (3) (4) (5))
    ;; or e.g. the (4) in (funcall (compose #'list #'sqrt '(4)) => (2.0) 
    ;; Note that args is not ((#'ROUND #'LIST)).
    (reduce #'(lambda (x y) (funcall y x))
        ;; fxs is (#'ROUND #'LIST) - captuted as closure since it is now
        ;; locally unbound.
        fxs
        ;; Initial value is: (apply #'SQRT '(4) => 2.0.
        ;; In Paul Graham's example, the mapcar passes
        ;; each square number individually.
        ;; The reverse order of parameters in the second lambda
        ;; first invokes: (ROUND 2.0) => 2
        ;; and then invokes: (LIST 2) => (2)
        :initial-value (apply fx args)))))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文