在比较类型的上限时,我在使用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
和上限的通用类{ {1}}在任何地方约束U
。考虑到这一点,类型类提供了对泛型类中T的特征的更细粒度的控制。
是否有任何非常明确的例子激励这种模式?
答案 0 :(得分:9)
试图简化一个主要方面,类型类试图独立于类层次结构收集行为。
假设您需要定义一个新的数字类型MetaNum
(使用标准数字运算),但无论出于何种原因,您都不能或不会将其作为Complex
类型的子类。< / p>
使用Numeric
类型类,您只需为MetaNum
提供适当的实例,即可提供所需的操作。
然后你可以创建一个GenList[MetaNum]
并对其进行总结。
您无法使用NumList
执行此操作,因为MetaNum
不是Complex
。当您尝试在第二时刻概括操作/数据结构时,您在定义NumList
时所做的实现选择会向您发出刺激。
结论
类型规则为您提供了更多的自由,可以独立于层次考虑而扩展您的行为,但代价是一些额外的复杂性和样板。
我不知道你的问题是否意思相同。