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

发布于 2025-01-06 07:11:53 字数 280 浏览 2 评论 0原文

在 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

硪扪都還晓 2025-01-13 07:11:53

要点

您应该只在顶层使用 defrecord1

因此,如果您确实需要自定义记录类型,则应该在 foo (在代码中的某个时刻,在 foo 的定义之前进行处理)。

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

为什么它不起作用

问题中的代码无法编译,因为 Clojure 的编译器在编译时将作为第一个参数提到的类名解析为 new 形式。 ((MyRecord. "1" "2") 在宏扩展过程中扩展为 (new 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 的类,其中 f1f2 都不是实例。


1 宏有时可能会输出封装在 do 中的 defrecord 形式以及其他一些形式;不过,顶级 do 很特殊,因为它们的作用就好像它们包装的表单是在顶级单独处理的一样。

The key points

You should only use defrecord at top level.1

So, if you do need a custom record type, you should define it outside of foo (at some point in your code which gets processed before foo'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 name MyRecord cannot yet be resolved to the appropriate class, because the latter has not yet been defined (it would be created by the defrecord form after foo was first called).

To get around this, you could do something horrible like

(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))

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:

(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

At this point, (Class/forName "user.Foo") (assuming again that all this happens in the user namespace) returns the class of f3, of which neither f1 nor f2 is an instance.


1 Macros occasionally might output a defrecord form wrapped in a do along with some other forms; top-level dos are special, though, in that they act as if the forms they wrap were individually processed at top level.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文