为什么喜欢Typeclass超过继承? [英] Why prefer Typeclass over Inheritance?

查看:82
本文介绍了为什么喜欢Typeclass超过继承?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据这个 Erik Osheim的幻灯片,他说继承可以解决与typeclass相同的问题,
但提到继承有一个问题叫:


脆性继承噩梦


并且表示继承是


紧密耦合多态到会员类型


他是指什么?






在我看来,继承很适合扩展,既可以改变现有类型的实现,也可以向接口添加新的成员类型(子类型)。

  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

    1. Prevent pollution of the interfaces of Real and Complex
    2. 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屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆