如何从一系列值中创建记录

时间:2010-12-22 22:12:51

标签: clojure

我有一个简单的记录定义,例如

(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 个答案:

答案 0 :(得分:4)

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.

答案 1 :(得分:4)

警告:仅适用于字面上的sequables!(参见Mihał的评论)

试试这个宏:

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

如果您使用以下内容展开:

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

你会得到这个:

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

这基本上就是你需要的。

您可以使用它来实例化其他记录类型或Java类。基本上,这只是一个类构造函数,它接受一个参数序列而不是许多参数。

答案 2 :(得分:4)

编写自己的构造函数可能就是这样。正如Arthur Ulfeldt所说,你有一个函数可以用作函数(例如用apply)而不是Java-interop构造函数调用。

使用您自己的构造函数,您还可以执行参数验证或提供默认参数。你可以获得另一个抽象层次;您可以定义make-user以返回哈希映射以进行快速开发,如果您以后决定更改为记录,则可以在不破坏所有内容的情况下执行此操作。您可以编写具有多个arities的构造函数,或者使用关键字参数,或者执行任何其他操作。

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

答案 3 :(得分:2)

你能做的一件简单事就是利用解构。

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

然后你就可以这样称呼它

(make-user ["John" "John@example.com" "Dreamland"])

答案 4 :(得分:0)

Clojure 1.4更新

defrecord现在定义了->Usermap->User因此跟随Goran的脚步,现在可以

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