Clojure: создание нового экземпляра из имени класса String - PullRequest
16 голосов
/ 20 сентября 2010

В 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 Javaна конструкторе из класса
  • с использованием eval, load-string, ...

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

Ответы [ 4 ]

23 голосов
/ 20 сентября 2010

Есть два хороших способа сделать это.Что лучше, зависит от конкретных обстоятельств.

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

(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 [~@args] (new ~(symbol classname) ~@args)))))

(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 [~@args] (new ~(symbol recordname) ~@args)))))


(defrecord ExampleRecord [a b c])

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

(example-record-factory "F." "Scott" 'Fitzgerald)
4 голосов
/ 20 сентября 2010

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

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

Ознакомьтесь с комментарием Михала об ограничениях этого макроса.

3 голосов
/ 16 декабря 2011

В Clojure 1.3 функция defrecord автоматически определит заводскую функцию, используя имя записи с добавлением «->». Аналогично, вариант, который использует карту, будет именем записи с добавлением «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)) ~@args))
3 голосов
/ 21 сентября 2010

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

http://david -mcneil.com / запись / 765563763 / повышенной Clojure-записи

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...