任何人都可以在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行对我来说看起来特别神奇。任何意见将不胜感激。
答案 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
将析构函数与绑定相结合。析构函数是一种允许您访问数据结构的一部分的函数。 car
和cdr
是简单的析构函数,用于提取列表的头部和尾部。 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
为#'sqrt
,rest
为(#'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
连续应用于#'list
和2
。这相当于
(funcall #'list (funcall #'round 2))
→ (#'list (#'round 2))
→ '(2)
答案 2 :(得分:5)
好的,这里有:
(#'sqrt #'round #'list)
),然后将第一个项目放入fn1
,其余项目放入rest
。我们有fn1
= #'sqrt
和rest
= (#'round #'list)
。(apply sqrt args)
(其中args
是给出的结果lambda的值)作为初始值,并且每次迭代都从rest
抓取下一个函数打电话。
(round (apply sqrt args))
,第二次迭代最终获得(list (round (apply sqrt args)))
。sqrt
)采用多个参数。其余函数仅使用单个参数调用,即使链中的任何特定函数执行多值返回也是如此。