Scala中类型类的动机是什么?

Fel*_*lix 9 design-patterns scala typeclass type-bounds

在与类型的上限进行比较时,我在使用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)
Run Code Online (Sandbox Code Playgroud)

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

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

是否有任何非常明确的例子激励这种模式?

pag*_*_5b 9

试图简化一个主要方面,类型类试图独立于类层次结构收集行为.

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

使用Numeric类型类,您只需为您提供适当的实例MetaNum,提供所需的操作.

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

你不能这样做NumList,因为MetaNum不是Complex.NumList当您尝试在第二时刻概括操作/数据结构时,您在定义时所做的实现选择会向您发出刺激.

结论类型
规则使您可以更自由地独立于层次结构考虑扩展您的行为,代价是一些额外的复杂性和样板.

我不知道你的问题是否意味着同样的问题.

  • 这是确切的推理.通常,类型类可以更简洁,然后它们在Scala中.他们编码的方式允许它们以更大的灵活性使用,然后可以像vanilla Haskell那样,但Haskell的实现更加简洁.另一个很好的例子是在你的Hierarchy中添加Serialization之类的东西,在层次结构的顶部添加接口会破坏你所有的旧类.使用类型类允许以递增方式实现功能,特别是仅在所需类型上实现. (2认同)
  • 正如@jroesch所说,强调类型类在scala中作为模式实现(即不是语言特性)这一事实是有用的,但这个概念取自其他语言(如haskell),其中特征嵌入在语言本身.例如,haskell不是OO语言,没有继承机制,因此类型类特征用于通过自定义函数实现在不同数据类型上派生公共功能.这就是为什么在scala上类型类似乎是多余的,因为scala与对象继承有一个功能重叠. (2认同)