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之外定义它

在clojure中,我想在函数中创建一个记录

我试过:

(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的编译器在编译时将所提到的类名解析为新表单的第一个参数的原因吗?为什么这需要在编译时进行?