Есть ли функция приложения в Clojure для функций Java?
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)
Есть ли способ применить для 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
edit: просто для удовольствия, вдохновленный комментарием 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
Ответ 2
Несколько пунктов, которые, хотя и не являются прямым ответом, здесь актуальны.
Во-первых, Java не имеет функций. Он имеет только методы экземпляра или статические методы. Это может показаться педантичным различием, но оно действительно имеет значение (как показано в некоторых других примерах, где нужны разные формы для статического вызова и вызова экземпляра).
Во-вторых, вступает в действие несоответствие импеданса между системами типов. Для того чтобы Java обладал полноценной поддержкой FP в стиле Java, ее нужно было бы статически вводить. Это довольно сложно сделать по-настоящему удовлетворительно (см. Обсуждение списка рассылки lambda-dev для подробностей о подходе, который используется и поступит в Java 8).
Из этих двух точек видно, что из Clojure лучшее, что мы действительно можем сделать, это поддерживать подход "все ставки отключены" для вызова методов Java через #() или аналогичный. Clojure будет выбирать только между формами для вызова на основе арности аргумента, поэтому для обеспечения корректного перегруженного Java-метода может потребоваться какой-то тип подсказок или кастинг.
Что еще более важно, конечно, если пользователь передает аргумент типа, который Java не ожидает или не может обрабатывать, это может быть не обнаружено до выполнения.
Ответ 3
Я написал несколько макросов, чтобы сделать это после того, как был вдохновлен gertalot ответом. Появляется возможность скомпилировать эквивалентный нормальный код. Тест был идентичным. Любопытно, что вы думаете.
(defmacro java-new-apply
([klass] `(new ~klass))
([klass args] `(new ~klass [email protected](map eval args))))
(defmacro java-static-apply
([f] f)
([f args] `(~f [email protected](map eval args))))
(defmacro java-method-apply
([method obj] method obj)
([method obj args] `(~method ~obj [email protected](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)