Istnieją dwa podstawowe podejścia:
refleksji:
(clojure.lang.Reflector/invokeConstructor Klass (to-array [arg ...]))
Powolny, ale całkowicie dynamiczne.
rozpakować argumenty uprzednio:
(let [[arg1 arg2 ...] args]
(Klass. arg1 arg2 ...))
((Klass. ...)
jest sposobem na język pisania (new Klass ...)
, to jest przekształcany w drugiej formie w makro czasie rozprężania.)
To będzie szybciej, jeśli kompilator może wydedukować, który konstruktor zostanie użyty (prawdopodobnie będziesz musiał podać odpowiednie wskazówki typu - użyj (set! *warn-on-reflection* true)
, aby sprawdzić, czy masz rację).
Drugie podejście jest oczywiście nieco nieporęczne. Jeśli spodziewasz się zbudować wiele instancji Klass
w wielu miejscach kodu, możesz napisać odpowiednią funkcję fabryczną. Jeśli oczekujesz do czynienia z wielu klas w ten sposób można abstrakcyjny dala proces definiowania funkcji fabrycznych:
(defmacro deffactory [fname klass arg-types]
(let [params (map (fn [t]
(with-meta (gensym) {:tag t}))
arg-types)]
`(defn ~(with-meta fname {:tag klass}) ~(vec params)
(new ~klass [email protected]))))
Wreszcie, jeśli proces definiowania funkcji fabrycznych, sam musi być całkowicie dynamiczny, można zrobić coś podobnie jak drugie podejście Chousera do this question: zdefiniuj funkcję, a nie makro, i nadaj jej eval
coś podobnego do powyższej składni (defn ...)
(składnia cytowana = z backtick przed nim; nie jestem pewien, jak dołączyć dosłowny backtick w poście SO), z wyjątkiem tego, że chcesz używać fn
zamiast defn
i ewentualnie pomiń fname
. Wywołanie kompilatora będzie kosztowne, ale zwrócona funkcja będzie działać tak, jak każda funkcja Clojure; patrz wyżej wspomniana odpowiedź Chousera na nieco dłuższą dyskusję.
Dla większej kompletności, jeśli używasz Clojure 1.3 lub nowszego, a klasa Java, która jest zaangażowana, jest faktycznie rekordem Clojure, to fabryczna funkcja pozycyjna zostanie już utworzona pod nazwą ->RecordName
.