ScalaDays 2019 is alweer een week voorbij, maar langzaam, maar zeker krijgt de enorme hoeveelheid hoge-kwaliteit input hun gevolgen in mijn kennis over Scala.

Naar aanleiding van een rode draad die ik vond tijdens deze ScalaDays, heb ik enige gedachten over het gebruik van Types in Scala geblogd.

In het algemeen kan best gesteld worden dat Types een hoop programmeer fouten kunnen voorkomen. De compiler geeft al feedback voordat er in runtime situaties (productie!) eindelijk blijkt dat er iets fout gaat. Daarom is het zo fijn als je als developer die types zoveel mogelijk voor je kunt laten werken: use the types, Luke!

Een goed doel voor types is het afvangen van invoer-data fouten: een bepaalde functie (of method) heeft één of meer parameters, waarvoor de functie een resultaat gaat produceren. Sommige functies, bekend als Partial Functions, produceren niet voor alle invoer een resultaat, bijvoorbeeld delen door 0, wortel uit een negatief getal, inhoud van een bestand, dat niet bestaat.

Als simpel voorbeeld neem ik de functie die twee getallen als invoer heeft en als resultaat de deling van de eerste door de tweede parameter (de deler) geeft:

  def deel(n: Number, deler: Number): Number = n.doubleValue() / deler.doubleValue();

Voor de invoer van een willekeurige eerste parameter met een deler 0 (of 0.0) komt daar geen acceptabel antwoord uit: of een Exception of iets als ‘oneindig’, wat niet echt goed gedefinieerd is in de computer wereld. Om te voorkomen dat een functie een onverwacht antwoord geeft of, erger nog, een exceptie zou gooien, kunnen we beter zorgen dat de invoer geen foutieve uitvoer kán opleveren; geen partial functions meer, want voor alle invoer komt een bijbehorende uitvoer terug.

In bovenstaand voorbeeld kan dat door te zorgen dat de ‘deler’ niet zomaar een getal is, maar het type NonZeroNumber heeft: een Number dat geen 0 (nul) als waarde toestaat. Daarmee heeft de ‘deel’ functie geen mogelijke parameter-waarden meer, waarvoor geen antwoord gegeven kan worden: voor iedere combinatie van mogelijke (!!) getallen wordt nu de deling van ’n’ door de ‘deler’ uitgevoerd en het antwoord daarvan wordt aan de aanroepende teruggegeven.

Een mooie Scala bibliotheek die hiervoor gebruikt zou kunnen worden is refined waarmee het mogelijk is om bestaande Scala types te verfijnen. In bovenstaand geval van de functie ‘deel’ zou de deler van het type:

  type NonZeroNumber = Number Refined NotZero

Deze constructie zou nul uitsluiten, waardoor die waarde niet meer geaccepteerd wordt als invoer. NB: deze verfijning is nog niet beschikbaar in de Refined bibliotheek. Aangezien dit ook een mooie kans is om eens iets bij te dragen aan open source software (OSS), ga ik hier binnenkort mee aan de slag!

Overigens is deze methode van werken ook onderdeel van zg. Dependent Types, zoals te vinden in Idris.

En dan komt de (terechte) vraag: wat heb ik daar dan aan? De exceptie van de nul-deling afvangen of een Either of Try teruggeven met het (verpakte) antwoord van de deling óf een foutmelding werkt voor mij goed genoeg! Maar nu weet ik van te voren dat ik geen nul mag invoeren. Het contract van de functie met de boze buitenwereld van aanroepende programma’s is nu expliciet. Ik hoef als gebruiker van de functie ‘deel’ niet meer te hopen dat de leverancier van die functie documentatie heeft gemaakt waarin delen-door-0 als uitzondering wordt aangemerkt of, als documentatie niet beschikbaar is, ik moet de source in om te kijken wat er gebeurd als ik bepaalde waarden aanlever.

Mijn programmeer-wereld wordt hiermee een stukje duidelijker en, als het meezit, gaat de compiler mij zelfs helpen om programmeer-fouten te voorkomen.

shadow-left