为什么喜欢Typeclass超过继承? [英] Why prefer Typeclass over Inheritance?
问题描述
但提到继承有一个问题叫:
脆性继承噩梦
并且表示继承是
紧密耦合多态到会员类型
他是指什么?
在我看来,继承很适合扩展,既可以改变现有类型的实现,也可以向接口添加新的成员类型(子类型)。
trait Foo {def foo}
class A1 extends Foo {
override def foo:Unit = ???
}
//改变现有A1
的foo实现类A2扩展A1与Foo {
覆盖def foo = ???
}
//将新类型B1添加到Fooable系列
类Bb延伸Foo {
覆盖def foo = ???
}
现在就类型分类而言:
trait Fooable [T] {...}
def foo [T:Fooable](t:T)= ...
class Aa {...}
class Bb {...}
object MyFooable {
隐式对象AaIsFooable extends Fooable [Aa]
隐式对象B1IsFooable extends Fooable [Bb]
...
我没有看到任何理由我喜欢Typeclass,我错过了什么?
当使用继承来实现ad-hoc多态时,我们可能需要严重污染我们的价值对象的接口。
假设我们想要实现一个实数和一个复数。没有任何功能,这就像写作一样简单
case class Real(值:Double)
case class Complex(real:Double,imaginary:Double)
现在假设我们要实现
- 两个实数
- 一个实数和一个复数
- 两个复数
使用继承的解决方案( Edit:)其实,我不确定这是否可以被称为继承,因为特征中的方法 add
没有实现,但在这方面,这个例子与Erik Orheim的例子没有区别)可能看起来像这样:
trait AddableWithReal [A] {
def add(other:Real):A
}
trait AddableWithComplex [A] {
def add(other:Complex):A
}
case class Real(值:Double)extends AddableWithComplex [Complex ]用AddableWithReal [Real] {
覆盖def a dd(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)使用AddableWithReal [Complex]扩展AddableWithComplex [Complex] {
覆盖def add(other:Complex):Complex = Complex (real + other.real,imaginary + other.imaginary)
覆盖def add(other:Real):Complex = Complex(other.value + real,imaginary)
}
由于add的实现与 Real
以及 Complex
,每次添加新类型(例如整数)和每次需要新操作时(例如减法),我们都必须扩大它们的接口。
类型类提供了一种将实现与类型分离的方法。例如,我们可以定义特征
trait CanAdd [A,B,C] {
def add(a :A,B:B):C
}
并分别使用implicits
object Implicits {
def add [A,B,C](a:A,b:B)(隐含的ev:CanAdd [A,B,C]):C = ev.add(a,b)
隐式对象CanAddRealReal extends CanAdd [Real,Real,Real] {
override def add(a: Real,b:Real):Real = Real(a.value + b.value)
}
隐式对象CanAddComplexComplex扩展CanAdd [Complex,Complex,Complex] {
覆盖def add(a :Complex,b:Complex):Complex = Complex(a.real + b.real,a.imaginary + b.imaginary)
}
隐式对象CanAddComplexReal扩展CanAdd [Complex,Real,Complex] {
覆盖def add(a:Complex,b:Real):Complex = Complex(a.real + b.value,a.imaginary)
}
隐式对象CanAddRealComplex扩展了CanAdd [Real,复杂,复杂] {
覆盖def add(a:Real,b:Complex):Complex = Complex(a.value + b.real,b.imaginary)
}
}
这种解耦至少有两个好处:
$ b $ ol
Real
和 Complex
CanAdd
- 功能无法修改可添加的类的源代码 For例如,我们可以定义 CanAdd [Int,Int,Int]
来添加两个 Int
值而不用修改<$ c
$ b $ pre $隐含对象CanAddIntInt扩展CanAdd [Int,Int,Int] {$ c $> Int class:
覆盖def add(a:Int,b:Int):Int = a + b
}
According to this Erik Osheim's slide, he says the inheritance can solve the same problem as typeclass would, but mentions that inheritance has a problem called:
brittle inheritance nightmare
and says the inheritance is
tightly coupling the polymorphism to the member types
What is he means?
In my opinion, Inheritance is good at extension, either to change implementation of existing type or add new member type(subtype) to interface.
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 = ???
}
Now in terms of typeclass:
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]
…
}
I don't see any reason to prefer Typeclass , am I missing something?
When using inheritance to achieve ad-hoc polymorphism we may need to heavily pollute the interface of our value objects.
Assume we want to implement a Real and a Complex number. Without any functionality, this is as simple as writing
case class Real(value: Double)
case class Complex(real: Double, imaginary: Double)
Now assume we want to implement addition of
- Two real numbers
- A real and a complex number
- Two complex numbers
A solution using inheritance (Edit: Actually, I am not sure if this can be called inheritance since the method add
in the traits has no implementation. However, in that regard, the example doesn't differ from Erik Orheim's example) could look like this:
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)
}
Because the implementation of add is tightly coupled with Real
and Complex
, we have to enlarge their interfaces each time a new type is added (e.g., integers) and each time a new operation is needed (e.g., subtraction).
Type classes provide one way to decouple the implementation from the types. For example, we can define the trait
trait CanAdd[A, B, C] {
def add(a: A, b: B): C
}
and separately implement the addition using implicits
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)
}
}
This decoupling has at least two benefits
- Prevent pollution of the interfaces of
Real
andComplex
- Allows introducing new
CanAdd
-functionality without the ability to modify the source code of the classes that can be added
For example, we can define CanAdd[Int, Int, Int]
to add two Int
values without modifying the Int
class:
implicit object CanAddIntInt extends CanAdd[Int, Int, Int] {
override def add(a: Int, b: Int): Int = a + b
}
这篇关于为什么喜欢Typeclass超过继承?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!