Clojure中是否有适用于Java函数的apply函数?

16
 user=> (Integer/rotateRight 0 0)
 0

 user=>  (apply Integer/rotateRight [0 0])
 CompilerException java.lang.RuntimeException: Unable to find static field: 
   rotateRight in class java.lang.Integer, compiling:(NO_SOURCE_PATH:172)

在Clojure中是否有一种方法可以使用Java函数?如果没有,我该如何编写一个宏或函数来支持这个功能?


有趣的消息:计划在未来的Clojure编译器重写中加入自动方法包装生成功能。http://dev.clojure.org/display/design/Better+method+inference - Ambrose
3个回答

17

我能想到的最简单的方法是将其包装在一个函数中,但我不确定这是否是最好/最惯用的方式:

user> (apply (fn [a b] (Integer/rotateRight a b)) [0 0])
0
或者,稍微缩短但等价的:
user> (apply #(Integer/rotateRight %1 %2) [0 0])
0

或者,您可以为Java方法调用创建一个适当的包装函数:

(defn rotate-right [a b]
  (Integer/rotateRight a b))

你可以像这样使用它:

user> (apply rotate-right [0 0])
0

编辑: 只是为了好玩,受到iradik评论中对效率的启发,对调用这个方法的三种不同方式进行时间比较:

;; direct method call (x 1 million)
user> (time (dorun (repeatedly 1E6 #(Integer/rotateRight 2 3))))
"Elapsed time: 441.326 msecs"
nil

;; method call inside function (x 1 million)
user> (time (dorun (repeatedly 1E6 #((fn [a b] (Integer/rotateRight a b)) 2 3))))
"Elapsed time: 451.749 msecs"
nil

;; method call in function using apply (x 1 million)
user> (time (dorun (repeatedly 1E6 #(apply (fn [a b] (Integer/rotateRight a b)) [2 3]))))
"Elapsed time: 609.556 msecs"
nil

1
这真是聪明,我想知道是否可以编写一个宏来为我编写它。 - user468687
1
你可能可以这样做,但我个人认为其好处有限:生成的表单几乎不会更短,而且上面的代码清楚地说明了正在发生什么。或者,您可以定义该函数并调用它,而不是Java方法。我将编辑我的答案以说明。 - Gert
1
更加简洁的代码可能会让人阅读时需要弄清楚 j-apply 的含义,因此在我看来可读性较差。 - Gert
3
不,如果涉及到eval,解决方案立即变得可怕。给定以上宏定义,请尝试(例如)(let [x "10"] (java-static-apply Integer/parseInt [x])) - 你会发现x不在作用域内。更糟糕的是,这个宏只有在您在编译时有文字序列的情况下才能工作 - 这正是您根本不需要使用apply的时候!它无法与(let [args ["10"]] (java-static-apply Integer/parseInt args))一起使用,因为它无法在编译时映射args。 - amalloy
这对于 map 也适用。(map #(Integer/parseInt %) ["1" "2" "3"]) - Patrick
显示剩余3条评论

4
有几个点虽然不能直接回答问题,但与此相关。
首先,Java没有函数。它只有实例方法或静态方法。这可能看起来很啰嗦,但确实有所区别(正如其他示例中所示,在静态和实例调用时需要不同的形式)。
其次,类型系统之间的阻抗不匹配发挥作用。要以Java方式完全支持FP,就需要进行静态类型检查。结果证明,这是相当困难且无法令人满意的(请参见lambda-dev邮件列表上的讨论,了解正在使用并将在Java 8中到达的方法的详细信息)。
从这两个观点出发,我们可以看到,从Clojure出发,我们真正能做的最好的事情就是通过#()或类似方法支持“所有的赌注”,调用Java方法。Clojure只会根据参数的数量选择调用形式,因此可能需要一些类型提示或强制转换来确保调用正确的重载Java方法。
更重要的是,如果用户传递了Java不期望或无法处理的类型的参数,则在运行时可能无法检测到。

我想知道如何在一组实例上调用实例方法。 - Timo

0

在受到gertalot的回答启发后,我写了几个宏来完成这个任务。看起来编译成了等效的普通代码。基准测试结果也相同。不知道您有何想法。

(defmacro java-new-apply 
   ([klass] `(new ~klass))
   ([klass args] `(new ~klass ~@(map eval args))))

(defmacro java-static-apply 
   ([f] f)
   ([f args] `(~f ~@(map eval args))))

(defmacro java-method-apply 
   ([method obj] method obj) 
   ([method obj args] `(~method ~obj ~@(map eval args))))

;; get date for Jan 1 1969
(import java.util.Date)
(java-new-apply Date [69 1 1])
(macroexpand '(java-new-apply Date [69 1 1]))
(new Date 69 1 1)

(java-static-apply Integer/rotateRight [2 3])
(macroexpand '(java-static-apply Integer/rotateRight [2 3]))
(. Integer rotateRight 2 3)

(java-method-apply .substring "hello world!" [6 11])
(macroexpand '(java-method-apply .substring "hello world!" [6 11]))
(. "hello world!" substring 6 11)

3
只有在将字面量传递给宏时,此方法才有效。尝试这样做:(def my-date-data [69 1 1]) (macroexpand '(java-new-apply Date my-date-data)),你会发现它无效。宏在编译时展开,而不是运行时展开,这意味着你无法访问传递的实际值。 - Goran Jovic
我尝试了同样的事情,并最终发现与使用包装函数的惯用解决方案相比,这并不值得。 - Goran Jovic
同意Goran的观点 - 无论如何,你最终都会得到一个包含在函数调用中的方法调用。 - Gert
哦,太好以至于让人难以置信。是的,你说得对,我想要真实的数据。 - user468687

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接