Как сделать запись из последовательности значений - PullRequest
11 голосов
/ 23 декабря 2010

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

(defrecord User [name email place])

Как лучше всего сделать запись, имеющую значения в последовательности

(def my-values ["John" "john@example.com" "Dreamland"])

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

(apply User. my-values)

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

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

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

Ответы [ 5 ]

4 голосов
/ 23 декабря 2010

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

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

(defmacro instantiate [klass values] 
        `(new ~klass ~@values))

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

(macroexpand '(instantiate User ["John" "john@example.com" "Dreamland"]))

вы получите это:

(new User "John" "john@example.com" "Dreamland")

, что в основном то, что вам нужно.

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

4 голосов
/ 23 декабря 2010

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

С вашей собственной функцией конструктора вы также можете выполнить проверку аргумента илипредоставить аргументы по умолчанию.Вы получаете другой уровень абстракции для работы;вы можете определить 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" "john@example.com" "Somewhere"])
#:user.User{:name "John", :email "john@example.com", :place "Somewhere"}

user> (make-user "John")
#:user.User{:name "John", :email "john@example.com", :place nil}

user> (make-user-keyword-args :place "Somewhere" :name "John")
#:user.User{:name "John", :email "john@example.com", :place "Somewhere"}

user> (make-user-from-hashmap {:user "foo"})
; Evaluation aborted.
; java.lang.Exception: Required argument `name` missing/empty.
4 голосов
/ 23 декабря 2010

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

(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.
2 голосов
/ 25 декабря 2010

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

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

Тогда вы можете просто назвать это так

(make-user ["John" "John@example.com" "Dreamland"])
0 голосов
/ 01 апреля 2013

Обновление для 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).

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