Есть ли функция приложения в 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)