为什么 Scala Cats 使用类型类而不是继承?

Why does Scala Cats use typeclasses instead of inheritance?

使用类型类而不是继承有什么意义?

这是一个通过上下文绑定使用 Monad 类型类的函数:

def f[A : Monad](x:A) = ??? 

(是的,我们现在有了 flatMap 方法)

然而,这使用了带有子类型绑定的继承:

def f[A <: Monad](x:A) = ???  
f(x) // where x is a CatsList which implements Monad trait.

(我们现在也得到了flatMap方法。)

不是都达到了同样的目的吗?

类型类更灵活。特别是,可以轻松地将类型类改造为现有类型。要通过继承来做到这一点,您需要使用适配器模式,当您有多个特征时,这会变得很麻烦。

例如,假设您有两个库,它们使用继承分别添加了特征 MeasurableFunctor

trait Measurable {
  def length: Int
}

trait Functor[A] {
  def map[B](f: A => B): Functor[B]
}

第一个库为 List => Measurable 定义了一个适配器:

class ListIsMeasurable(ls: List[_]) extends Measurable {
  def length = ls.length
}

第二个库对 List => Functor 做了同样的事情。现在我们要编写一个函数,它接受具有 length 和 map 方法的东西:

def foo(x: Measurable with Functor) = ???

当然希望能传一个List。然而,我们的适配器在这里没有用,我们必须再写一个适配器来使 List 符合 Measurable with Functor。通常,如果您有 n 个可能适用于 List 的接口,则您有 2^n 个可能的适配器。另一方面,如果我们使用类型类,就不需要第三个类型类实例的额外样板:

def foo[A : Measurable : Functor](a: A) = ???

您所说的对于最简单的情况是可行的,但是在很多情况下键入 classes 可以为您提供更好的灵活性。

Monoid 类型为例 class:

trait Monoid[A] {
  def empty: A
  def combine(x: A, y: A): A
}

如果您尝试用继承来表达 empty 函数,您会发现这几乎是不可能的。这是因为类型 classes 对类型而不是单个 class 实例进行操作。

现在让我们为 Tuple2:

定义一个 Monoid
implicit def tupleMonoid[A: Monoid, B: Monoid]: Monoid[(A, B)] = ...

现在你处在一个继承无法让你到达任何地方的领域。使用 type classes 我们可以为 type class 实例添加条件。 IE。一个元组是一个 Monoid,当它的两个类型也是 Monoid 时。对于继承,您不能这样做。