为什么 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方法。)
不是都达到了同样的目的吗?
类型类更灵活。特别是,可以轻松地将类型类改造为现有类型。要通过继承来做到这一点,您需要使用适配器模式,当您有多个特征时,这会变得很麻烦。
例如,假设您有两个库,它们使用继承分别添加了特征 Measurable
和 Functor
:
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
时。对于继承,您不能这样做。
使用类型类而不是继承有什么意义?
这是一个通过上下文绑定使用 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方法。)
不是都达到了同样的目的吗?
类型类更灵活。特别是,可以轻松地将类型类改造为现有类型。要通过继承来做到这一点,您需要使用适配器模式,当您有多个特征时,这会变得很麻烦。
例如,假设您有两个库,它们使用继承分别添加了特征 Measurable
和 Functor
:
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
时。对于继承,您不能这样做。