此函数通过上下文绑定使用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方法。)
两个人都做不到同一件事吗?
答案 0 :(得分:4)
类型类更灵活。特别是,可以轻松地将类型类改装为现有类型。要实现继承,您需要使用适配器模式,当您具有多个特征时,适配器模式会变得很麻烦。
例如,假设您有两个使用继承分别添加了特征Measurable
和Functor
的库:
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
。有了继承,您将无法做到这一点。