9

与类型的上限进行比较时,我在激励 Scala 中使用类型类时遇到了一些麻烦。

考虑以下代码:

  case class NumList[T <: Complex](xs: Complex*) {
    def sum = (xs fold new Complex(0, 0))(_ + _)
    def map[U <: Complex](f: Complex => U): NumList[U] = NumList(xs.map(f): _*)
    override def toString = "[" + xs.mkString(", ") + "]"
  }

  case class GenList[T](xs: T*) {
    def sum(implicit num: Numeric[T]) = xs.sum
    def map[U](f: T => U) = GenList(xs.map(f): _*)
    override def toString = "[" + xs.mkString(", ") + "]"
  }

  val r = new Real(2)
  val n = new Natural(10)
  val comps = NumList(r, n, r, n)

  println(comps)
  println("sum: " + comps.sum)
  println("sum * 2: " + comps.map(x => x + x).sum)

  val comps2 = GenList(4, 3.0, 10l, 3d)
  println(comps2)
  println("sum: " + comps2.sum)
  println("sum * 2: " + comps2.map(_ * 2).sum)

虽然这两个列表解决了类似的问题,但一个使用数字类型类,另一个使用类型参数的上限。我非常了解技术差异,但我很难理解类型类的核心动机。到目前为止,我发现的最佳动机如下:

虽然子类化或实现接口允许您进行几乎相同的设计,但类型类允许您在每个方法的基础上指定类型的特征,而具有类型T和上限的泛型类在使用它的任何地方都受到U约束。T考虑到这一点,类型类在泛型类中对 T 的特性提供了更细粒度的控制。

有没有非常明确的例子可以激发这种模式?

4

1 回答 1

9

试图简化一个主要方面,类型类试图独立于你的类层次来收集行为。

假设您需要定义一个新的数字类型(使用标准数字运算),但无论出于何种原因,MetaNum您都不能或不会将其设为您的类型的子类。Complex

使用Numerictypeclass,您只需要为您的 提供适当的实例MetaNum,提供所需的操作。

然后你可以创建一个GenList[MetaNum]并对其求和。

你不能用NumList, 因为MetaNum不是Complex. 当您尝试在第二个时刻概括您的操作/数据结构时,您在定义时所做的实现选择NumList将反击您。

结论
Typeclasses 使您可以更自由地扩展您的行为,而不受分层考虑的影响,但代价是一些额外的复杂性和样板文件。

我不知道你在你的问题中是否有同样的意思。

于 2013-04-10T13:54:22.513 回答