Teil 12: Extraktoren

Wir haben in einem früheren Artikel bereits das Pattern Matching kennen gelernt. In dem dortigen Artikel musste ich euch für die Erklärungen, wie genau Pattern Matching nun funktioniert, auf einen späteren Zeitpunkt verweisen. In diesem Artikel werde ich euch die nötigen Erklärungen geben und nebenbei noch viele Beispiele bringen was wir mit Pattern Matching noch so alles machen können.

Einfache Extraktoren

Ein Beispiel, das ich schon gebracht habe, gab uns die Möglichkeit durch Pattern Matching einen Wert an Variablen zu binden:

def heavyCalculation() = {
  val memoryUsage = 50
  val cpuUsage = 91
  val networkUsage = 31
  (memoryUsage, cpuUsage, networkUsage)
}

scala> val (memoryUsage, cpuUsage, networkUsage) = heavyCalculation()
memoryUsage: Int = 50
cpuUsage: Int = 91
networkUsage:
 Int = 31

Die Funktionsweise des obigen Codes obliegt keinesfalls nur den Fähigkeiten des Compilers daraus Variablenzuweisungen zu generieren. Wir können aktiv in diesen Prozess eingreifen und festlegen was wir haben wollen. Hierfür benötigen wir nur einen sogenannten Extraktor. Ein Extraktor ist syntaktischer Zucker des Compilers – wir können ihn durch eine unapply-Methode innerhalb eines object erstellen und dann auf ihn zugreifen:

object StringExtractor {
  def unapply(s: String): Option[String] = s(0) match {
    case 'u' => Some(s.substring(1).toUpperCase)
    case 'l' => Some(s.substring(1).toLowerCase)
    case _ => None
  }
}

Wir können den Extraktor bequem aufrufen indem wir in Klammern das zu extrahierende Objekt übergeben:

scala> val StringExtractor(s) = "uHello"
s: String = HELLO

Der Compiler sorgt dann dafür, dass die unapply-Methode aufgerufen wird. Auf welche Typen ein Extraktor angewendet werden kann hängt vom Typ des Parameters der unapply-Methode ab – in unserem Beispiel wäre es ein String. Der Typ unserer Variable, die durch den Extraktor erzeugt wird hängt vom Rückgabetyp der unapply-Methode ab. Zum besseren Verständnis hier der Code, den der Compiler aus dem Extraktor erzeugen würde:

scala> val s = StringExtractor.unapply("uHello") match {
     |   case Some(s) => s
     |   case None => throw new MatchError
     | }
s: String = HELLO

Versuchen wir unseren Extraktor mit einem anderen Typ zu füttern, bekommen wir vom Compiler direkt eine Fehlermeldung:

scala> val StringExtractor(s) = 5
<console>:13: error: scrutinee is incompatible with pattern type;
 found   : String
 required: Int
       val StringExtractor(s) = 5
                          ^

Der Extraktor selbst funktioniert sehr einfach. Er prüft ob der erste Buchstaben eines Strings ein u oder ein l ist und wenn ja wird der restliche Stringinhalt in lauter Groß- oder Kleinbuchstaben umgewandelt. Die unapply-Methode gibt dann das extrahierte Objekt in einem Option verpackt zurück. Option haben wir schon früher kennen gelernt. Es gibt unserem Compiler die Möglichkeit zu erkennen ob ein Extraktor erfolgreich war oder nicht. Geben wir ein Some mit Inhalt zurück, so war der Extrahiervoragng erfolgreich. Haben wir nichts zu extrahieren müssen wir ein None zurückgeben. Würden wir nur das extrahierte Objekt zurück geben hätte der Compiler keine Möglichkeit festzustellen ob der Extraktionsvorgang erfolgreich war. Das ist insofern problematisch, da nach einem fehlgeschlagenen Pattern direkt zum nächsten Pattern gesprungen wird. Wenn also die Möglichkeit besteht, dass unser Extraktor fehlschlagen kann, dann müssen wir einen Default-Fall festlegen:

def extract(s: String) = s match {
  case StringExtractor(s) => s
  case _ => s
}

scala> extract("lHello World")
res4: String = hello world

scala> extract("uHello World")
res5: String = HELLO WORLD

scala> extract("hello")
res6: String = hello

Hätten wir keinen Default-Fall würde unser Code zur Laufzeit einen MatchError werfen:

scala> val StringExtractor(s) = "hello"
scala.MatchError: hello (of class java.lang.String)
<stack trace>

Anmerkung:
Der Extraktor muss mit einem Großbuchstaben anfangen, bei einem Kleinbuchstaben würde er nicht erkannt werden. Warum das so ist hab ich im Artikel über Pattern Matching bereits geschrieben.

Der Compiler hat hier keine Möglichkeit zu überprüfen ob der Code zur Laufzeit auch funktionieren wird. Er könnte zwar herausfinden was der Extraktor genau macht, er kann aber nicht wissen was für Eingabewerte er erhält. In obigem Beispiel haben wir einen statisch festgelegten String, aber was wenn der String durch eine Benutzereingabe erzeugt wird oder aus einer Datenbank kommt? Um zu vermeiden, dass unser Code hier einen potenziellen Fehler erzeugt, müssen wir unseren Extraktor ändern:

object StringExtractor {
  def unapply(s: String) = s(0) match {
    case 'u' => Some(s.substring(1).toUpperCase)
    case 'l' => Some(s.substring(1).toLowerCase)
    case _ => Some(s)
  }
}

Wir haben den Rückgabewert der unapply-Methode geändert. Anstatt ein Option erhalten wir nun nur noch ein Some. Dadurch funktioniert unser Code immer und wir können uns die Hilfsmethode zum Extrahieren ersparen:

scala> val StringExtractor(s) = "hello"
s: java.lang.String = hello

scala> val StringExtractor(s) = "uhello"
s: java.lang.String = HELLO

Mehrfache Extraktoren

Unser Code funktioniert jetzt für einen einzigen Parameter, wenn wir uns aber an das Tuple-Beispiel zurück erinnern, dann hatten wir dort aber mehrere Parameter:

scala> val (a, b) = (5, "hello")
a: Int = 5
b: java.lang.String = hello

Es ist gar nicht schwer dieses Verhalten selbst nachzubauen, wir müssen nur die Signatur der unapply-Methode ändern:

class Pair(val a: Int, val b: Int)
object Pair {
  def unapply(p: Pair): Option[(Int, Int)] = Some((p.a, p.b))
}

scala> val Pair(a, b) = new Pair(8, 12)
a: Int = 8
b: Int = 12

Die unapply-Methode erwartet jetzt ein Pair-Objekt. Zurückgeben tut sie dann alle zu extrahierenden Parameter in Tuple-Form (wieder gepackt in ein Option). Unsere Pair-Klasse können wir hier leider nur mit zwei Ints aufrufen. Wollen wir sie lieber mit einem String aufrufen wie beim Tuple Beispiel gezeigt, müssten wir den Parametertyp ändern, was jedoch sehr umständlich ist. In einem späteren Kapitel über parametrisierte Typen werde ich euch aber zeigen wie man dieses Problem geschickt lösen kann.

Anmerkung:
Wir können beim Erzeugen eines Tuples die runden Klammern weglassen wenn die Parameter schon in runden Klammern stehen. Das Codestück

Some((p.a, p.b))

können wir also auch

Some(p.a, p.b)

schreiben. Ihr glaubt es nicht? Dann probiert es aus!

Im Artikel über Pattern Matching habe ich euch versprochen folgenden Code zum Laufen zu bekommen:

val Person(name, address @ Address(city, zip, street)) = ...

Die Implementierungen sind wieder nicht besonders schwer:

class Person(val name: String, val age: Int, val address: Address)
object Person {
  def unapply(p: Person) = Some(p.name, p.age, p.address)
}
class Address(val city: String, val zip: Int, val street: String)
object Address {
  def unapply(a: Address) = Some(a.city, a.zip, a.street)
}

Das Extrahieren beschränkt sich dann auf bereits bekannte Sachen:

scala> val p = new Person("helen", 31, new Address("musterstadt", 12345, "musterstraße"))
p: Person = Person@22f49424

scala> val Person(name, age, address @ Address(city, zip, street)) = p
name: String = helen
age: Int = 31
address: Address = Address@68e2cd6f
city: String = musterstadt
zip: Int = 12345
street: String = musterstraße

Mit Hilfe des @-Zeichen können wir gleichzeitig die Adresse extrahieren aber auch deren Instanz an eine Variable binden.

Unsere Extraktor müssen wir übrigens nicht zwingend an ein object binden, wir können sie auch durch Klassen erzeugen:

class StringExtractor(u: Char = 'u', l: Char = 'l') {
  def unapply(s: String) = s(0) match {
    case `u` => Some(s.substring(1).toUpperCase)
    case `l` => Some(s.substring(1).toLowerCase)
    case _ => Some(s)
  }
}

Dies erlaubt uns unsere Extraktoren ein wenig anzupassen. Wir können bei der Objekterzeugung nämlich angeben auf welche Buchstaben der Extraktor prüfen soll.

scala> val SE1 = new StringExtractor()
SE1: StringExtractor = StringExtractor@7137f424

scala> val SE1(s) = "uhello"
s: java.lang.String = HELLO

scala> val SE2 = new StringExtractor('a', 'b')
SE2: StringExtractor = StringExtractor@1752b8b

scala> val SE2(s) = "ahello"
s: java.lang.String = HELLO

scala> val SE2(s) = "uhello"
s: java.lang.String = uhello

Beachtet hier bitte wieder dass, die Extraktoren innerhalb einer match-Expression groß geschrieben sein müssen und dass die Variablen mit Backticks referenziert werden müssen. Die mit val definierten Extraktoren dürfen wir auch kein schreiben.

Sequenzielle Extraktoren

Wir wissen jetzt wie wir einen Extraktor mit einem Parameter und auch mit mehreren Parametern erstellen, aber was ist wenn wir die Anzahl der Parameter gar nicht wissen? Das ist bspw. dann der Fall wenn wir eine Liste haben:

scala> val List(head, tail @ _*) = List(1, 2, 3)
head: Int = 1
tail: Seq[Int] = List(2, 3)

scala> val head :: tail = List(1, 2, 3)
head: Int = 1
tail: List[Int] = List(2, 3)

Die Liste kann eine beliebige Anzahl an Elementen besitzen, unsere unapply-Methode müsste also ein Option mit einer Seq zurückgeben. Schreiben wir den dazugehörigen Code:

class Container(val x: Int*)
object Container {
  def unapply(p: Container): Option[Seq[Int]] = Some(p.x)
}

Varargs sind in Scala nichts anderes als eine Seq, wir können sie also direkt zurückgeben. Testen wir den Code gleich noch:

scala> val Container(x1, x2, x3) = new Container(45, 32, 107)
<console>:9: error: wrong number of arguments for object Container
       val Container(x1, x2, x3) = new Container(45, 32, 107)
                    ^
<console>:9: error: recursive value x$1 needs type
       val Container(x1, x2, x3) = new Container(45, 32, 107)
                     ^

scala> val Container(x) = new Container(45, 32, 107)
x: Seq[Int] = WrappedArray(45, 32, 107)

Hm, das ist aber nicht das was wir erwartet haben. Aber es ist das spezifizierte Verhalten. Was haben wir denn genau hingeschrieben? Unsere unapply-Methode gibt eine Seq zurück. So weit so gut, aber woher soll der Compiler wissen ob wir durch das Pattern Matching eine Seq oder aber die Elemente der Seq erhalten wollen? Er kann es nicht wissen, deshalb können wir auch nur auf eine Seq matchen und nicht auf einzelne Elemente. Und da wir nur ein Element zurückgeben – nämlich die Seq – erhalten wir auch eine Fehlermeldung wenn wir versuchen den Extraktor mit mehreren Parametern aufzurufen. Bei List funktioniert es aber doch auch. Was ist dort anders? Tatsächlich kennt der Scala Compiler nicht nur eine unapply-Methode, sondern deren zwei. Die zweite nennt sich unapplySeqund kann als Ersatz zur normalen unapply-Methode genutzt werden. Wie der Name schon suggeriert ermöglicht sie uns nicht nur eine Seq zurückzugeben, sondern auch deren Elemente.

class Container(val x: Int*)
object Container {
  def unapplySeq(p: Container): Option[Seq[Int]] = Some(p.x)
}

scala> val Container(x) = new Container(45, 32, 107)
scala.MatchError: Container@16b5d4e1 (of class Container)
<stack trace>

scala> val Container(x @ _*) = new Container(45, 32, 107)
x: Seq[Int] = WrappedArray(45, 32, 107)

scala> val Container(head, tail @ _*) = new Container(45, 32, 107)
head: Int = 45
tail: Seq[Int] = WrappedArray(32, 107)

scala> val Container(x1, x2, x3) = new Container(45, 32, 107)
x1: Int = 45
x2: Int = 32
x3: Int = 107

Toll, nicht wahr? Es funktioniert alles so wie erwartet. Die unapplySeq-Methode unterliegt nur einer kleinen Einschränkung: Wir dürfen sie nicht zusammen mit einer unapply-Methode bereitstellen. Sollten beide Methoden existieren, so wird der Compiler nur die unapply-Methode auswählen und die andere nicht weiter beachten.

Jetzt haben wir für den Schluss aber noch etwas, das gerne besprochen werden möchte:

scala> val head :: tail = List(1, 2, 3)
head: Int = 1
tail: List[Int] = List(2, 3)

Diese Schreibweise unterscheidet sich von den Vorherigen. Wir müssen hier nicht mehr umständlich unseren Extraktor definieren. Stattdessen sieht es eher so aus wie wenn wir auf die Prepend-Methode von List zurückgreifen würden:

scala> val head :: tail = 1 :: 2 :: 3 :: Nil
head: Int = 1
tail: List[Int] = List(2, 3)

Der einzige Unterschied ist, dass das Nil am Schluss fehlt. Aber warum fehlt es? Die Antwort darauf wird ein wenig klarerer wenn wir unseren Code ein wenig umändern:

scala> val ::(head, tail) = 1 :: 2 :: 3 :: Nil
head: Int = 1
tail: List[Int] = List(2, 3)

Huch, was war das? Es war mal wieder syntaktischer Zucker des Compilers, der uns hier das Leben erleichtert. Immer dann wenn eine Klasse zwei Typparameter erwartet (die wir später im Detail kennen lernen werden) oder einen Konstruktor mit zwei Parametern besitzt, besteht die Möglichkeit, dass wir sie nicht in der Form

Class(obj1, obj2)

sondern als

obj1 Class obj2

aufrufen können. Das Gleiche haben wir oben beim extrahieren der List-Elemente gemacht. Diese Schreibweise wird uns aber nur bei Typparameter und Konstruktoren erlaubt und sonst nirgends. Aber wieso funktioniert unser Code dann? Das Symbol :: ist doch eine Methode in List? Ja, es ist eine Methode aber auch der Aufruf eines Extraktors. Genau genommen existiert :: zwei Mal – einmal als Methode und einmal als Klasse. Die Klasse :: stellt einen geeigneten Konstruktor bereit, der uns diese Schreibweise erlaubt. Hier die Klassendefinition von scala.collection.immutable.:::

final case class ::[B](
  private var hd: B,
  private[scala] var tl: List[B])
extends List[B] {...}

Die Klassendefinition ist für den Anfang ein wenig verwirrend und genau deshalb werden wir uns jetzt eine eigene List schreiben. Das hilft uns nicht nur zu verstehen wann genau die Methode :: und wann das Objekt :: aufgerufen wird – es hilft uns vor allem auch die Stärken und Schwächen von List kennen zu lernen. Fangen wir damit also gleich an. Und danach gibt es noch ein paar Übungsaufgaben bei denen ihr testen könnt ob ihr auch alles verstanden habt und ohne meine Hilfe zurechtkommt.

Praxisbeispiel: Implementierung von List

Eine List ist eine einfach verkette Liste. Jedes Stück der List besitzt neben dem Element, das es aufnimmt noch eine Referenz auf das nächste Stück der Liste. Daraus folgt ein einfacher Konstruktor:

class IntList(val head: Int, val tail: IntList)
object IntList {
  def apply(head: Int, tail: IntList) = new IntList(head, tail)
}

Wir beschränken unsere Listimplementierung darauf, dass sie nur Ints aufnehmen kann, dann müssen wir uns noch nicht mit parametrisierten Typen herumschlagen. Durch die Implementierung einer apply-Methode können wir uns fortan gleich noch das new sparen. Wir können nun schon eine List erstellen:

scala> val xs = IntList(1, IntList(2, IntList(3, null)))
xs: IntList = IntList@7137f424

Das null ist uns jetzt noch ein Dorn im Auge. Es ist nicht typsicher und kann zu NullPointerExceptions führen. Versuchen wir es also zu umgehen:

class IntNil extends IntList(0, null)
object IntNil {
  def apply(): IntList = new IntNil
}

scala> val xs = IntList(1, IntList(2, IntList(3, IntNil())))
xs: IntList = IntList@57a68215

Aber wirklich besser ist das auch nicht. Wir haben das null jetzt nur vom Anwendungscode in die Bibliothek verlagert. Außerdem erzeugen wir bei jedem Aufruf von IntNil ein neues Objekt. Daraus folgt:

scala> IntNil() != IntNil()
res4: Boolean = true

Anmerkung:
Achtet darauf, dass ihr IntNil() aufruft und nicht nur IntNil. Wenn ihr die Klammern weg lässt referenziert ihr nicht die apply-Methode sondern den Typ IntNil. Dessen genaue Bedeutung kann uns im Moment egal sein, es muss nur klar sein, dass er existiert.

Wir dürfen also nur eine Instanz von IntNil besitzen. Wir erreichen das am besten wenn wir unseren Code ein wenig umbauen:

abstract class IntList {
  def head: Int
  def tail: IntList
}

class Cons(val head: Int, val tail: IntList) extends IntList
object Cons {
  def apply(head: Int, tail: IntList) = new Cons(head, tail)
}

object IntNil extends IntList {
  def head = throw new UnsupportedOperationException("nil head")
  def tail = throw new UnsupportedOperationException("nil tail")
}

Anstatt Verhalten durch IntNil auszutauschen haben wir nun eine polymorphe Datenstruktur, deren genaues Verhalten von den Subklassen abhängen. Scala erlaubt uns das Überschreiben von Methoden durch Attribute (so geschehen in Cons), da der Compiler für die Attribute entsprechende Zugriffsmethoden erzeugt.
Das null ist einer Exception gewichen, welche direkt durch die Methoden head und tail zurückgegeben wird. Die Codeerzeugung unterscheidet sich nicht groß von der vorherigen Version:

scala> val xs = Cons(1, Cons(2, Cons(3, IntNil)))
xs: Cons = Cons@4f86f5f

Ergänzen wir unseren Code durch eine vernünftige String-Repräsentation:

abstract class IntList {
  def head: Int
  def tail: IntList
  def isEmpty: Boolean

  override final def toString = {
    val sb = StringBuilder.newBuilder
    sb append "IntList("
    sb append head

    var xs = tail
    while (!xs.isEmpty) {
      sb append ", "
      sb append xs.head
      xs = xs.tail
    }

    sb append ")"
    sb.toString
  }
}

class Cons(val head: Int, val tail: IntList) extends IntList {
  def isEmpty = false
}
object Cons {
  def apply(head: Int, tail: IntList) = new Cons(head, tail)
}

object IntNil extends IntList {
  def head = throw new UnsupportedOperationException("nil head")
  def tail = throw new UnsupportedOperationException("nil tail")
  def isEmpty = true
}

Die isEpmty-Methode spart uns einen Vergleich auf IntNil, welchen man jetzt aber durchaus machen könnte, da es nur eine Instanz davon gibt. Die Ausgabe ist gleich zufriedenstellender.

scala> val xs = Cons(1, Cons(2, Cons(3, IntNil)))
xs: Cons = IntList(1, 2, 3)

Die Erzeugung der List sieht noch nicht besonders elegant aus. Ändern wir das:

// in IntList
def :: (i: Int) = new Cons(i, this)

scala> val xs = 1 :: 2 :: 3 :: IntNil
xs: Cons = IntList(1, 2, 3)

Als nächstes wollen wir auf die tolle Konkatenationsschreibweise zurückgreifen:

// in object Cons
def unapply(c: Cons) = Some(c.head, c.tail)

scala> val x1 Cons (x2 Cons x3) = 1 :: 2 :: 3 :: IntNil
x1: Int = 1
x2: Int = 2
x3: IntList = IntList(3)

Die Klammern werden leider benötigt da der Compiler sonst durch die Auswertungsreihenfolge (von links nach rechts) durcheinander kommt. Wir können das ändern indem wir der Cons-Klasse einen Namen geben, der mit einem Doppelpunkt endet.

class :: (val head: Int, val tail: IntList) extends IntList {
  def isEmpty = false
}
object :: {
  def apply(head: Int, tail: IntList) = new ::(head, tail)
  def unapply(c: ::) = Some(c.head, c.tail)
}

Wenn wir alle Vorkommen von Cons durch :: ersetzen, dann können wir durch die umgekehrte Auswertungsreihenfolge die Klammern weglassen:

scala> val head :: tail = 1 :: 2 :: 3 :: IntNil
head: Int = 1
tail: IntList = IntList(2, 3)

scala> val x1 :: x2 :: x3 :: IntNil = 1 :: 2 :: 3 :: IntNil
x1: Int = 1
x2: Int = 2
x3: Int = 3

Das war sie schon. Die ganze „Magie“ der Extraktoren. Zu Schluss noch eine Extraktor-Implementierung für unsere IntList:

object IntList {
  def apply(a: Int*) = {
    def loop(xs: Seq[Int], ys: IntList): IntList =
      if (xs.isEmpty) ys else loop(xs.tail, xs.head :: ys)
    loop(a, IntNil).reverse
  }

  def unapplySeq(a: IntList) = {
    def loop(xs: IntList, ys: List[Int]): List[Int] =
      if (xs.isEmpty) ys else loop(xs.tail, xs.head :: ys)
    Some(loop(a, Nil).reverse)
  }
}

// in IntList
def reverse: IntList = {
  def loop(xs: IntList, ys: IntList): IntList =
    if (xs.isEmpty) ys else loop(xs.tail, xs.head :: ys)
  loop(this, IntNil)
}

Der Code ist ein wenig umständlich. Wir müssen zuerst die Listen aufbauen und sie dann umdrehen. Wir haben leider keine Möglichkeit eine Liste direkt rückwärts aufzubauen, da sie nur einfach verkettet ist. Aber zumindest funktioniert der Code:

scala> val xs = IntList(1,2,3)
xs: IntList = IntList(1, 2, 3)

scala> val xs = IntList(1,2,3)
xs: IntList = IntList(1, 2, 3)

scala> val IntList(head, tail @ _*) = IntList(1, 2, 3)
head: Int = 1
tail: Seq[Int] = List(2, 3)

scala> val IntList(head, tail @ _*) = IntList(1 to 3: _*)
head: Int = 1
tail: Seq[Int] = List(2, 3)

Ihr erinnert euch doch hoffentlich noch an Ranges, die man im dritten Beispiel bewundern kann.

Die apply-Methode könnte man übrigens auch so schreiben:

// in object IntList
def apply(a: Int*) = (a :\ (IntNil: IntList)) { _ :: _ }

Das wäre die funktionale Herangehensweise an die Erzeugung einer geeigneten Liste. Das will ich aber nicht erklären, sonder mal nur so in den Raum werfen, damit ihr wisst was euch erwartet wenn ihr mir treu bleibt und fleißig weiter lest. 😉

Zum Abschluss noch die komplette Implementierung von IntList:

object IntList {
  def apply(a: Int*) = {
    def loop(xs: Seq[Int], ys: IntList): IntList =
      if (xs.isEmpty) ys else loop(xs.tail, xs.head :: ys)
    loop(a, IntNil).reverse
  }

  def unapplySeq(a: IntList) = {
    def loop(xs: IntList, ys: List[Int]): List[Int] =
      if (xs.isEmpty) ys else loop(xs.tail, xs.head :: ys)
    Some(loop(a, Nil).reverse)
  }
}

abstract class IntList {
  def head: Int
  def tail: IntList
  def isEmpty: Boolean

  def :: (i: Int) = new ::(i, this)

  def reverse: IntList = {
    def loop(xs: IntList, ys: IntList): IntList =
      if (xs.isEmpty) ys else loop(xs.tail, xs.head :: ys)
    loop(this, IntNil)
  }

  override final def toString = {
    val sb = StringBuilder.newBuilder
    sb append "IntList("
    sb append head

    var xs = tail
    while (!xs.isEmpty) {
      sb append ", "
      sb append xs.head
      xs = xs.tail
    }

    sb append ")"
    sb.toString
  }
}

class :: (val head: Int, val tail: IntList) extends IntList {
  def isEmpty = false
}
object :: {
  def apply(head: Int, tail: IntList) = new ::(head, tail)
  def unapply(c: ::) = Some(c.head, c.tail)
}

object IntNil extends IntList {
  def head = throw new UnsupportedOperationException("nil head")
  def tail = throw new UnsupportedOperationException("nil tail")
  def isEmpty = true
}
Advertisements

No comments yet

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: