Clojure: creazione di una nuova istanza dal nome della class String

In Clojure, dato un nome di class come una stringa, ho bisogno di creare una nuova istanza della class. In altre parole, come implementerei new-instance-from-class-name in

(def my-class-name "org.myorg.pkg.Foo") ; calls constructor of org.myorg.pkg.Foo with arguments 1, 2 and 3 (new-instance-from-class-name my-class-name 1 2 3) 

Sto cercando una soluzione più elegante di

  • chiamare il metodo newInstance di Java su un costruttore dalla class
  • usando eval, load-string, …

In pratica, lo userò su classi create usando defrecord. Quindi se c’è qualche syntax speciale per quello scenario, sarei piuttosto interessato.

Ci sono due buoni modi per farlo. Quale è il migliore dipende dalla circostanza specifica.

Il primo è la riflessione:

 (Clojure.lang.Reflector / invokeConstructor
   (risoluzione (simbolo "intero"))
   (to-array ["16"])))

È come chiamare (new Integer "16") … includere tutti gli altri argomenti del ctor necessari nel vettore to-array. È facile, ma più lento in fase di esecuzione rispetto all’utilizzo di new suggerimenti di tipo sufficienti.

La seconda opzione è il più veloce ansible, ma un po ‘più complicata, e usa eval :

 (defn make-factory [nome della class e tipi]
   (let [args (map # (con-meta (simbolo (str "x"% 2)) {: tag% 1}) tipi (intervallo))]
     (eval `(fn [~ @ args] (nuovo ~ (nome di class simbolo) ~ @ args))))))

 (def int-factory (make-factory "Integer" 'String))

 (int-factory "42")

Il punto chiave è il codice di valutazione che definisce una funzione anonima, come make-factory . Questo è lento – più lento dell’esempio di riflessione qui sopra, quindi fallo solo raramente, ad esempio una volta per class. Ma avendo fatto ciò hai una normale funzione Clojure che puoi memorizzare da qualche parte, in una var come int-factory in questo esempio, o in una hash-map o in un vettore a seconda di come la userai. Indipendentemente da ciò, questa funzione di fabbrica funzionerà alla massima velocità compilata, può essere sottolineata da HotSpot, ecc. E verrà sempre eseguita molto più velocemente rispetto all’esempio di riflessione.

Quando si tratta in modo specifico di classi generate da deftype o defrecord , è ansible saltare l’elenco dei tipi poiché tali classi hanno sempre esattamente due operatori, ciascuno con diverse abilità. Questo permette qualcosa come:

 (defn record-factory [recordname]
   (let [recordclass ^ Class (resolve (symbol recordname))
         max-arg-count (applica max (map # (count (.getParameterTypes%))
                                       (.getConstructors recordclass)))
         args (map # (symbol (str "x"%)) (range (- max-arg-count 2)))]]
     (eval `(fn [~ @ args] (nuovo ~ (nome record del simbolo) ~ @ args))))))


 (defrecord ExampleRecord [abc])

 (esempio: record-factory (record-factory "ExampleRecord"))

 (esempio-record-factory "F." "Scott" 'Fitzgerald)

Dal momento che “nuovo” è un modulo speciale, non sono sicuro che sia ansible farlo senza una macro. Ecco un modo per farlo usando una macro:

 user=> (defmacro str-new [s & args] `(new ~(symbol s) [email protected])) #'user/str-new user=> (str-new "String" "LOL") "LOL" 

Controlla il commento di Michal sui limiti di questa macro.

Ecco una tecnica per estendere defrecord per creare automaticamente funzioni di costruzione ben definite per build istanze di record (nuove o basate su un record esistente).

http://david-mcneil.com/post/765563763/enhanced-clojure-records

In Clojure 1.3, defrecord automaticamente defn una funzione di fabbrica utilizzando il nome del record con “->” anteposto. Allo stesso modo, una variante che prende una mappa sarà il nome del record preceduto da “map->”.

 user=> (defrecord MyRec [ab]) user.MyRec user=> (->MyRec 1 "one") #user.MyRec{:a 1, :b "one"} user=> (map->MyRec {:a 2}) #user.MyRec{:a 2, :b nil} 

Una macro come questa dovrebbe funzionare per creare un’istanza dal nome della stringa del tipo di record:

 (defmacro newbie [recname & args] `(~(symbol (str "->" recname)) [email protected]))