2016-04-05 4 views
1

Я реализовал быстрый алгоритм питания в Clojure:Clojure mutimethods для отправки по типу

(defn fast-pow [a n] 
    (cond (zero? n) 1 
    (even? n) (letfn [(square [x] (*' x x))] 
       (square (fast-pow a (/ n 2)))) 
    :else (*' a (fast-pow a (dec' n))))) 

А теперь я хочу играть с намеками типа и Java Interop. Я хочу сделать это из-за лучшего понимания всего этого «java-материала» в clojure. Похоже, их довольно легко, но на самом деле есть много скрытых препятствий. Итак, я написал:

(defn ^java.math.BigInteger fast-pow 
    ([^java.lang.Long a ^java.lang.Long n] 
    (fast-pow (java.math.BigInteger/valueOf a) (java.math.BigInteger/valueOf n))) 
    ([^java.math.BigInteger a ^java.math.BigInteger n] 
    (cond (zero? n) java.math.BigInteger/ONE 
     (even? n) (letfn [(square [x] (.multiply x x))] 
        (square (fast-pow a (.divide n (java.math.BigInteger/valueOf 2))))) 
     :else (.multiply a (fast-pow a (.subtract n BigInteger/ONE)))))) 

Конечно, он даже не скомпилирован из-за неправильной проблемы с arity. Так что я google как отправить по типу в clojure и нашел multimethods. На самом деле в этот момент я был меняться наивный и возбужденный, я никогда раньше не пробовал multimethods, поэтому, я написал что-то так:

(derive java.math.BigInteger ::biginteger) 
(derive java.lang.Long ::long) 
(defmulti fast-pow (fn [a n] [(class a) (class n)])) 

(defmethod fast-pow [::biginteger ::biginteger] [a n] 
    (cond (zero? n) java.math.BigInteger/ONE 
     (even? n) (letfn [(square [x] (.multiply x x))] 
        (square (fast-pow a (.divide n (java.math.BigInteger/valueOf 2))))) 
     :else (.multiply a (fast-pow a (.subtract n BigInteger/ONE))))) 
(defmethod fast-pow [::long ::long] [a n] 
    (fast-pow 
    (java.math.BigInteger/valueOf a) 
    (java.math.BigInteger/valueOf n))) 

Дела идут немного сложнее. Это прекрасно работает, но мне интересно, есть ли более чистый путь к такой вещи, как просто перегрузка по типу. Я не понял, почему в clojure я не могу этого сделать, даже если в Java я могу.

PS: Конечно, я могу «спрятать» логику на основе java.math.BigInteger внутри вложенной функции и вызвать ее из внешней функции fast-pow, но на самом деле - это не интересно для меня, я уверен, эта проблема может быть решена используя multimethods. Если я ошибаюсь или что-то не хватает, пожалуйста, объясните мне это.;)

+2

Только некоторые подсказки стиля: извлеките функцию 'square' наружу, бесполезно определять ее локально через' letfn' на каждом этапе рекурсии. Кроме того, взгляните на 'recur' (и, вероятно,' loop') и переместите рекурсивный вызов в позицию хвостового вызова - то, как вы делаете рекурсию, скорее всего, выведет стек довольно скоро. – schaueho

ответ

3

Если вы хотите отправить по типу более одного аргумента, то это будут различные методы. имейте в виду, что это уже встроено во все вспомогательные математические функции, такие как + 'и *'.

user> (+' 2N 3) 
5N 

Вы не можете, в Clojure, используйте тип подсказок, чтобы выполнить этот тип перегрузки/отправки (и это было сделано нарочно). Если вы только отправляете по первому аргументу, вы должны использовать protocols, потому что они намного быстрее.

+0

Хорошо, спасибо, но почему это так сложно? Я имею в виду, не отправляется ли тип, уже реализованный в JVM? Это потому, что функция evey в clojure выглядит как класс для JVM? –

+1

Я не могу говорить с личной мотивацией, почему, хотя язык был разработан таким образом. Мультиметоды предлагают полностью общий метод отправки, который охватывает этот и любой другой тип отправки, который может быть выражен во время вызова. И протоколы предлагают отправку по первому утверждению, которое является огромным большинством случаев. Поэтому я могу только предположить, что отправка по типу второго аргумента не рассматривалась как достаточно важная, чтобы гарантировать третий тип отправки (и протоколы, добавленные позже, изначально Clojure только имели мультиметоды). Если вы хотите, вы можете создать эту функцию с помощью макросов –