Clojure:如何在函数中创建记录?
在clojure中,我想在函数中创建一个记录 我试过:Clojure:如何在函数中创建记录?,clojure,Clojure,在clojure中,我想在函数中创建一个记录 我试过: (defn foo [] (defrecord MyRecord [a b]) (let [b (MyRecord. "1" "2")])) 但它导致了一个例外: java.lang.IllegalArgumentException: Unable to resolve classname: MyRecord 有什么想法吗?关键点 只能在顶层使用defrecord。1 因此,如果您确实需要自定义记录类型,您应该在foo之外定义它
(defn foo []
(defrecord MyRecord [a b])
(let [b (MyRecord. "1" "2")]))
但它导致了一个例外:
java.lang.IllegalArgumentException: Unable to resolve classname: MyRecord
有什么想法吗?关键点
只能在顶层使用defrecord
。1
因此,如果您确实需要自定义记录类型,您应该在foo
之外定义它(在代码中的某个点,在foo
的定义之前进行处理)
否则,您可以使用常规地图。特别是,如果foo
将创建多个“类型”的实体(在概念级别),那么尝试为每个实体创建一个记录类型(一个Java类)可能没有意义;自然的解决方案是使用带有:type
键的映射来指示所表示的实体的种类
为什么它不起作用
问题中的代码没有编译,因为Clojure的编译器在编译时将作为第一个参数提到的类名解析为new
表单。((MyRecord.1“2”)
在宏扩展过程中扩展为(新的MyRecord“1”2”)
。)这里的名称MyRecord
还不能解析为适当的类,因为后者尚未定义(它将在首次调用foo
后由defrecord
表单创建)
为了避免这种情况,你可以做一些可怕的事情,比如
(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
和f2
都不是一个实例
1宏有时可能会输出一个包装在
do
中的defrecord
表单以及一些其他表单;顶级do
s很特别,因为它们的作用就像它们包装的表单是在顶级单独处理的一样。非常感谢!您提到的invokeConstructor
函数帮助我解决了一个非常具有挑战性的谜题:请参阅我的第二个答案,即为什么foo
中的defrecord
部分必须通过eval
执行?乐意帮助。如果我在回答这个问题的时候注意到这个问题的话,我早就把反射器贴在那里了——很高兴这里出现了这个问题。关于上面的foo
,我在一次实验中使用了eval
,以便能够动态地确定所创建记录的名称,然后将其简化为这个版本——我想我当时可以去掉eval
。一旦defrecord
表单的形状是静态固定的,eval
确实是不必要的,尽管在这种情况下,也没有任何理由不使用顶级的defrecord
来获得制作一个正常程序的机会。在函数中使用defrecord
几乎可以保证至少是“对周围代码没有好处”和“造成奇怪而微妙的破坏”,很可能两者都有;我能看到玩它的唯一原因是了解Clojure的实现细节。否则它只是绞刑架级的绳子。。。写这篇文章主要是为了让没有人,通过任何想象,我可以得到这样的印象:我提供了上面的foo
作为生产代码的模型——我只是为了完整性而发布它,因为我认为有时在REPL中滥用Clojure很有趣。你知道Clojure的编译器在编译时将所提到的类名解析为新表单的第一个参数的原因吗?为什么这需要在编译时进行?