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

时间:2018-07-08 03:17:17

标签: scala inheritance functional-programming typeclass scala-cats

在继承上使用类型类有什么意义?

此函数通过上下文绑定使用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方法。)

两个人都做不到同一件事吗?

2 个答案:

答案 0 :(得分:4)

类型类更灵活。特别是,可以轻松地将类型类改装为现有类型。要实现继承,您需要使用适配器模式,当您具有多个特征时,适配器模式会变得很麻烦。

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

trait Measurable {
  def length: Int
}

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

第一个库为List =>可衡量的定义了一个适配器:

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) = ???

答案 1 :(得分:4)

您说的是在最简单的情况下是可能的,但是在许多情况下,类型类可以为您提供更好的灵活性。

Monoid类型的类为例:

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

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

现在让我们为Monoid之类的东西定义一个Tuple2

implicit def tupleMonoid[A: Monoid, B: Monoid]: Monoid[(A, B)] = ...

现在您处在继承根本无法将您带到任何地方的领域。使用类型类,我们可以为类型类实例添加条件。即当元组的两个类型均为Monoid时,它就是Monoid。有了继承,您将无法做到这一点。