在clojure中,我想在函数内创建一条记录。
我试过了:
(defn foo []
(defrecord MyRecord [a b])
(let [b (MyRecord. "1" "2")]))
但它会导致异常:
java.lang.IllegalArgumentException: Unable to resolve classname: MyRecord
有什么想法吗?
答案 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
是特殊的,因为它们就像它们包装的表单在顶层单独处理一样。