Clojure:如何在函数内创建记录?

时间:2012-02-16 08:54:18

标签: clojure

在clojure中,我想在函数内创建一条记录。

我试过了:

(defn foo []
  (defrecord MyRecord [a b])
  (let [b (MyRecord. "1" "2")]))

但它会导致异常:

java.lang.IllegalArgumentException: Unable to resolve classname: MyRecord

有什么想法吗?

1 个答案:

答案 0 :(得分:10)

关键点

您应该只在顶层使用defrecord 1

因此,如果您确实需要自定义记录类型,则应在foo之外定义它(在代码中的某个位置,在foo的定义之前处理)。

否则,您可以使用常规地图。特别是,如果foo将创建多个“类型”的实体(在概念层面),尝试为每个类型创建一个记录类型(Java类)可能没有意义;自然的解决方案是使用带有:type键的地图来表示所代表的实体类型。

为什么它不起作用

问题中的代码无法编译,因为Clojure的编译器在编译时解析了作为new表单的第一个参数提到的类名。 ((MyRecord. "1" "2")在宏扩展过程中扩展为(new MyRecord "1" "2")。)此处名称MyRecord尚未解析为适当的类,因为后者尚未定义(它将是在defrecord首次调用之后由foo表单创建。

要解决这个问题,你可以做一些可怕的事情,比如

(defn foo []
  (eval '(defrecord MyRecord [x y]))
  (let [b (clojure.lang.Reflector/invokeConstructor
           ;; assuming MyRecord will get created in the user ns:
           (Class/forName "user.MyRecord")
           (into-array Object ["1" "2"]))]
    b))

导致小猫通过反射调用其finalize方法,导致可怕的死亡。

作为最后的评论,上面这个可怕的版本会起作用,但它也会在每次调用时以相同的名称创建一个新的记录类型。这会导致奇怪的事情发生。尝试以下示例来说明风味:

(defprotocol PFoo (-foo [this]))

(defrecord Foo [x]
  PFoo
  (-foo [this] :foo))

(def f1 (Foo. 1))

(defrecord Foo [x])

(extend-protocol PFoo
  Foo
  (-foo [this] :bar))

(def f2 (Foo. 2))

(defrecord Foo [x])

(def f3 (Foo. 3))

(-foo f1)
; => :foo
(-foo f2)
; => :bar
(-foo f3)
; breaks

;; and of course
(identical? (class f1) (class f2))
; => false
(instance? (class f1) f2)
; => false

此时,(Class/forName "user.Foo")(再次假设所有这些都发生在user命名空间中)返回f3类,其中f1和{{1}是一个实例。


1 宏偶尔会输出f2形式的defrecord形式以及其他一些形式;但是,顶级do是特殊的,因为它们就像它们包装的表单在顶层单独处理一样。