哪种类型的系统足够强大来表达这一点?

时间:2011-10-10 20:52:56

标签: generics types interface type-systems

假设我们定义了这样的接口:

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<T>内的元素上调用哈希,因此T必须是Hashable。所以我们必须这样做:

class List<T : Hashable> : Hashable {
   ... same here ...
}

另一方面,我们还希望能够使用不可清除的元素构造列表。在这种情况下,我们根本不希望List<T>实施Hashable。所以:

List<AHashableType>      is itself Hashable
List<NotAHashableType>   is not Hashable

此外,我们希望哈希方法是多态的(OO类型是多态的,而不是参数多态的)。因此,如果我们构建一个List<Foo>,其中包含BarBaz,其中FooHashableBar以及{{1 Baz具有不同Foo方法的子类型,那么hash()应该在运行时调用正确的哈希方法。

这似乎是语言应该能够表达的基本内容。在不同情况下会出现相同的模式,例如List<Foo>.hash()ToString()。到目前为止,我还没有找到该语言的语言和解决方案,这使我能够以类型安全的方式表达这一点。你知道这种语言和解决方案吗? Haskell与它的类型类非常接近,但遗憾的是它们是在编译时调度的。

4 个答案:

答案 0 :(得分:2)

也许我误读了OP,但似乎有必要在编译时知道给定的List是否为Hashable。否则,对非hash() Hashable的{​​{1}}来电会怎样?

但是,我确信CLOS存在解决方案。 (多次发送肯定会派上用场,我很清楚它更有表现力的处理子类的方式。)

答案 1 :(得分:2)

不幸的是,你需要一些关于泛型的条件类型约束(注释模板可以通过部分特化和私有继承来支持它)。我不知道有静态类型的语言,但可以通过扩展方法进行monkeypatching解决。这是C#中的一个例子。

public static int GetHash<T>(this List<T> list) where T:IEquatable<T>
{
      if(list == null) throw new ArgumentNullException("list");
      int h = 0;
      foreach(var x in list){
         h = CombineHash(h,x.GetHashCode());
      }
      return h;
}

CombineHash结合您的哈希码。 请注意,默认情况下,C#中的每个方法实际上都有一个哈希码,但IEquatable<T>是一个指定GetHashCode作为成员的接口。 因此List<int>.可以调用GetHash,但List<object>不能object不能实现接口。 由于GetHashCode不是特别有趣,所以我们说这样做了:

interface IFoo{
    string Bar();
}
class Foo:IFoo{
    public virtual string Bar(){return "Foo";}
}
class Baz:Foo{
     public override string Bar(){return "Baz";}
}
public static string Bar(this List<T> list) where T:IFoo
{
    if(list == null) throw new ArgumentNullException("list");
    StringBuilder sb = new StringBuilder();
    foreach(var x in list){
       sb.Append(x.Bar());
    }
    return sb.Bar();
}

此处由于我们将T限制为IFoo,我们现在可以在List<IFoo>List<Foo>List<Baz>上调用此方法但不是List<int>int未实现IFoo。 e.g。

public static void SomeMethod()
{
      Console.WriteLine(new List<IFoo>{new Baz(),new Foo()}.Bar()); //compiles
      Console.WriteLine(new List<int>{1,2,3,4,}.Bar()); // does not compile.
}

答案 2 :(得分:1)

我认为将您描述的两个问题分开是有用的。第一种是有条件地实现接口。就像你说的,Haskell类型类可以做到这一点。它看起来像是:

class Hashable t where
  hash :: t -> Int

instance Hashable Int where
  hash i = i

-- Reads as "(List t) is Hashable if t is Hashable"
instance (Hashable t) => Hashable (List t) where
  hash l = ...

第二个问题是OO多态,你可以在Haskell中模仿类似函数指针的机制。

-- Pretending we have some kind of OO method dispatch here
instance Hashable Foo where
    hash foo = foo.computeHash()

class Foo
   computeHash()

我读过几篇论文,将这种“条件接口实现”添加到类似Java的语言中。其中之一是JavaGI(http://homepages.cwi.nl/~ralf/JavaGI/),它还增加了为最初未定义的数据类型实现接口的能力(如Haskell类型类)。我记不起其他论文的内容了。

答案 3 :(得分:0)

您描述的问题是IMHO在许多情况下违反接口隔离原则的良好理由,特别是在Java或.NET等有限类型系统中。虽然能够使用类型检查作为确保仅要求对象执行它们能够执行的操作的手段具有很大的价值,但在程序运行之前不能确定所有能力问题。在许多情况下,代码将接收对具有可选功能的对象的引用,如果存在则应该使用它,如果不存在则可以使用。如果界面包括用于这种可选能力的方法,以及确定是否应该使用那些方法的手段,那么这些对象的组合或聚合同样可以暴露这种可选能力。如果接口仅包括保证能够执行的实施者的动作,那么在构造时,不可变聚合必须识别它将要宣传的所有能力,并且可变聚合不能宣传他们不需要来自每个项目的任何能力这是补充的。

如果某个接口的某些实现具有某种功能而其他接口不具备这种能力,并且相同的对象必须处理具有该功能的事物和不具备该功能的事物的可能性很大能够在出现时使用这种能力,我建议在界面中包括能力以及确定何时可用的方法。例如,如果一个集合标识IFoo个实例,其中一些可以Boz(),并且集合本身应该实现IFoo并且对集合调用Boz应该是合法的只有当所有元素都可以Boz时,我才会建议IFoo包含Boz()以及CanBoz属性。 CanBoz属性可以询问集合中的每个项目是否可以Boz,并且仅当所有包含的项目都这样做时才返回肯定。您将失去可能来自更严格的类型检查的编译时保证,但是您可以在静态类型检查不允许的情况下使用该方法。