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

Wei*_*Lin 3 inheritance functional-programming scala typeclass scala-cats

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

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

def f[A : Monad](x:A) = ??? 
Run Code Online (Sandbox Code Playgroud)

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

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

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

(我们现在还获得了 flatMap 方法。)

难道两者没有达到同样的目的吗?

Bri*_*hon 5

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

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

trait Measurable {
  def length: Int
}

trait Functor[A] {
  def map[B](f: A => B): Functor[B]
}
Run Code Online (Sandbox Code Playgroud)

第一个库为 List => Measurable 定义了一个适配器,很有帮助:

class ListIsMeasurable(ls: List[_]) extends Measurable {
  def length = ls.length
}
Run Code Online (Sandbox Code Playgroud)

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

def foo(x: Measurable with Functor) = ???
Run Code Online (Sandbox Code Playgroud)

当然,我们应该希望能够通过它List。然而,我们的适配器在这里毫无用处,我们必须编写另一个适配器来List符合Measurable with Functor. 一般来说,如果您有n可能适用于 的接口List,那么您就有2^n可能的适配器。另一方面,如果我们使用类型类,则不需要第三个类型类实例的额外样板:

def foo[A : Measurable : Functor](a: A) = ???
Run Code Online (Sandbox Code Playgroud)