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 的宏时发现的。

我已经附上一个修正,以便使其扩展为预期的 (link:对我而言,至少) 输出。

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(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报告)
...