Clojure: создание нового экземпляра из имени класса String

В Clojure, учитывая имя класса в виде строки, мне нужно создать новый экземпляр класса. Другими словами, как бы я реализовал new-instance-from-class-name в

(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) 

Я ищу решение более элегантное, чем

вызов метода Java newInstance для конструктора из классаиспользуя eval, load-string, ...

На практике я буду использовать его на классах, созданных с помощью defrecord. Так что если есть какой-то особый синтаксис для этого сценария, мне было бы очень интересно.

Ответы на вопрос(4)

цию, используя имя записи с добавлением «->». Точно так же вариант, который берет карту, будет именем записи с добавлением «map->».

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

Подобный макрос должен работать для создания экземпляра из строкового имени типа записи:

(defmacro newbie [recname & args] `(~(symbol (str "->" recname)) [email protected]))
Решение Вопроса

зависит от конкретных обстоятельств.

Первое отражение:

(clojure.lang.Reflector/invokeConstructor
  (resolve (symbol "Integer"))
  (to-array ["16"]))

Это как звонить(new Integer "16") ... включите любые другие аргументы ctor, которые вам нужны, в вектор to-array. Это легко, но медленнее во время выполнения, чем использованиеnew с достаточными подсказками типа.

Второй вариант максимально быстрый, но немного более сложный и используетeval:

(defn make-factory [classname & types]
  (let [args (map #(with-meta (symbol (str "x" %2)) {:tag %1}) types (range))]
    (eval `(fn [[email protected]] (new ~(symbol classname) [email protected])))))

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

(int-factory "42")

Ключевым моментом является получение кода, который определяет анонимную функцию, какmake-factory делает. Этомедленный - медленнее, чем в приведенном выше примере с отражением, поэтому делайте это как можно реже, например, один раз в классе. Но сделав это, у вас есть обычная функция Clojure, которую вы можете хранить где-нибудь, в видеint-factory в этом примере, или в хэш-карте или векторе, в зависимости от того, как вы будете его использовать. Несмотря на это, эта заводская функция будет работать на полной скорости компиляции, может быть встроена в HotSpot и т. Д. И всегда будет работать многоБыстрее чем пример отражения.

Когда вы имеете дело с классами, сгенерированнымиdeftype или жеdefrecord, вы можете пропустить список типов, так как эти классы всегда имеют ровно два ctors с разными арностями. Это позволяет что-то вроде:

(defn record-factory [recordname]
  (let [recordclass ^Class (resolve (symbol recordname))
        max-arg-count (apply max (map #(count (.getParameterTypes %))
                                      (.getConstructors recordclass)))
        args (map #(symbol (str "x" %)) (range (- max-arg-count 2)))]
    (eval `(fn [[email protected]] (new ~(symbol recordname) [email protected])))))


(defrecord ExampleRecord [a b c])

(def example-record-factory (record-factory "ExampleRecord"))

(example-record-factory "F." "Scott" 'Fitzgerald)
 chris21 сент. 2010 г., 21:21
Отлично! Второй вариант, очевидно, очень общий метод. Я уже использовал это по-другому.

new' - это особая форма, я не уверен, что вы можете сделать это без макроса. Вот способ сделать это с помощью макроса:

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

Посмотрите комментарий Михала об ограничениях этого макроса.

 chris20 сент. 2010 г., 06:34
Боюсь, что s не будет буквальной строкой. Я отредактировал вопрос, чтобы отразить это.
 Rayne20 сент. 2010 г., 06:23
Спасибо что подметил это. Не думал об этом упоминать.
 Michał Marczyk20 сент. 2010 г., 06:17
Обратите внимание, что это будет работать только еслиs макрос получает (буквенную) строку, а не произвольное выражение, вычисляющее строку. В последнем случае избежатьeval или отражающая конструкция экземпляра.

нных функций конструктора для создания экземпляров записей (новых или основанных на существующей записи).

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

Ваш ответ на вопрос