为什么许多Clojure函数是可变的?
我在Clojure经常遇到这样一个问题:为什么许多Clojure函数是可变的?,clojure,variadic-functions,Clojure,Variadic Functions,我在Clojure经常遇到这样一个问题: user=> (max [3 4 5 6 7]) [3 4 5 6 7] ; expected '7' 有些函数不符合我的期望 这里有一个使用apply的解决方案: user=> (apply max [3 4 5 6 7]) 7 其他示例包括concat和min 作为Clojure新手,我的问题是,为什么这些函数是可变的?我希望他们能按顺序操作。使用apply是获得我想要的东西的最佳/惯用方法吗 注意:我不是想说使用可变函数不好,或者
user=> (max [3 4 5 6 7])
[3 4 5 6 7] ; expected '7'
有些函数不符合我的期望这里有一个使用
apply
的解决方案:
user=> (apply max [3 4 5 6 7])
7
其他示例包括concat
和min
作为Clojure新手,我的问题是,为什么这些函数是可变的?我希望他们能按顺序操作。使用
apply
是获得我想要的东西的最佳/惯用方法吗
注意:我不是想说使用可变函数不好,或者有更好的方法。我只是想知道是否有一个规则或惯例正在被遵循,或者这种方法是否有我应该知道的特定优势
编辑:我认为原来的问题不清楚。我的意思是: 在我使用过的其他编程语言中,有一些类似于的操作,例如添加数字、查找较大的元素、连接列表 这些操作通常有两个用例: 1) 使用接受两个参数的函数组合两个元素
2) 使用接受元素列表(或序列)的函数组合0到n个元素 第二种情况下的函数可以从第一种情况下的函数构建(通常使用
reduce
)
但是,Clojure添加了第三个用例:
3) 使用可变函数组合0到n个元素
所以问题是,Clojure为什么要添加第三个案例?保罗的回答表明:
- 这使得代码更加灵活
- 历史的力量在起作用
+
,当您只是尝试进行一些计算时,将所有内容按顺序包装会很烦人
2) 效率。使用集合/序列包装所有内容也将是低效的,首先需要创建序列,然后在运行时将其解压缩,而不是在编译时查找正确的Java函数
3) 期望值。这就是这些函数在其他Lisp中的工作方式,同样,在其他函数语言中,它们的语法也有所不同,因此对于来到Clojure的人来说,这是一个合理的期望值。我想说,在其他函数语言中,将函数(如+
应用于序列)的惯用方法是使用reduce
或foldl
/foldr
,因此这也与Clojure处理它的方式一致
4) 灵活性。这些函数可以与高阶函数一起使用,例如map
,如果它们是可变的,则使用起来更方便。假设你有三个向量,你想对同一位置的元素应用一个函数。如果您的函数是可变的,那么您可以将map与多个集合一起使用(map也必须是可变的;):
这比如果所有这些函数都只接受集合的话要方便得多
惯用用法:(根据科塔拉克的精彩评论编辑)
这取决于您是否应该使用reduce
或apply
对于数学函数(+,-,*,/,等等),
在Java世界中使用两个参数reduce
更有意义,因为它可以直接使用两个参数的Java版本。使用apply
时,它们会执行一种隐式的reduce
(该函数添加两个参数,然后使用结果、下一个参数和其他参数进行递归。reduce的作用非常类似。)
对于str
而言,使用apply
可能更有效。当使用多个参数调用str
时,它将创建一个StringBuilder,向其中添加所有参数,然后创建一个字符串。使用reduce
将创建StringBuilder n-1次,每次仅添加一个字符串。这和笑话一样,导致了O(n^2)的复杂性
到目前为止的结论是:在数学函数中使用
apply
没有多大伤害,但在str
中使用reduce
可能会非常昂贵。亚历克斯·米勒(Alex Miller)刚刚在他的博客中写到了一个相关问题:我不同意1和2:这很不方便,或者效率较低,或者对我来说也是一样,因为我从序列开始+
是一种转移注意力的方法,因为它有时像二进制加法,有时像一个求和
函数——我在讨论它行为的求和
部分;我将从OP中删除它。但是对于3和4来说,这是一个很好的例子!1和2:你可以从一个序列开始,但不是每个人都这样做!我想,既然clojure拥有deftype/defrecord和协议,那么这些函数可能会被重载到序列中,我不确定在这之前clojure中是否完全可以做到这一点。如果您没有依赖于类型的分派,并且在运行时查找如何分派(无论是基于数字还是序列),那么您将付出性能代价。我认为没有,至少在clojure.core中没有。但是我认为(reduce+[yourlist])
和类似的max
等等实际上可能比自己写的要短,或者至少不会长太多。我想这可能也是它们还不存在的原因。不要从列表等函数中滚动求和。应用是将集合用作函数参数的惯用方法。使用reduce
还是Apply
取决于函数。对于+
来说,它是reduce
,因为两个参数版本可以内联,而更高的变量版本不能内联。他们在引擎盖下做了一个减少。对于str
它是apply
,因为str
可以重新使用底层的StringBuilder
。因此,它比使用reduce时效率更高,生成的中间字符串垃圾更少
(map + [1 2 3 4] [2 3 4 5] [3 4 5 6])
; [6 9 12 15]