Как сделать запись из последовательности значений

У меня есть простое определение записи, например

(defrecord User [name email place])

Каков наилучший способ сделать запись с ее значениями в последовательности

(def my-values ["John" "[email protected]" "Dreamland"])

Я надеялся на что-то вроде

(apply User. my-values)

но это не сработает. Я закончил:

(defn make-user [v]
  (User. (nth v 0) (nth v 1) (nth v 2)))

Но я чувствую, что есть лучший способ добиться этого...

Ответы

Ответ 1

Предупреждение: работает только для буквенных последовательностей! (см. комментарий Mihał)

Попробуйте этот макрос:

(defmacro instantiate [klass values] 
        `(new ~klass [email protected]))

Если вы развернете его с помощью

(macroexpand '(instantiate User ["John" "[email protected]" "Dreamland"]))

вы получите следующее:

(new User "John" "[email protected]" "Dreamland")

который в основном вам нужен.

И вы можете использовать его для создания экземпляров других типов записей или классов Java. В принципе, это просто конструктор класса, который принимает одну последовательность параметров вместо многих параметров.

Ответ 2

функция defrecord создает скомпилированный класс с некоторыми неизменяемыми полями в нем. это не правильные функции clojure (т.е. не класс, реализующий iFn). Если вы хотите называть его конструктором с применением (который ожидает iFun), вам нужно обернуть его анонимной функцией, чтобы применить его, чтобы переварить его.

(apply #(User. %1 %2 %3 %4) my-values)

он ближе к тому, с чего вы начали, хотя ваш подход к определению конструктора с хорошим описательным именем имеет свое собственное обаяние:)

из API:

Note that method bodies are
not closures, the local environment includes only the named fields,
and those fields can be accessed directy.

Ответ 3

Написание собственной функции-конструктора - это, вероятно, путь. Как сказал Артур Ульфельдт, у вас есть функция, которую вы можете использовать как функцию (например, с apply), а не вызов конструктора Java-interop.

С помощью вашей собственной функции конструктора вы также можете выполнить проверку аргументов или предоставить аргументы по умолчанию. Вы получаете еще один уровень абстракции для работы; вы можете определить make-user, чтобы вернуть хэш-карту для быстрой разработки, и если позже вы решите изменить записи, вы можете сделать это, не нарушая все. Вы можете писать конструкторы с несколькими атрибутами или принимать ключевые аргументы или делать любое количество других вещей.

(defn- default-user [name]
  (str (.toLowerCase name) "@example.com"))

(defn make-user
  ([name] (make-user name nil nil))
  ([name place] (make-user name nil place))
  ([name user place]
     (when-not name
       (throw (Exception. "Required argument `name` missing/empty.")))
     (let [user (or user (default-user name))]
       (User. name user place))))

(defn make-user-keyword-args [& {:keys [name user place]}]
  (make-user name user place))

(defn make-user-from-hashmap [args]
  (apply make-user (map args [:name :user :place])))

user> (apply make-user ["John" "[email protected]" "Somewhere"])
#:user.User{:name "John", :email "[email protected]", :place "Somewhere"}

user> (make-user "John")
#:user.User{:name "John", :email "[email protected]", :place nil}

user> (make-user-keyword-args :place "Somewhere" :name "John")
#:user.User{:name "John", :email "[email protected]", :place "Somewhere"}

user> (make-user-from-hashmap {:user "foo"})
; Evaluation aborted.
; java.lang.Exception: Required argument `name` missing/empty.

Ответ 4

Одна простая вещь, которую вы можете сделать, это использовать деструктурирование.

(defn make-user [[name email place]]
  (User. name email place))

Затем вы можете просто называть его следующим образом

(make-user ["John" "[email protected]" "Dreamland"])

Ответ 5

Обновление для Clojure 1.4

defrecord теперь определяет ->User и map->User, таким образом следуя в ногах Горана, теперь можно

(defmacro instantiate [rec args] `(apply ~(symbol (str "->" rec)) ~args))

который также работает с нелиберальными последовательностями, как в (instantiate User my-values). В качестве альтернативы, вдоль линий map->User можно определить функцию seq->User

(defmacro def-seq-> [rec] `(defn ~(symbol (str "seq->" rec)) [arg#] (apply ~(symbol (str "->" rec)) arg#)))

(def-seq-> User)

что позволит (seq->User my-values).