请在2024 Clojure 状态调查!中分享您的看法。

欢迎!有关如何工作的更多信息,请参阅关于页面。

0
Clojure

reify, deftype 等在指定两次类时静默失败

(macroexpand '(reify Map (size (link: this) 0),

                 Counted (count (link: this) 0), 
                 Map (keySet (link: this) nil)))

;=> (reify* (link: Counted Map) (count (link: this) 0) (keySet (link: this) nil))

后面的 Map 部分完全取代了前者,这是我在编写一个将一些自动化方法体注入 reify 的宏时发现的。

我已经附上一个修复方案,以便上述代码扩展到预期的(链接:对我来说是这样的)输出。

4 个答案

0

评论者:stu

在 Clojure 中,通常重新定义某物是替换原有内容,而不是通过合并新旧内容来增强它。这使得本地理解代码的工作方式变得容易。有人可能会说以下代码片段有您所描述的相同类型的问题

(defn foo [a] 1) (defn foo [a b] 1) (foo 1) ; 第一个参数是否丢失是错误吗?

我也想知道当前行为可能是一些宏的便利。(显然不是您的!)我将更改工单的类型和标题,以更好地反映请求的性质,并看看 BDFL 会说什么。

0

评论者:richhickey

在这个工单中的每一件事都需要更精确地说明。我不知道具体的问题所在,也不知道提出的解决方案。

需要注意的是,必须接受基础接口下的定义,因此类区域不是严格的,也不预期是完整的。

0

评论者:amalloy

Stuart:你给出的两个foo形式是完全独立的,要将这两种行为统一,您需要将它们分组。用户想要一次性定义foo,对它进行修改,然后重新定义它是完全合理的——clojure.core 就使用 let、reduce 等进行类似操作。

按现在的写法,deftype/reify 有一定的相似之处——因为没有任何物理上分组 Map 的声明和相关函数,所以当 Heading 类似于 Map 重复出现时,并不清楚会发生什么,而且文档中也没有说明。

我认为差异在于 reify 情况下,两者位于相同的顶级形式中,因此编译器可以检测到您正在尝试做一些“奇怪”的操作,因此 defn 示例中的无声重新定义(对于您的示例是合理的)会让人感到惊讶。有几种解决方案可以减少这种惊讶:

1) 允许或要求 reify 按组组织东西,就像 (reify (Comparable (compare (this other) 1))) 一样。这样,将 Comparable 与其方法显式分组有两个目的:它暗示了其他为 Comparable 定义的实现应该包含在那个分组中;并且使它更容易执行操作,因为您只需遍历形式,找到 Comparable,然后插入另一个定义。

2) 如果接口被指定两次,则抛出异常。这并不是最佳选择,因为用户自己分组东西可能是一件很麻烦的事情,而根据已有的分组,deftype 做起来比较容易。但是,它可以通过声明“不允许”而不是让用户猜测出了什么问题,来避免混淆和惊讶。

3) 将我的原始示例代码解释为打开 Map 接口,添加实现,然后以后再添加更多实现。

我原本希望 reify 首先实现(1),但在此阶段,我相信语法不向后兼容,因此这似乎不是一个好主意。我想,(2) 或 (3) 都可以,而且它们似乎都比当前令人困惑的行为有所改进。当然,我更倾向于(3),但我可以理解希望使 reify 拒绝具有不明显意图的语法,而不是将其解释为我认为最有用的意图。

0
参考:https://clojure.atlassian.net/browse/CLJ-821(由 amalloy 报告)
...