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


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

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

但我感觉有一些更好的方法来实现这一目标......


6928
2017-12-22 22:12


起源

并添加答案:1。看一看 github.com/david-mcneil/defrecord2 提供一些额外的东西。我认为他们在未来的clojure版本中会计划一些这样的东西。 - nickik
作为旁注:您可以在构造函数中使用解构绑定。只是出于美观的原因。 (定义make-user [[n e p]](用户.n e p))。 - Kintaro


答案:


警告:仅适用于文字的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类。基本上,这只是一个类构造函数,它接受一个参数序列而不是许多参数。


4
2017-12-22 22:57



NB。这只会在 instantiate 宏接收一个文字的seqable值。例如。 (def vals ["John" "john@example.com" "Dreamland"]) 其次是 (instantiate User vals) 不管用。加上一个宏不能用 apply。 - Michał Marczyk
@Mihał:谢谢。我没想到那个角落的情况。幸运的是,在使用文字的情况下,不需要申请。 - Goran Jovic
不是我需要它,但我发现了一种令人讨厌的方式,它也适用于非文字的seqable - (defn instantiate [klass values] (eval (read-string (str "(new " (.getName klass) " " (let [s (str values)] (subs s 1 (- (.length s) 1))) ")")))) - Nevena


defrecord函数创建一个带有一些不可变字段的编译类。它不是一个合适的clojure函数(即:不是一个实现iFn的类)。如果你想使用apply(它需要一个iFun)来调用它的构造函数,你需要将它包装在一个匿名函数中,这样apply就能够消化它。

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

尽管你定义一个具有良好描述性名称的构造函数有自己的魅力,但它更接近你的开始:)

来自 API

请注意方法体是
不是闭包,本地环境只包括命名字段,
这些字段可以直接访问。

4
2017-12-22 22:22



绝对喜欢这个比我的nth-y更多的东西..但我仍然需要注意所有预期的论点..我正在寻找一些可以应用于任何记录定义的一般方法......如果有一个...... - Nevena


编写自己的构造函数可能是要走的路。正如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.

4
2017-12-22 23:01





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

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

然后你就可以这样称呼它

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

2
2017-12-25 05:15





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)


0
2018-03-31 23:02



你可以简单 (apply ->User values); ->User 是一个正常的功能。 - jbm