2015-05-30 2 views
10

Согласно этому Erik Osheim's slide, он говорит, что наследование может решить ту же проблему, как класс типов будет, но упоминает, что наследование имеет проблему под названием:Почему вы предпочитаете Typeclass over Inheritance?

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] 
    … 
} 

Я не вижу никаких причин предпочитать класс типов, я что-то отсутствует?

+1

Что делать, если вы хотите добавить 'Int' в вашу семью' Foo'? Как ты делаешь это? –

+0

'class FooInt расширяет Int с помощью Foo' ?? –

+2

@ Wei-ChingLin Это даст ошибку: незаконное наследование от конечного класса Int' – Kolmar

ответ

12

При использовании наследования для достижения ad-hoc-полиморфизма нам может потребоваться существенное загрязнение интерфейса наших объектов значений.

Предположим, мы хотим реализовать Реальное и комплексное число. Без какой-либо функциональности, это так же просто, как написание

case class Real(value: Double) 

case class Complex(real: Double, imaginary: Double) 

Теперь предположим, что мы хотим реализовать добавление

  • двух действительных чисел
  • Реальный и комплексное число
  • Два комплексных числа

Решение с использованием наследования (Редактировать: На самом деле, я не уверен, что это можно назвать наследованием, поскольку метод add в чертах не имеет реализации. Однако, в этом отношении пример ничем не отличается от примера Эрика Орхейма в) может выглядеть следующим образом:

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) 
} 

Поскольку реализация оного тесно связана с 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)(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) 
    } 
} 

Такое разделение имеет по крайней мере два преимущества

  1. Предотвращение загрязнения интерфейсов Real и Complex
  2. Позволяет вводить новые CanAdd -функциональность Хет возможность изменять исходный код классов, которые могут быть добавлены

Например, мы можем определить CanAdd[Int, Int, Int] добавить два Int значения без изменения Int класса:

implicit object CanAddIntInt extends CanAdd[Int, Int, Int] { 
    override def add(a: Int, b: Int): Int = a + b 
} 
+0

Было бы короче сказать 'ev.add (a, b)' вместо 'неявно [CanAdd [A, B, C]]. Add (a, b)'? –

+0

@VictorMoroz Вы правы. Сначала у меня не было 'ev' как аргумента, но границы контекста работают только с одним общим параметром. Спасибо за подсказку; Я обновил ответ. –

Смежные вопросы