以下是 Rich Hickey 在博客中的一段话,但我不明白使用 apply 的动机。请帮忙解释。
Clojure 和 CL 之间的一个重要区别是 Clojure 是 Lisp-1,因此不需要 funcall,而只有在将函数应用于运行时定义的参数集合时才使用 apply。因此,(apply f [i]) 可以写作 (f i)。
此外,他所说的 "Clojure 是 Lisp-1" 和不需要 funcall 是什么意思?我从未在 CL 中编程过。
谢谢
以下是 Rich Hickey 在博客中的一段话,但我不明白使用 apply 的动机。请帮忙解释。
Clojure 和 CL 之间的一个重要区别是 Clojure 是 Lisp-1,因此不需要 funcall,而只有在将函数应用于运行时定义的参数集合时才使用 apply。因此,(apply f [i]) 可以写作 (f i)。
此外,他所说的 "Clojure 是 Lisp-1" 和不需要 funcall 是什么意思?我从未在 CL 中编程过。
谢谢
如果要传递给函数的参数数量在编译时不确定(对不起,我不太熟悉Clojure语法,转而使用Scheme),则可以使用apply:
(define (call-other-1 func arg) (func arg))
(define (call-other-2 func arg1 arg2) (func arg1 arg2))
只要在编译时知道参数的数量,就可以像上面的示例一样直接传递它们。但是,如果在编译时不知道参数的数量,则无法这样做(好吧,你可以尝试类似以下的东西):(define (call-other-n func . args)
(case (length args)
((0) (other))
((1) (other (car args)))
((2) (other (car args) (cadr args)))
...))
但很快这就变成了一个噩梦。这时,apply 就出现了:
(define (call-other-n func . args)
(apply other args))
它将获取作为最后一个参数给出的列表中包含的任意数量的参数,并使用这些值调用作为第一个参数传递给 apply 的函数。
Lisp-1和Lisp-2这两个术语指的是函数是否与变量在同一个命名空间中。
在Lisp-2(也就是2个命名空间)中,表单中的第一个项目将被作为函数名进行评估,即使它实际上是具有函数值的变量的名称。因此,如果要调用变量函数,您必须将变量传递给另一个函数。
在Lisp-1中,例如Scheme和Clojure,评估为函数的变量可以放在初始位置,因此您无需使用apply
来将其评估为函数。
apply
基本上是将一个序列解包,并将其作为单独的参数传递给函数。
以下是一个示例:
(apply + [1 2 3 4 5])
这将返回15。它基本上展开为(+ 1 2 3 4 5)
,而不是(+ [1 2 3 4 5])
。
使用 apply
将一个作用于多个参数的函数转化为作用于单一参数序列的函数。你也可以在序列之前插入参数。例如,map
可以作用于多个序列。这个示例(来自ClojureDocs)使用 map
来转置矩阵。
user=> (apply map vector [[:a :b] [:c :d]])
([:a :c] [:b :d])
vector
。因此,apply
扩展为
user=> (map vector [:a :b] [:c :d])
太可爱了!
PS: 如果要返回一个向量的向量而不是一个向量序列,请将整个内容包装在vec
中:
user=> (vec (apply map vector [[:a :b] [:c :d]]))
在这个语境下,vec
可以被定义为(partial apply vector)
,但事实上它并没有被定义成这样。
关于Lisp-1和Lisp-2:数字1和2表示在给定上下文中名称可以表示的事物数量。在Lisp-2中,你可以有两个不同的事物(一个函数和一个变量)具有相同的名称。因此,在任何一个可能有效的地方,您需要用某些东西装饰您的程序来指示您的意思。幸运的是,Clojure(或Scheme…)允许名称仅表示一个事物,因此无需进行任何装饰。
apply在协议中非常有用,特别是与线程宏一起使用。我刚刚发现了这一点。由于您无法在编译时使用&宏来扩展接口参数, 因此可以使用大小不可预测的向量。
例如,我将其用作记录和某个特定xml文件本身之间的接口的一部分,记录包含有关该文件的一些元数据。
(query-tree [this forms]
(apply xml-> (text-id-to-tree this) forms)))
text-id-to-tree
是这个特定记录的另一种方法,它将文件解析为 XML 拉链。在另一个文件中,我使用特定查询扩展了协议,实现了 query-tree
,指定要通过 xml-> 宏线程化的命令链:
(tags-with-attrs [this]
(query-tree this [zf/descendants zip/node (fn [node] [(map #(% node) [:tag :attrs])])])
(注意:这个查询本身会返回很多没有属性的标签的“nil”结果。使用过滤和缩减功能可以得到一个干净的唯一值列表)。
顺便提一下,zf是指clojure.contrib.zip-filter,zip是指clojure.zip。xml->宏来自clojure.contrib.zip-filter.xml库,我使用了:use
命令。