2013-04-10 9 views
9

Ich habe Probleme, die Verwendung von Typklassen in Scala zu motivieren, wenn man mit Obergrenzen auf Typen vergleicht.Was motiviert Typklassen in Scala?

Betrachten Sie den folgenden Code ein:

case class NumList[T <: Complex](xs: Complex*) { 
    def sum = (xs fold new Complex(0, 0))(_ + _) 
    def map[U <: Complex](f: Complex => U): NumList[U] = NumList(xs.map(f): _*) 
    override def toString = "[" + xs.mkString(", ") + "]" 
    } 

    case class GenList[T](xs: T*) { 
    def sum(implicit num: Numeric[T]) = xs.sum 
    def map[U](f: T => U) = GenList(xs.map(f): _*) 
    override def toString = "[" + xs.mkString(", ") + "]" 
    } 

    val r = new Real(2) 
    val n = new Natural(10) 
    val comps = NumList(r, n, r, n) 

    println(comps) 
    println("sum: " + comps.sum) 
    println("sum * 2: " + comps.map(x => x + x).sum) 

    val comps2 = GenList(4, 3.0, 10l, 3d) 
    println(comps2) 
    println("sum: " + comps2.sum) 
    println("sum * 2: " + comps2.map(_ * 2).sum) 

Während diese beiden Listen ähnliche Probleme zu lösen, benutzt man die numerische Typ-Klasse und der andere verwendet eine obere Schranke für die Typparameter. Ich verstehe die technischen Unterschiede sehr gut, aber es fällt mir schwer, zur Kernmotivation der Typklassen zu kommen. Die beste Motivation, die ich bisher gefunden habe, ist die folgende:

Während Unterklassen oder die Implementierung von Schnittstellen ermöglicht Ihnen meist die gleichen Designs zu tun, können Sie Klassen-Klassen Features eines Typs auf einer pro-Methode Basis angeben, während a generische Klasse mit Typ T und obere Grenze U Constrains T überall dort, wo es verwendet wird. Vor diesem Hintergrund bieten type-classes eine feinere Kontrolle über Features von T in generischen Klassen.

Gibt es sehr klare Beispiele, die das Muster motivieren?

Antwort

9

Um einen Hauptaspekt zu vereinfachen, versuchen typeclasses, das Verhalten unabhängig von Ihrer Klassenhierarchie zu sammeln.

Angenommen, Sie müssen einen neuen numerischen Typ MetaNum (mit numerischen Standardoperationen) definieren, aber Sie können oder werden es aus irgendeinem Grund nicht zu einer Unterklasse Ihres Complex Typs machen.

Mit Numeric typeclass, müssen Sie nur eine geeignete Instanz für Ihre MetaNum bereitstellen und die erforderlichen Operationen bereitstellen.

Dann können Sie eine GenList[MetaNum] erstellen und darüber summieren.

Sie können dies nicht mit der NumList tun, weil MetaNum keine Complex ist. Die Implementierungsauswahl, die Sie bei der Definition von NumList getroffen haben, wird auf Sie zurückstoßen, wenn Sie versuchen, Ihre Operation/Datenstruktur in einem zweiten Moment zu verallgemeinern.

Fazit
Typeclasses gibt Ihnen mehr Freiheit, Ihr Verhalten unabhängig von hierarchischen Überlegungen, auf Kosten einiger zusätzlicher Komplexität und vorformulierten zu verlängern.

Ich kann nicht sagen, ob Sie dasselbe in Ihrer Frage meinten.

+2

Dies ist die genaue Begründung. Im Allgemeinen können Typenklassen präziser sein als in Scala. So wie sie kodiert sind, können sie mit viel mehr Flexibilität verwendet werden, dann kann man in etwas wie Vanille Haskell, doch Haskells Implementierung ist viel prägnanter. Ein anderes gutes Beispiel ist das Hinzufügen von etwas wie Serialisierung zu Ihrer Hierarchie, das Hinzufügen der Schnittstelle zum Anfang der Hierarchie würde alle Ihre alten Klassen brechen. Durch die Verwendung von Typklassen kann die Funktionalität inkrementell und speziell nur für die benötigten Typen implementiert werden. – jroesch

+2

Wie @jroesch sagte, ist es nützlich, die Tatsache zu betonen, dass Typklassen in Scala als Muster implementiert sind (also kein Sprachfeature), aber das Konzept aus anderen Sprachen (wie Haskell) stammt, in die das Feature eingebettet ist die Sprache selbst. Wenn z. B. Haskell keine OO-Sprache ist, gibt es keinen Vererbungsmechanismus. Daher wird die typeclass-Funktion verwendet, um allgemeine Funktionen durch benutzerdefinierte Funktionsimplementierung für verschiedene Datentypen abzuleiten.Aus diesem Grund scheint typeclasses in scala redundant zu sein, da sich die Funktionalität mit der Objektvererbung überschneidet. –

+0

Sehr gut. Ich ließ das für eine Weile allein und ging allein auf die Frage zurück und versuchte es zu motivieren. Ich kam zu einem sehr ähnlichen Argument. Nett! – Felix