Clojure:如何在函数内创建记录?
在 clojure 中,我想在函数内创建一条记录。
我尝试过:
(defn foo []
(defrecord MyRecord [a b])
(let [b (MyRecord. "1" "2")]))
但它会导致异常:
java.lang.IllegalArgumentException: Unable to resolve classname: MyRecord
有什么想法吗?
In clojure, I would like to create a record inside a function.
I tried:
(defn foo []
(defrecord MyRecord [a b])
(let [b (MyRecord. "1" "2")]))
But it causes an exception:
java.lang.IllegalArgumentException: Unable to resolve classname: MyRecord
Any idea?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
要点
您应该只在顶层使用
defrecord
。1因此,如果您确实需要自定义记录类型,则应该在
foo (在代码中的某个时刻,在
foo
的定义之前进行处理)。否则,您可以只使用常规地图。特别是,如果 foo 将创建多个“类型”的实体(在概念层面),那么尝试为每个“类型”创建记录类型(Java 类)可能没有意义;自然的解决方案是使用持有
:type
键的映射来指示所表示的实体的类型。为什么它不起作用
问题中的代码无法编译,因为 Clojure 的编译器在编译时将作为第一个参数提到的类名解析为
new
形式。 ((MyRecord. "1" "2")
在宏扩展过程中扩展为(new MyRecord "1" "2")
。)这里的名称MyRecord
尚无法解析为适当的类,因为后者尚未定义(它将在foo
首次创建后由defrecord
形式创建)称为)。为了解决这个问题,您可以做一些可怕的事情,例如
导致小猫通过反射调用其
finalize
方法,从而导致可怕的死亡。最后要说的是,上述可怕的版本可以工作,但它也会在每次调用时以相同的名称创建一个新的记录类型。这会导致奇怪的事情发生。尝试以下示例来体验一下:
此时,
(Class/forName "user.Foo")
(再次假设所有这一切都发生在user
命名空间中)返回f3
的类,其中f1
和f2
都不是实例。1 宏有时可能会输出封装在
do
中的defrecord
形式以及其他一些形式;不过,顶级do
很特殊,因为它们的作用就好像它们包装的表单是在顶级单独处理的一样。The key points
You should only use
defrecord
at top level.1So, if you do need a custom record type, you should define it outside of
foo
(at some point in your code which gets processed beforefoo
's definition).Otherwise, you could just use a regular map. In particular, if
foo
will be creating entities of multiple "types" (at the conceptual level), it probably makes no sense to try to create a record type (a Java class) for each; the natural solution would be to use maps holding a:type
key to indicate the sort of entity represented.Why it doesn't work
The code from the question doesn't compile, because Clojure's compiler resolves class names mentioned as first arguments to
new
forms at compile time. ((MyRecord. "1" "2")
is expanded to(new MyRecord "1" "2")
during the macro expansion process.) Here the nameMyRecord
cannot yet be resolved to the appropriate class, because the latter has not yet been defined (it would be created by thedefrecord
form afterfoo
was first called).To get around this, you could do something horrible like
which causes a kitten to have its
finalize
method invoked through reflection, resulting in a gruesome death.As a final remark, the above horrible version would sort of work, but it would also create a new record type under the same name at each invocation. This causes weirdness to ensue. Try the following examples for a flavour:
At this point,
(Class/forName "user.Foo")
(assuming again that all this happens in theuser
namespace) returns the class off3
, of which neitherf1
norf2
is an instance.1 Macros occasionally might output a
defrecord
form wrapped in ado
along with some other forms; top-leveldo
s are special, though, in that they act as if the forms they wrap were individually processed at top level.