【问题标题】:Is there an apply function in Clojure for Java functions?Clojure 中是否有适用于 Java 函数的应用函数?
【发布时间】:2011-12-29 09:37:53
【问题描述】:
 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 函数?如果不是,我怎么能写一个支持这个的宏或函数?

【问题讨论】:

标签: java clojure


【解决方案1】:

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

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

【讨论】:

  • 这很聪明,我想知道我是否可以编写一个为我编写的宏。
  • 您可能可以,但我个人认为这样做的好处是微乎其微的:生成的表单几乎不会更短,并且上面的代码清楚地说明了发生了什么。或者,您可以定义函数并调用它而不是 java 方法。我将编辑我的答案来说明。
  • 我不同意。如果你能做什么(j-apply 'Integer/rotateRight [0 0])?那不是更简洁吗?
  • 它会更简洁,但是阅读您的代码的人需要弄清楚 j-apply 的含义,所以它的可读性较差,恕我直言。
  • @iradik 不,如果你让eval 参与其中,那么解决方案会立即变得很糟糕。鉴于上述宏定义,尝试(例如)(let [x "10"] (java-static-apply Integer/parseInt [x])) - 你会发现x 不在范围内。更糟糕的是,这个宏只有在你有 literal seq at compile-time 时才有效——这正是你根本不需要 apply 的时候!它根本不适用于(let [args ["10"]] (java-static-apply Integer/parseInt args)),因为它无法在编译时映射到 args。
【解决方案2】:

这里有几点虽然不是直接的答案,但与此相关。

首先,Java 没有函数。它只有实例方法或静态方法。这似乎是一种迂腐的区别,但它确实有所不同(如在静态和实例调用需要不同形式的其他一些示例中所示)。

其次,类型系统之间的阻抗不匹配会起作用。要让 Java 以 Javaish 方式获得完全成熟的 FP 支持,它需要进行静态类型化。事实证明,要以真正令人满意的方式做到这一点非常困难(有关正在使用的方法的详细信息,请参阅 lambda-dev 邮件列表中的讨论,了解 Java 8 中正在使用的方法的详细信息)。

从这两点我们可以看出,从 Clojure 中我们真正能做的最好的事情就是支持通过 #() 或类似方法调用 Java 方法的“所有赌注都没有”的方法。 Clojure 只会根据参数的数量在要调用的形式之间进行选择,因此可能需要某种类型的提示或强制转换来确保调用正确的重载 Java 方法。

当然,更重要的是,如果用户传递了 Java 不期望或无法处理的类型的参数,则可能要到运行时才能检测到。

【讨论】:

  • 正确。我想知道如何在一系列实例上调用实例方法。
【解决方案3】:

在受到 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)

【讨论】:

  • 这仅在您将文字传递给宏时才有效。试试这个:(def my-date-data [69 1 1])(macroexpand '(java-new-apply Date my-date-data)) 看看它不起作用。宏在编译时扩展,而不是在运行时扩展,这意味着您无法访问传递的实际值。
  • tried the same thing 并最终发现,与带有包装函数的惯用解决方案相比,它是不值得的。
  • 同意 Goran 的观点——无论你如何分割它,你总是会得到一个封装在函数调用中的方法调用。
  • 哦,太好了,我想是真的。是的,你是对的,是的,我想要真实的数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-17
  • 1970-01-01
  • 2011-07-11
  • 2014-06-14
  • 2014-02-24
  • 2011-12-09
相关资源
最近更新 更多