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) ; 首个参数消失了,这是一个错误吗?

我还怀疑当前的行为可能是某些宏的一个便捷方式。(显然不对您的!)我正在更改工单的类型和标题,以更好地反映请求的性质,并看看 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报告)
...