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部分完全替代了前面的部分,我在编写一个宏,该宏会自动为您注入一些方法体时发现了这个问题。

我已经附上一个修复程序,将上述内容展开为预期的输出。

4个答案

0

评论由:stu

在Clojure中,重新定义某物通常替换原始内容,而不是通过合并旧的和新的内容来增强它。这使得更容易本地推理代码的工作方式。有人可能会争辩说以下代码片段就存在您描述的问题

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

我也想知道当前的行为是否可能是某些宏的便利之处。(显然不是您的宏!)我正在更改此票据的类型和标题,以更好地反映请求的性质,并查看BDFL会怎么说。

0

评论由:richhickey

在此票据中需要用更多精确的话来说明。我不知道具体问题是什么,也不知道提出的解决方案是什么。

请注意,接受基接口下的定义是必要的,因此类域不是严格的,也不期望是完整的。

0

评论者:amalloy

Stuart:你给出的两个 foo 形式是完全分开的,要将这两者统一,你必须将它们组合在一起。用户想要一次性定义 foo,对其修改,然后再重新定义 - Clojure.core 类似地使用 let、reduce 等。

按原样编写的 deftype/reify 具有某种相似的“外观”,因为没有任何实际上将 Map 的声明与其函数分组的东西,当给出像 Map 这样的标题两次时,不清楚会发生什么,文档中也没有说明。

我认为区别在于,在 reify 的情况下,这两者在同一顶级形式中,因此编译器可以检测到你正在尝试做“奇怪”的事情,所以对于你的 defn 示例,静默重新定义(对你的示例来说是合理的)是令人惊讶的。有几种解决方案可以减少这种惊讶。

1) 允许或要求 reify 组合事物,如 (reify (Comparable (compare (link: 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 报告)
...