Clojure-defprotocol作为表达式问题的解决方案

Clojure-defprotocol作为表达式问题的解决方案,clojure,polymorphism,Clojure,Polymorphism,在《Clojure的乐趣》一书中,defprotocol是作为表达式问题的解决方案提供的——“希望为现有的具体类实现一组现有的抽象方法,而不必更改定义它们的代码。” 给出的示例如下: (defprotocol Concatenatable (cat [this other])) (extend-type String Concatenatable (cat [this other] (.concat this other))) (cat "House" " of Leav

在《Clojure的乐趣》一书中,
defprotocol
是作为表达式问题的解决方案提供的——“希望为现有的具体类实现一组现有的抽象方法,而不必更改定义它们的代码。”

给出的示例如下:

(defprotocol Concatenatable
  (cat [this other]))

(extend-type String
  Concatenatable
  (cat [this other]
    (.concat this other)))

(cat "House" " of Leaves")
;=> "House of Leaves"

(extend-type java.util.List
  Concatenatable
  (cat [this other]
    (concat this other)))

(cat [1 2 3] [4 5 6])
;=> (1 2 3 4 5 6)
有人认为这在Java这样的语言中是不可能的,但它与下面的语言有什么不同

public class Util {
  public static String cat(final String first,
                           final String second) {
    return first.concat(second);
  }

  public static <T> List<T> cat(final List<T> first,
                                final List<T> second) {
    final List<T> list = new List<T>(first);
    list.addAll(second);
    return list;
  }
}
Clojure函数
cat
不是
String
List
类上的一个方法,而是一个独立的函数,它被重载以接受
String
List
参数


尽管我真的很喜欢Clojure,但我不理解这种构造的优越性。

每当出现一个新类型,您希望应用
cat
函数时,您需要“重新打开”您的
Util
类,并为新的目标类型添加方法重载


表达式问题试图避免这种需要,这样现有类型就不会受到定义的新操作的干扰,现有实现的操作也不会受到想要参与这些操作的新类型的干扰。此处显示的Clojure协议示例无法实现第一个目标,因为向已发布的协议添加新函数需要协议已扩展到的所有类型为该新方法定义一个实现。

好的。您大张旗鼓地发布了这个
cat
Java库,每个人都下载它。它太棒了,我想让我自己的
TVCommercial
类型成为可具体化的,这样我就可以把它发送到你库中操作可具体化对象的部分

但是我不能,因为您调用了
Util.cat(obj1,obj2)
,它对
TVCommercial
没有重载。我无法扩展您的代码来处理我的类型,因为我不拥有您的代码

您可以将Concatenable定义为解决此问题的接口:

interface Concatenable {
  Concatenable cat(Concatenable other);
}
但现在我不能写一个既可具体化又。。。我不知道,一个处理猫的动物处理器。Clojure的协议通过分散分派功能和实现来解决这两个问题:它们分布在各地,而不是某个单一位置。在Java中,您可以选择:

  • 将所有类型分派放在单个开关/案例或重载方法中
  • 定义一个接口,指定一个具有特定名称的方法

Clojure基本上是后者,但由于它使用了名称空间名称,因此没有与认为
cat
是一个好的函数名的其他协议发生冲突的危险。

您提到的名称冲突问题与表达式问题并不真的密切相关。但是,定义接口
Concatenble
是问题的一部分,因为它锁定了只有一个相关操作:
cat
。添加新操作需要再次打开
Concatenable
,或者可能定义参与类型必须实现的其他接口。@seh我不认为我会这样做。如果我们想增加可关联目标必须支持的操作数量,那么我们必须重新定义协议,就像重新定义接口一样。我认为Clojure允许这样做,如果调用未实现的协议函数,只会抛出未经检查的异常,但这只是掩盖问题,而不是解决问题。是的,Clojure在对没有该函数扩展的类型的实例调用协议函数时抛出
IllegalArgumentException
。与协议的契约,也许精神上比文字上更重要,是整个协议将被定义为它扩展到的类型。否则,将功能捆绑在协议中有什么意义?与CL的泛型函数相比,CL的泛型函数仅通过相同的包成员资格弱地聚集在一起,但无法表示一组函数应该一起定义。向已发布的协议添加新函数需要协议已扩展到的所有类型都为该新方法定义一个实现。-我想知道——你试过了吗?如果您执行OP中的代码,然后执行以下代码,您会发现这不是真的:(defprotocol concatenable(cat[this other])(rev[this]))user=>(cat“House”of Leaves”)“House of Leaves”不需要重新定义。不,我没有尝试,但是当您针对字符串调用新的
rev
函数时会发生什么?在这一点上,
String
只是部分实现了协议
可连接的
?也许我应该写下,扩充协议不能像抽象类那样包含默认实现,因此以前定义的类型扩展仍然是定义的,但现在不完整,用这种类型调用新函数将产生一个
IllegalArgumentException
。它们的操作方式相似。区别(如amalloy所强调的)在于,在Java情况下,参与者集由
Util
的编写者定义,而在Clojure情况下,参与者集由
可连接的
的用户定义。
interface Concatenable {
  Concatenable cat(Concatenable other);
}