为什么更喜欢类型类而不是继承?
Why prefer Typeclass over Inheritance?
根据这个Erik Osheim's slide,他说继承可以解决和类型类一样的问题,
但提到继承有一个问题叫做:
brittle inheritance nightmare
并说继承是
tightly coupling the polymorphism to the member types
他是什么意思?
在我看来,继承擅长于扩展,要么改变现有类型的实现,要么向接口添加新的成员类型(子类型)。
trait Foo { def foo }
class A1 extends Foo{
override def foo: Unit = ???
}
//change the foo implementation of the existing A1
class A2 extends A1 with Foo{
override def foo = ???
}
// add new type B1 to Fooable family
class Bb extends Foo{
override def foo = ???
}
现在在类型类方面:
trait Fooable[T] { … }
def foo[T:Fooable](t:T) = …
class Aa {…}
class Bb {…}
object MyFooable {
implicit object AaIsFooable extends Fooable[Aa]
implicit object B1IsFooable extends Fooable[Bb]
…
}
我看不出有任何理由更喜欢 Typeclass ,我是不是漏掉了什么?
当使用继承来实现临时多态性时,我们可能需要严重污染值对象的接口。
假设我们要实现一个实数和一个复数。没有任何功能,这就像写
一样简单
case class Real(value: Double)
case class Complex(real: Double, imaginary: Double)
现在假设我们要实现
的加法
- 两个实数
- 一个实数和一个复数
- 两个复数
一个使用继承的解决方案(编辑:其实我不确定这是否可以称为继承,因为特征中的方法add
没有实现。但是,在这方面,该示例与 Erik Orheim 的示例没有区别)可能如下所示:
trait AddableWithReal[A] {
def add(other: Real): A
}
trait AddableWithComplex[A] {
def add(other: Complex): A
}
case class Real(value: Double) extends AddableWithComplex[Complex] with AddableWithReal[Real] {
override def add(other: Complex): Complex = Complex(value + other.real, other.imaginary)
override def add(other: Real): Real = Real(value + other.value)
}
case class Complex(real: Double, imaginary: Double) extends AddableWithComplex[Complex] with AddableWithReal[Complex] {
override def add(other: Complex): Complex = Complex(real + other.real, imaginary + other.imaginary)
override def add(other: Real): Complex = Complex(other.value + real, imaginary)
}
因为 add 的实现与 Real
和 Complex
紧密耦合,我们必须在每次添加新类型(例如整数)和每次新操作时扩大它们的接口是需要的(例如,减法)。
Type classes 提供了一种将实现与类型分离的方法。例如,我们可以定义特征
trait CanAdd[A, B, C] {
def add(a: A, b: B): C
}
并使用隐式单独实现加法
object Implicits {
def add[A, B, C](a: A, b: B)(implicit ev: CanAdd[A, B, C]): C = ev.add(a, b)
implicit object CanAddRealReal extends CanAdd[Real, Real, Real] {
override def add(a: Real, b: Real): Real = Real(a.value + b.value)
}
implicit object CanAddComplexComplex extends CanAdd[Complex, Complex, Complex] {
override def add(a: Complex, b: Complex): Complex = Complex(a.real + b.real, a.imaginary + b.imaginary)
}
implicit object CanAddComplexReal extends CanAdd[Complex, Real, Complex] {
override def add(a: Complex, b: Real): Complex = Complex(a.real + b.value, a.imaginary)
}
implicit object CanAddRealComplex extends CanAdd[Real, Complex, Complex] {
override def add(a: Real, b: Complex): Complex = Complex(a.value + b.real, b.imaginary)
}
}
这种解耦至少有两个好处
- 防止
Real
和Complex
接口污染
- 允许引入新的
CanAdd
功能,但不能修改可添加的 classes 的源代码
例如,我们可以定义CanAdd[Int, Int, Int]
来添加两个Int
值而不修改Int
class:
implicit object CanAddIntInt extends CanAdd[Int, Int, Int] {
override def add(a: Int, b: Int): Int = a + b
}
根据这个Erik Osheim's slide,他说继承可以解决和类型类一样的问题, 但提到继承有一个问题叫做:
brittle inheritance nightmare
并说继承是
tightly coupling the polymorphism to the member types
他是什么意思?
在我看来,继承擅长于扩展,要么改变现有类型的实现,要么向接口添加新的成员类型(子类型)。
trait Foo { def foo }
class A1 extends Foo{
override def foo: Unit = ???
}
//change the foo implementation of the existing A1
class A2 extends A1 with Foo{
override def foo = ???
}
// add new type B1 to Fooable family
class Bb extends Foo{
override def foo = ???
}
现在在类型类方面:
trait Fooable[T] { … }
def foo[T:Fooable](t:T) = …
class Aa {…}
class Bb {…}
object MyFooable {
implicit object AaIsFooable extends Fooable[Aa]
implicit object B1IsFooable extends Fooable[Bb]
…
}
我看不出有任何理由更喜欢 Typeclass ,我是不是漏掉了什么?
当使用继承来实现临时多态性时,我们可能需要严重污染值对象的接口。
假设我们要实现一个实数和一个复数。没有任何功能,这就像写
一样简单case class Real(value: Double)
case class Complex(real: Double, imaginary: Double)
现在假设我们要实现
的加法- 两个实数
- 一个实数和一个复数
- 两个复数
一个使用继承的解决方案(编辑:其实我不确定这是否可以称为继承,因为特征中的方法add
没有实现。但是,在这方面,该示例与 Erik Orheim 的示例没有区别)可能如下所示:
trait AddableWithReal[A] {
def add(other: Real): A
}
trait AddableWithComplex[A] {
def add(other: Complex): A
}
case class Real(value: Double) extends AddableWithComplex[Complex] with AddableWithReal[Real] {
override def add(other: Complex): Complex = Complex(value + other.real, other.imaginary)
override def add(other: Real): Real = Real(value + other.value)
}
case class Complex(real: Double, imaginary: Double) extends AddableWithComplex[Complex] with AddableWithReal[Complex] {
override def add(other: Complex): Complex = Complex(real + other.real, imaginary + other.imaginary)
override def add(other: Real): Complex = Complex(other.value + real, imaginary)
}
因为 add 的实现与 Real
和 Complex
紧密耦合,我们必须在每次添加新类型(例如整数)和每次新操作时扩大它们的接口是需要的(例如,减法)。
Type classes 提供了一种将实现与类型分离的方法。例如,我们可以定义特征
trait CanAdd[A, B, C] {
def add(a: A, b: B): C
}
并使用隐式单独实现加法
object Implicits {
def add[A, B, C](a: A, b: B)(implicit ev: CanAdd[A, B, C]): C = ev.add(a, b)
implicit object CanAddRealReal extends CanAdd[Real, Real, Real] {
override def add(a: Real, b: Real): Real = Real(a.value + b.value)
}
implicit object CanAddComplexComplex extends CanAdd[Complex, Complex, Complex] {
override def add(a: Complex, b: Complex): Complex = Complex(a.real + b.real, a.imaginary + b.imaginary)
}
implicit object CanAddComplexReal extends CanAdd[Complex, Real, Complex] {
override def add(a: Complex, b: Real): Complex = Complex(a.real + b.value, a.imaginary)
}
implicit object CanAddRealComplex extends CanAdd[Real, Complex, Complex] {
override def add(a: Real, b: Complex): Complex = Complex(a.value + b.real, b.imaginary)
}
}
这种解耦至少有两个好处
- 防止
Real
和Complex
接口污染 - 允许引入新的
CanAdd
功能,但不能修改可添加的 classes 的源代码
例如,我们可以定义CanAdd[Int, Int, Int]
来添加两个Int
值而不修改Int
class:
implicit object CanAddIntInt extends CanAdd[Int, Int, Int] {
override def add(a: Int, b: Int): Int = a + b
}