泛型参数 V.S. 抽象类型成员

 

 

by Bill Venners
October 7, 2009

http://www.artima.com/weblogs/viewpost.jsp?thread=270195

 

摘要:

在这篇博客中,作者试图回答Scala编程中一个共同的问题:在Scala API设计时,什么时候用泛型参数,什么时候用抽象类型成员。

 

---------------------------------------------------------------------------------------------

学习Scala建一个抽象类型模型的时,一个普遍的问题是如何决定使用泛型参数,还是抽象类型成员。对于不熟悉这两则区别的人来说,Scala的泛型参数有点像Java的泛型参数,只是Java用尖括号,而Scala用方括号。

 

Java泛型 

interface Collection<T> {
    // ...
}
 

 

Scala泛型使用方括号 

// Type parameter version
trait Collection[T] {
  // ..

}
 

Scala的抽象类型成员(Abstract type members)没用和Java等同的。 两个语言中,类,接口(Java),特征(Scala)都可以有方法和字段作为成员。Scala的类或特征(trait)可以有类型成员,下面例子是抽象类型成员:

 

// Type member version

trait Collection {
  type T
  // ...
}

 

这两种情况,抽象类型都可以在子类中具体化。下面的例子中子类用String具体化了类型参数T:

// Type parameter version

trait StringCollection extends Collection[String] {
  // ...
}

 

或者在子类内部具体化类型参数T:

 

// Type member version
trait StringCollection extends Collection {
  type T = String
  // ...
}

 

这看上去,事实上也是,有两种不同的方法达到同一个目的。那么我们怎么做选择呢?我在采访Martin Odersky(Scala创始人)的时候问了这个问题,他最先解释了同时有这两种方法是正交的。

Martin Odersky 写道
总是有两种对抽象的观念:
参数化和抽象成员。Java也有可以同时有这两个,但这取决于你想抽象什么。
用Java你可以有抽象方法,但你不能将方法当作参数。你不能有抽象字段,但可以将传递参数。简单将你可以没用抽象类型成员,但你可以将具体的类型当参数。所以用Java你也可以有这三种方式,只是你在做不同类型的事情时的抽象原则有所不同。你可以说这些差异是简直是专制。

我们试图让Scala在抽象方面变得完全和正交。我们决定对所有这三种成员(类型,字段,方法)的抽象有统一的构造原则。你可以像值参数那样有抽象字段。你可以像参数一样传递方法(或叫函数),或者你能抽象他们。你可以像参数那样具体化类型,或者抽象他们。我们可以在概念上为其他建立一个模型(we can model one in terms of the other)。至少在原则上,我们能对每个不同的参数化做统一的OO抽象。所以在一定意义上说,Scala是更充分和正交(orthogonal )的语言。

 

他同时解释了抽象类型成员和泛型参数在实际应用中的不同:

 

Martin Odersky 写道
在实际应用中,当你在不同时候使用类型参数时,会暴露参数,甚至参数边界(bounds of parameters)。在1998年ECOOP,Kim Bruce, Phil Wadler和我在一篇文章中展示了当你在不知道的情况下增加的东西会编程二次方。所以这是个很好的理由不要参数,而使用抽象成员,因为他们不会给你成倍的增加。
  当Martin给我这个答案的时候,我并不确认是不是真的理解了他所讲的,但是现在我可以给出更多的不同点。我总是倾向于使用泛型参数,因为我来自C++和Java背景,更加熟悉类型参数化和类型成员。尽管如此,我遇到一个设计问题,我使用了抽象类型成员解决了问题,而不是泛型类型参数。
  这个问题是当我设计ScalaTest时想提供traits让用户写的测试能传入特定的对象。这可以给大家选择使用函数编程方式写测试,而不只是像命令式的JUnit中的setUp,tearDown方法。要提供这种选择,我需要让用户能通过具体类型参数或者成员来指示特定对象的类型。换句话说,我要在ScalaTest API中提供这样的trait.
// Type parameter version
trait FixtureSuite[F] {
  // ...
}
或者 :
// Type member version
trait FixtureSuite {
  type F
  // ...
}
一种情况是,F是特定参数穿入的,suite的子类需要提供具体类型。这里的测试具体子类需要将StringBuilder传入到每个测试,使用类型参数达到这个目的:
// Type parameter version
class MySuite extends FixtureSuite[StringBuilder] {
  // ...
}
 同时另一个例子是将StringBuilder当作抽象类型成员值传递给每个测试:
// Type member version
class MySuite extends FixtureSuite {
  type F = StringBuilder
  // ...
}
 
So far there's not much difference. However, one other use case I had is that I wanted to allow people to create traits that provide a concrete definition for the fixture type and could be mixed into suite classes. This would allow users to encode commonly used fixtures into helper traits that could be mixed into any of their suite classes that need them. This is where a difference showed up. Here's how you'd write that trait using the generic type parameter approach:



你可能感兴趣的:(编程,scala,JUnit,F#,OO)