vrijdag 20 maart 2009

Wellicht Java?

Recentelijk heb ik een weblog geschreven over een bepaalde "monad", namelijk de "Maybe Monad".
Maar toen ik er bijna klaar mee was kwam ik erachter dat ik ook nog een post moest schrijven voor deze ISAAC weblog. Het artikel is een beetje te groot, en wellicht te esoterisch, om zomaar te vertalen naar het Nederlands. Daarom geef ik hier een samenvatting.

Het volledige artikel is hier te lezen.

De Maybe Monad, in de simpelste van termen, gaat over de vraag, "Wat moet een functie teruggeven als de combinatie van parameters eigenlijk tot niks uitkomt?" Om een voorbeeld te noemen, delen door nul. De meeste talen die we tegenkomen in ons werk bij ISAAC geven, in dit geval, over het algemeen een "exception", of in geval van Javascript "NaN". In feite is een exception throwen precies wat er zou moeten gebeuren, omdat delen door nul nog helemaal niet is opgelost door de wiskunde. Maar een aantal talen naast Java leveren een antwoord op deze vraag met een mechanisme dat "lichter" is dan een exception. Om dit te doen word in vrijwel alle gevallen een variatie van de Maybe Monad gebruikt. In een aantal talen is dit enorm simpel uitgedrukt, bijvoorbeeld Haskell:

data Maybe = Some a
             Nothing


Of in Scala:

trait Option {}
sealed case class Some[A](val a:A) extends Option[A] {}
sealed case object None extends Option[Any] {}


Maar dat zijn Haskell en Scala, en niet Java. Gelukkig is het (bijna) net zo simpel als in deze talen. Net zo simpel als een Pair of (simpele) Tuple class die iedereen wel eens schrijft tijdens een project. De Scala versie hierboven is eigenlijk enorm vereenvoudigd (maar is, binnen Scala helemaal functioneel te gebruiken), het heeft extra methoden, bijvoorbeeld een "getOrElse", die voor Some a terug geeft, en voor None de parameter die je meegeeft. Daarnaast is Option "Iterable", dat wil zeggen, je kan het in een for-loop gebruiken. Deze dingen zijn zeker nodig in Java, omdat Java geen Pattern Matching heeft, zoals Haskell en Scala (Haskell leeft erop!). Daarnaast zijn constant instanceof checks in je code niet veel beter dan null checks.

In Java is de Maybe Monad niet veel moeilijker in het gebruik dan het bovenstaande (code is te vinden in de post hierboven):

public Maybe integerDivide(int value, int divisor);

Het grootste voordeel is dat je de mogelijkheid dat het "fout" kan gaan expliciet gemaakt word, i.p.v. een runtime exception (zoals ArithmaticException, bij het delen door 0). Je wilt die niet zomaar krijgen in een 24/7/365 systeem alleen omdat een informatieleverancier ergens (per ongelijk) een 0 plaatst. Maar om try/catch om iedere deel operatie te zetten is ook weer te vervelend, het zelfde met iedere keer if statements om een deel operatie. Nu kan je gewoon, simpel, getOrElse aanroepen. Ik moet toegeven, in Haskell en Scala heb je het mooie Pattern Matching mechanisme, waar dit allemaal elegant word opgelost. Hoewel dit in Java iets minder elegant is, is het nogaltijd eleganter dan exceptions of velen if statements.

Daarnaast beschrijf ik hoe je de Map interface kan uitbreiden zodat deze Some teruggeeft als de key daadwerkelijk bestaat, en None teruggeeft als deze niet bestaat. Heel handig als null een goede waarde is voor een value bij een Key. Je hoeft dan niet nog een keer de key op te zoeken. En dat gebeurd ook niet in de aangepaste get, omdat er alleen Some of None waardes erin staan zal het zo zijn dat als er null terugkomt (intern) het alleen kan betekenen dat de key niet bestaat, en dan kan None teruggegeven worden.

Als laatste gaat de post over wat het nou toch is met die null, waarom ergeren we er ons toch iedere keer aan, en blijven we er toch bij terugkomen? Een van de karakteristieken van null is dat je het aan iedere type kan toewijzen, dit lijkt heel vreemd, en dat is het in feite ook. Null is een zogenaamde "Bottom Type", en is een type (in een Type System) dat (impliciet) een subtype is van alle andere types in het Type System. In Java is, bijvoorbeeld, Object een bottom type, zelfs Object subclasses Object in Java. Maar, er zit een gotcha aan, null heeft geen type. En dat is (vind ik) nou jammer, het betekend gewoon dat er allerlei special cases in de JVM en de Type System van Java zitten, alleen maar om die null waarde zonder type. Maar waarom is null nou bedacht?

Die eer komt toe aan Sir Tony Hoare, in 1965, en hij noemt het zijn "Million Dollar Mistake". Het was toendertijd zo gedaan omdat het simpeler was om te maken. Ik zal zeker de eerste zijn om te zeggen dat luiheid over het algemeen een goede karakter eigenschap is voor een programmeur, maar nu, 44 jaar later, moet zelfs ik bekennen dat er gevallen zijn waar luiheid schade berokkend. Null references zijn er een van. Het is wellicht grappig om te weten dat C++ null pointers kan hebben, maar niet null references (het zou een compiler bug zijn als dat mogelijk was!). Maar de pijn lijkt een beetje verbeterd nu er Elvis operators (zoals ze deze noemen in Groovy) komen in Java7.

En toch, ik vrees dat we voor een lange tijd niet af zijn van "null". Gelukkig hebben we gezien dat er "betere" dingen zijn in het geval "geen waarde". Dus, als je je afvraagt "wat moet ik doen als ik eigenlijk niks kan teruggeven vanuit deze functie?" Dus geen exception (tenzij het echt "exceptioneel" is) en ook geen null, want dat is een waarde, maar geen "None" terug! Maak het expliciet, de wereld zal je er dankbaar voor zijn.