我有一个简单的记录定义,例如
(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)))
但我觉得有更好的方法来实现这个目标......
答案 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现在定义了->User
和map->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)
。