2016-08-09 147 views
1

Gibt es eine bessere Erklärung als "so funktioniert es". Ich meine, ich versuchte, diese ein:Warum kann ich den Mustervergleich nicht auf nicht fallbezogene Klassen anwenden?

class TestShortMatch[T <: AnyRef] { 
    def foo(t: T): Unit = { 
    val f = (_: Any) match { 
     case Val(t) => println(t) 
     case Sup(l) => println(l) 
    } 
    } 

    class Val(t: T) 
    class Sup(l: Number) 
} 

und Compiler Beschwerden:

Cannot resolve symbol 'Val' Cannot resolve symbol 'Sup'

Natürlich, wenn ich case vor jedem der Klassen hinzufügen, wird es funktionieren. Aber was ist der Grund? Führt der Compiler eine Optimierung durch und erzeugt einen spezifischen Byte-Code?

+0

Die kanonische Referenz ist Kapitel 24 der Programmierung in Scala. Das Buch ist in seiner 3. Ausgabe, so dass die erste ed online ist http://www.artima.com/pins1ed/extractors.html –

Antwort

5

Der Grund ist zweifach. Pattern Matching ist nur syntaktischer Zucker für die Verwendung und case classes geschehen, um Ihnen ein paar Methoden kostenlos zu geben, von denen eine Extraktor-Methode, die dem Hauptkonstruktor entspricht.


Wenn Sie oben an die Arbeit Ihrem Beispiel wollen, benötigen Sie eine unapply Methode innerhalb definieren Objekte Val und Sup. Dazu müssen Sie den Methoden Extraktor bräuchten (die nur val Felder definiert auf, so müssen Sie werden Ihre Felder val s machen):

class Val[T](val t: T) 
class Sup(val l: Number) 

object Val { 
    def unapply[T](v: Val[T]): Option[T] = Some(v.t) 
} 
object Sup { 
    def unapply(s: Sup): Option[Number] = Some(s.l) 
} 

Und welche Stelle Sie so etwas wie val Val(v) = new Val("hi") tun können. Meistens ist es jedoch besser, Ihre Klasse zu einer Klasse case zu machen. Dann sollten Sie nur zusätzliche Extraktoren definieren.

Das übliche Beispiel ist Koordinaten (auf die ich keinen Verweis zu finden scheinen):

case class Coordinate(x: Double, val: Double) 

Und dann können Sie eine benutzerdefinierte Extraktoren wie

object Polar { 
    def unapply(c: Coordinate): Option[(Double,Double)] = {...} 
} 
object Cartesian { 
    def unapply(c: Coordinate): Option[(Double,Double)] = Some((c.x,c.y)) 
} 

definieren, um die konvertieren zwei verschiedene Darstellungen, alle, wenn Sie Muster zuordnen.

3

Sie können Mustererkennung für beliebige Klassen verwenden, aber Sie müssen an unapply method implementieren, um das Objekt zu "dekonstruieren".

Bei einer Fallklasse wird die Methode "unapply" automatisch vom Compiler generiert, sodass Sie sie nicht selbst implementieren müssen.

1

Wenn Sie match exp { case Val(pattern) => ... case ... } schreiben, ist das gleichbedeutend mit etwas wie folgt aus:

match Val.unapply(exp) { 
    case Some(pattern) => 
    ... 
    case _ => 
    // code to match the other cases goes here 
} 

Das heißt, es verwendet das Ergebnis der unapply Methode Begleiter Objekt, um zu sehen, ob das Spiel gelungen.

Wenn Sie eine Fallklasse definieren, wird automatisch ein Begleitobjekt mit einer geeigneten unapply-Methode definiert. Für eine normale Klasse nicht. Die Motivation dafür ist dieselbe wie für die anderen Dinge, die automatisch für Fallklassen definiert werden (wie equals und hashCode zum Beispiel): Indem Sie eine Klasse als eine Fallklasse deklarieren, geben Sie eine Aussage darüber ab, wie die Klasse funktionieren soll sich verhalten. Angesichts dessen besteht eine gute Chance, dass das automatisch generierte Programm das macht, was Sie wollen. Für eine allgemeine Klasse liegt es an Ihnen, diese Methoden so zu definieren, wie sie sich verhalten sollen.

Beachten Sie, dass Parameter für Fallklassen standardmäßig val s sind, was für normale Klassen nicht gilt. So hat Ihre Klasse class Val(t: T) nicht einmal Zugriff auf t von außen. Es ist also nicht einmal möglich, eine unapply-Methode zu definieren, die den Wert t erreicht. Das ist ein weiterer Grund, warum Sie keinen automatisch generierten unapply für normale Klassen erhalten: Es ist nicht einmal möglich, einen zu erzeugen, es sei denn, alle Parameter sind val s.