例如,我有一个简单的记录定义
(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)))
但我感觉有一些更好的方法来实现这一目标......
警告:仅适用于文字的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类。基本上,这只是一个类构造函数,它接受一个参数序列而不是许多参数。
defrecord函数创建一个带有一些不可变字段的编译类。它不是一个合适的clojure函数(即:不是一个实现iFn的类)。如果你想使用apply(它需要一个iFun)来调用它的构造函数,你需要将它包装在一个匿名函数中,这样apply就能够消化它。
(apply #(User. %1 %2 %3 %4) my-values)
尽管你定义一个具有良好描述性名称的构造函数有自己的魅力,但它更接近你的开始:)
来自 API:
请注意方法体是
不是闭包,本地环境只包括命名字段,
这些字段可以直接访问。
编写自己的构造函数可能是要走的路。正如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.
你可以做的一件简单事就是利用解构。
(defn make-user [[name email place]]
(User. name email place))
然后你就可以这样称呼它
(make-user ["John" "John@example.com" "Dreamland"])
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)
。