假设我们定义了一个这样的接口:
interface Hashable {
int hash();
}
现在我们可以创建实现这个接口的类。例如 List
类:
class List<T> : Hashable {
int hash(){
int h = 0;
foreach(x in this){
h = combineHash(h,x.hash());
}
return h;
}
}
问题是我们对 List
内的元素调用 hash,因此 T 必须是 Hashable
。所以我们必须这样做:
class List<T : Hashable> : Hashable {
... same here ...
}
另一方面,我们也希望能够使用不可散列的元素构造列表。在这种情况下,我们根本不希望 List
实现 Hashable
。所以:
List<AHashableType> is itself Hashable
List<NotAHashableType> is not Hashable
此外,我们希望哈希方法是多态的(OO 类型的多态,而不是参数多态)。因此,如果我们构造一个包含 Bar
和 Baz
的 List
,其中 Foo
是Hashable
、Bar
和 Baz
是 Foo
的子类型,具有不同的 hash()
方法,那么List.hash()
应在运行时调用正确的哈希方法。
这似乎是语言应该能够表达的基本事物。相同的模式出现在不同的情况下,例如使用 ToString()
和 IComparable
。到目前为止,我还没有找到一种语言和该语言的解决方案,可以让我以类型安全的方式表达这一点。您知道这样的语言和该语言的解决方案吗? Haskell 的类型类非常接近,但不幸的是这些是在编译时调度的。
Suppose that we define an interface like this:
interface Hashable {
int hash();
}
Now we can make classes that implement this interface. For example a List
class:
class List<T> : Hashable {
int hash(){
int h = 0;
foreach(x in this){
h = combineHash(h,x.hash());
}
return h;
}
}
The problem is that we call hash on elements inside the List<T>
, so T has to be Hashable
. So we have to do:
class List<T : Hashable> : Hashable {
... same here ...
}
On the other hand, we also want to be able to construct lists with elements that are not hashable. In that case we simply don't want List<T>
to implement Hashable
. So:
List<AHashableType> is itself Hashable
List<NotAHashableType> is not Hashable
Furthermore, we want the hash method to be polymorphic (the OO kind of polymorphic, not parametrically polymorphic). So if we construct a List<Foo>
with Bar
's and Baz
's inside, where Foo
is Hashable
and Bar
and Baz
are subtypes of Foo
with different hash()
methods, then that List<Foo>.hash()
should call the right hash method at runtime.
This seems like a basic thing that languages should be able to express. The same pattern comes up in different cases, for example with ToString()
and with IComparable
. So far I haven't found a language and a solution in that language that lets me express this in a type safe way. Do you know of such a language and a solution in that langauge? Haskell comes quite close with its type classes, but unfortunately these are dispatched at compile time.
发布评论
评论(4)
不幸的是,您需要一些泛型的条件类型约束(注释模板可以通过部分专业化和私有继承来支持这一点)。我不知道有哪种静态类型语言具有它们,但可以通过扩展方法使用猴子补丁来解决。这是 C# 中的示例。
组合哈希 (CombineHash) 组合您的哈希码。
请注意,C# 中的每个方法实际上默认都有一个哈希码,但
IEquatable
是一个将GetHashCode
指定为成员的接口。因此
List.
可以调用GetHash
,但List
这里,由于我们将
T
约束为IFoo
,所以我们现在可以调用它List
、List
或List
上的方法,但不是List
asint
未实现IFoo
。例如
Unfortunately, you need some how a conditional type constraint for generics (note templates could support this through partial specialization and private inheritance). I don't know a staticly typed language that has them but could be solved with monkeypatching through extension methods.Here is an example in C#.
Where CombineHash combines your hashcodes.
Note that every method in C# actually has a hashcode by default, but
IEquatable<T>
is an interface that specifiesGetHashCode
as a member.Thus
List<int>.
can callGetHash
, butList<object>
can't asobject
doesn't implement the interface.Since
GetHashCode
isn't particularly interesting let's say we did this:Here since we constrained
T
To be anIFoo
we are now able to invoke this method on either aList<IFoo>
,List<Foo>
, or aList<Baz>
but not aList<int>
asint
doesn't implementIFoo
.e.g.
也许我误读了OP,但似乎有必要在编译时知道给定的
List
是否是Hashable
。否则,在非Hashable
List
上调用hash()
会发生什么情况?不过,我确信 CLOS 存在解决方案。 (多重分派肯定会派上用场,而且我隐约意识到它处理子类的更具表现力的方式。)
Maybe I'm misreading the OP, but it seems like it'd be necessary to know at compile-time whether a given
List
wasHashable
or not. Otherwise, what would happen to a call tohash()
on a non-Hashable
List
?I'm sure, however, that a solution exists with the CLOS. (Multiple dispatch would certainly come in handy, and I'm dimly aware of its more expressive way of dealing with subclasses.)
我认为将您描述的两个问题分开是有用的。第一个是有条件地实现接口。就像你说的,Haskell 类型类就是这样做的。它看起来像:
第二个问题是 OO 多态性,您可以在 Haskell 中使用类似函数指针的机制来模拟它。
我读过几篇论文,将这种“条件接口实现”添加到类似 Java 的语言中。其中之一是 JavaGI (http://homepages.cwi.nl/~ralf/JavaGI/),它还增加了为最初未定义的数据类型(如 Haskell 类型类)实现接口的能力。我不记得另一篇论文叫什么了。
I think it's useful to separate the two issues you've described. The first is conditionally implementing an interface. Like you said, Haskell type classes do this. It would look something like:
The second issue is OO polymorphism, which you can mimic in Haskell with a function-pointer-like mechanism.
I've read a couple papers that add this sort of "conditional interface implementation" to a Java-like language. One of them is JavaGI (http://homepages.cwi.nl/~ralf/JavaGI/), which also adds the ability to implement an interface for a data type that you didn't originally define (like Haskell type classes). I can't remember what the other paper was called.
恕我直言,您描述的问题是在许多情况下违反接口隔离原则的一个很好的理由,特别是在有限类型的系统(例如 Java 或 .NET 的系统)中。虽然能够使用类型检查作为确保仅要求对象执行它们能够执行的操作的手段具有很大的价值,但并非所有能力问题都可以在程序运行之前决定。在许多情况下,代码将收到对具有可选功能的对象的引用,如果存在,则应使用该对象,如果不存在,则应解决该问题。如果接口包括用于此类可选能力的方法,以及确定是否应使用这些方法的方法,则此类对象的组合或聚合同样可以公开此类可选能力。如果接口仅包含实现者保证能够执行的操作,那么不可变聚合必须在构造时识别它想要宣传的所有功能,而可变聚合不能宣传它们不需要从每个项目中获得的任何功能已添加。
是否存在接口的某些实现具有而其他实现不具有的能力,以及是否很可能相同的对象必须处理具有该能力的事物和不具有该能力的事物,并且应该能够使用当存在这种能力时,我建议在界面中包含该能力以及确定该能力何时可用的方法。例如,如果一个集合标识
IFoo
实例,其中一些实例可以Boz()
,并且集合本身应该实现IFoo
并调用Boz 时,集合上的 >Boz 才应该是合法的,我建议让
IFoo
包含Boz() 以及
CanBoz
属性。CanBoz
属性可以询问集合中的每个项目是否可以Boz
,只有当所有包含的项目都这样做时才返回肯定。您将失去可能来自更严格的类型检查的编译时保证,但您将能够在静态类型检查不允许的情况下使用该方法。The problem you describe is IMHO a good justification for going against the Interface Segregation Principle in a number of situations, especially in limited type systems such as those of Java or .NET. While there is great value in being able to use type checking as a means of ensuring that objects are only asked to perform actions of which they are capable, not all questions of ability can be decided before a program is run. There are many situations where code will receive a reference to an object with optional abilities which it should use if present and work around if not. If an interface includes methods for such optional abilities, along with a means of determining whether those methods should be used, then compositions or aggregates of such objects can likewise expose such optional abilities. If interfaces only include actions which implementers are guaranteed to be able to perform, then an immutable aggregation must, on construction, identify all abilities it's ever going to want to advertise, and mutable aggregations cannot advertise any abilities they wouldn't require from every item that's added.
If there is an ability which some implementations of an interface will have and others will not, and if there's a significant likelihood that the same objects will have to deal with things that have the ability and things that don't, and should be able use such ability when present, I would suggest including the ability in the interface along with a means of determining when it is available. For example, if a collection identifies
IFoo
instances, some of which canBoz()
, and the collection itself should implementIFoo
and callingBoz
on the collection should be legal when and only when all elements canBoz
, I'd suggest havingIFoo
includeBoz()
along with aCanBoz
property. TheCanBoz
property can ask each item in the collection whether it canBoz
, and return affirmative only when all the contained items do so. You would lose the compile-time assurances that could come from stricter type-checking, but you would gain the ability to use the method in those cases where static type checking could not allow it.