Tuesday, October 26, 2010

@Deprecated null => Sinnvoller, typisierter Ersatz für "nichts"

Wie kann man "nichts"-Werte ohne Null sinnvoll abdecken?
Die Antwort auf die Frage zum Glück relativ einfach, denn es gibt bereits Typen die einen solchen "nichts"-Typ kennen: Sowohl Strings als auch Collections als auch Arrays kennen eine leere Menge, die nach allgemeiner Empfehlung auch immer anstelle von "nichts" verwendet werden sollte, wenn man mit diesen Typen hantiert.

Was haben alle diese Werte gemeinsam, was dazu führt, dass sie einen "nichts"-Wert kennen?
 -> Sie sind Collections und haben eine Kardinalität. Jeder dieser Typen enthält 0..n Werte eines bestimmten Typs. Die Nullmenge stellt dabei immer den "nichts"-Wert dar.

Bei den Collections kann man grundsätzlich zwei Haupttypen dieser Aufzählungen gleicher Werte unterscheiden: Mengen und Sequenzen. Mengen (Sets) enthalten beliebige viele unterschiedliche Werte eines Typs. Sequenzen (Sequences) enthalten eine fest vorgebene Abfolge von Werten (z.B. Listen, Arrays, Queues, Stacks). Aber beide können 0..n Werte enthalten, und bei beiden kann man die Wertmengen durchlaufen. (In dem Zusammenhang sei erwähnt, das aus irgendwelchen Gründen viele Sprachen Strings und Arrays nicht als normale Sequenzen betrachten, aber das nur als Kuriosum am Rande.)

Die Antwort darauf, wie man "nichts" sinnvoll abdecken kann scheint also irgendwie mit Collections, primär mit Mengen zusammenzuhängen.. Normale Collections eignen sich nun aber nicht für den genannten Usecase, denn man will zwar "nichts" haben, aber grundsätzlich nicht beliebig viele Werte (und die damit verbundene Komplexität) abdecken. Es läuft nicht auf 0...n hinaus, sondern auf eine Menge von 0..1 statt der normalen 1..1 Beziehung.

Ein neuer Datentyp: Option

Damit haben wir nun unseren Zieltyp - eine Menge von 0..n: Option[T

Option ist ein unveränderlicher Typ der genau zwei Werte haben kann:
  • None => eine Konstante, die den leeren Wert darstellt) 
  • Some(t) => ein Wert, der den gesetzten Zielwert enthält
Schnittstelle: Es reicht prinzipiell, die folgenden Collection-Operationen anzubieten:
iterator() => erzeugt einen Iterator, um über die Menge zu iterieren
isEmpty() => true für None, false für Some

Die Verwendung z.B. in Java könnte wie folgt aussehen (eine Implementierung wird noch nachgereicht).

class Alien{
   Option<Gender> gen = Option.none();
   Option<Hair> hair = Option.some(Hair.Green);

   boolean isHumanlike(){
     return !gen.isEmpty() && !hair.isEmpty();
   }
   
   String getHairColor(){
     for (Hair h: hair){
       return h.toString();
     }
     return "";
   }
   
   Option<Gender> getGender(){ return gen; }
   Option<Hair> getHair(){ return hair; }
}


Vorteile durch die Verwendung von Option
Soweit so gut.. Die erste Frage die jetzt kommen dürfte ist: Was bringt ein solcher minimaler Typ? Die Antwort liefert der obige Quelltext:
  • Keine NoPEs: Der Compiler zwingt mich dazu, einen Option-Wert mit isEmpty() zu prüfen bzw. mit for-each zu zerlegen, wenn ich an den Inhalt kommen möchte.
  • Die Operationen sind vollkommen sicher: Keine der beiden Operationen kann unter irgendwelchen Umständen Exceptions werfen.
  • Der Code ist kurz: Er ist zwar etwas länger als ein reiner null-check (s. getHairColor()), aber immer noch handhabbar. Außerdem sollten optionale Werte IMMER die Ausnahme sein. Die Performance ist ebenfalls extrem hoch, wenn auch deutlich unter dem Typunsicheren nullcheck.
  • Er ist selbstdokumentierend: Geht wieder in die Richtung des ersten Punkts. Durch die Typannotation mache ich nach außen jedem klar was dieser Code macht und wie er zu benutzen ist. Deshalb kann ich auch ohne Probleme den Zugriff auf die Felder zulassen und sie als Außenstehender auch sorglos benutzen.

Abschluss
Der "Option"-Typ wird in vielen funktional orientierten Sprachen als Standardtyp angeboten, die im Allgemein ein deutlich besser designtes Typsystem verwenden als die meisten objektorientierten Sprachen wie Java.
In Haskell ist er als Maybe[a] => Nothing <-> Just[a] bekannt,
das hier verwendete Namensschema habe ich hingegen aus Scala übernommen, da ich es etwas aussagekräftiger finde.

Ich hoffe das die Infos hier relativ interessant waren und wünsche viel Spaß dabei, den Unterschied zwischen "nichts" und "etwas"; und den Kardinalitäten 1:1, 0:n und 0:1 herauszufinden!

Links:
http://james-iry.blogspot.com/2007/08/martians-vs-monads-null-considered.html
http://www.codecommit.com/blog/scala/the-option-pattern

Eine Kontroverse in der, eher schwach, gegen den Option-Typ argumentiert wird:
http://java.dzone.com/articles/why-scala%E2%80%99s-%E2%80%9Coption%E2%80%9D-and?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+javalobby%2Ffrontpage+%28Javalobby+%2F+Java+Zone%29

No comments:

Post a Comment