Alternativen


In manchen Fällen braucht man Alternativen, in diesem hier zu Option und zu Either. Denn damit ist es nur umständlich auzudrücken, dass von zwei Werten mindestestens einer vorhanden sein muss, z.B. wenn wenigstens Vor- oder Nachname eingegeben werden muss, aber auch beides erlaubt ist. Ein anderes Beispiel wäre, wenn man von einer Funktion entweder ein Resultat oder einen Fehler (bis hierhin wäre das ein typischer Fall für Either) oder aber sowohl ein Resultat wie auch eine Warnung zurückbekommen kann.

Soetwas mit den vorhandenen Mitteln auszudrücken ist nicht ganz einfach: Ein Tupel von zwei Options erlaubt auch die „verbotene“ (None, None)-Kombination, und da Either nicht beide Werte gleichzeitig abbilden kann, landet man damit bei Konstruktionen wie Either[Either[A,B], Tuple2[A,B]], mit dem kein Mensch vernünftig arbeiten kann. Dabei ist ein selbstgestrickter Alternative-Type nicht schwer zu schreiben:

sealed trait Alternative[+A,+B] {
  def leftOption: Option[A] = this match {
    case First(a) => Some(a)
    case Second(_) => None
    case Both(a, _) => Some(a)
  }

  def rightOption: Option[B] = this match {
    case First(_) => None
    case Second(b) => Some(b)
    case Both(_, b) => Some(b)
  }

  def bothOptions= (leftOption, rightOption)

  //more efficient implementation than using mapLeft and then mapRight
  def map[AA,BB](fa: A => AA, fb: B => BB):Alternative[AA,BB] = this match {
    case First(a) => First(fa(a))
    case Second(b) => Second(fb(b))
    case Both(a, b) => Both(fa(a),fb(b))
  }

  def mapLeft[AA] (fa: A => AA):Alternative[AA, B] = this match {
    case First(a) => First(fa(a))
    case Second(b) => Second(b)
    case Both(a, b) => Both(fa(a),b)
  }

  def mapRight[BB] (fb: B => BB): Alternative[A, BB] = this match {
    case First(a) => First(a)
    case Second(b) => Second(fb(b))
    case Both(a, b) => Both(a,fb(b))
  }

  def orElse[AA >: A, BB >: B](that:Alternative[AA,BB]) = (this,that) match {
    case (First(a),Second(b)) => Both(a,b)
    case (First(a),Both(_,b)) => Both(a,b)
    case (Second(b),First(a)) => Both(a,b)
    case (Second(b),Both(a,_)) => Both(a,b)
    case _ => this
  }

  def leftOrElse[AA >: A](aDefault:AA) = leftOption.getOrElse(aDefault)
  def rightOrElse[BB >: B](bDefault:BB) = rightOption.getOrElse(bDefault)
  def getOrElse[AA >: A, BB >: B](aDefault:AA, bDefault:BB) =
    (leftOrElse(aDefault), rightOrElse(bDefault))
}

case class First[+A,+B](_1:A) extends Alternative[A,B]
case class Second[+A,+B](_2:B) extends Alternative[A,B]
case class Both[+A,+B](_1:A, _2:B) extends Alternative[A,B]

Natürlich ist das nur eine von vielen Möglichkeiten, so einen Typ zu implementieren, und man könnte natürlich noch viele nützliche Sachen hinzufügen. Man kann in der Implementierung sehr schön sehen, wie sehr Fallklassen und Pattern Matching unser Leben erleichtern können. Die einzige kleine Herausforderung war, die Varianzen richtig hinzubekommen, aber da half ein bisschen Kiebitzen bei unseren „Vorbildern“ Option und Either.

Ich wollte mit diesem Beispiel zeigen, dass man nicht auf Krampf irgendwelche wilden Konstrukte mit Klassen wie Option oder Either verwenden sollte, wenn sie nicht wirklich zur Aufgabenstellung passen, weil es leicht ist, sich selbst eine passende Variante zu schreiben. Viel Spaß beim Ausprobieren!

Advertisements

Ein Gedanke zu “Alternativen

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