IS DE OPMARS VAN FUNCTIONEEL PROGRAMMEREN BIJ WEB DEVELOPMENT MEER DAN EEN RECENTE GRIL? Promotor: MH Kevin DeRudder Onderzoeksvraag uitgevoerd door PIETER-JAN VANDENBUSSCHE Voor het behalen van de graad van Bachelor in de NEW MEDIA AND COMMUNICATION TECHNOLOGY Howest | 2015-2016 IS DE OPMARS VAN FUNCTIONEEL PROGRAMMEREN BIJ WEB DEVELOPMENT MEER DAN EEN RECENTE GRIL? Promotor: MH Kevin DeRudder Onderzoeksvraag uitgevoerd door PIETER-JAN VANDENBUSSCHE Voor het behalen van de graad van Bachelor in de NEW MEDIA AND COMMUNICATION TECHNOLOGY Howest | 2015-2016 Mijn naam is Pieter-Jan Vandenbussche, student New Media & Communication Technology (NMCT) Howest, campus Kortrijk. Gedurende de middelbare school had ik al een interesse in programmeren en dergelijke, maar omwille van een lage motivatie in die periode – toen een van mijn mindere eigenschappen – ben ik er ook niet verder op ingegaan. Na afgestudeerd te zijn van de middelbare school met een diploma Latijn-Moderne Talen en geen benul te hebben over welke carrière wil ik nastreven in de – toen – nabije toekomst, begon ik aan mijn korte periode als student in de Rechten. Te laat bleek het dat mijn interesse in dit veld stilaan onder nul begon te dippen en dus besloot ik – jammer genoeg voor mijn ouders – na één jaar om het voor gezien te houden. In de weken na mijn laatste semester als rechtenstudent ben ik – na wat grondig ‘soulsearching’ – tot de realisatie gekomen dat ik een carrière in development wil nastreven. Toentertijd kende ik reeds iemand die net zijn eerste jaar NMCT had vervolledigd en die een lovende opinie had over de opleiding. Uiteindelijk – na wat vergelijking met andere scholen – er toch voor gekozen om mijn diploma te behalen aan Howest, studierichting NMCT. Mijn interesse in alles omtrent programmeren begon serieus te groeien tijdens deze laatste drie jaren, met een nadruk op web development, front – en backend. Buiten de lessen was ik altijd op zoek naar nieuwe interessante topics, en functioneel programmeren was altijd al een van die topics waar ik graag wat dieper op zou willen ingaan. Toen het nieuws kwam dat we een onderwerp dienden te zoeken voor een bachelorproef, was de keuze vlug gemaakt. Het bleek dat 2 van mijn interesses, web development en functioneel programmeren (waarover ik toen een zeer gelimiteerde kennis had), een zekere toekomst hadden, of zo zag het er toch zo uit, volgens meerdere bronnen. Tot zo ver de keuze van mijn onderzoeksvraag. Graag zou ik Kevin DeRudder, mijn promotor, en Ann Deraedt bedanken voor hun begeleiding gedurende het schrijven van deze bachelorproef. Verder wil ik alle lectoren die mij deze voorbije drie jaren onderwezen hebben bedanken voor hun inzet. Tot slot wil ik mijn ouders bedanken die mij constant motiveren om het beste van mezelf te geven en die mij de kans gegeven hebben om – na een wat ongelukkig eerste jaar als student – te starten in NMCT. De laatste tijd worden concepten van functioneel programmeren meer en meer toegepast bij web development, specifieker nog, in JavaScript. Deze bachelorproef is een onderzoek naar waarom functioneel programmeren in web development populairder wordt en of die recente populariteit niets meer is dan een recente gril zonder enige serieuze voordelen. Het onderzoek zal beginnen met een inleiding te geven wat functioneel programmeren nu juist is en redenen waarom men functioneel programmeren in het algemeen zou gebruiken. Hierna worden de faciliteiten onderzocht die JavaScript standaard aanlevert waarmee men in een zekere functionele programmeerstijl kan werken. Vervolgens wordt er ingegaan op verschillende libraries, frameworks en dergelijke die concepten uit functioneel programmeren implementeren in JavaScript. Ten slotte wordt er onderzocht waarom men juist concepten uit het functioneel programmeren zou gebruiken in JavaScript en wordt er een conclusie gegeven, namelijk dat deze recente populariteit van functioneel programmeren in JavaScript wel degelijk meer is dan een recente gril. Hoe complexer web applicaties worden hoe moeilijker het wordt voor de developer. Concepten uit het functioneel programmeren helpen de developer om die complexiteit beter te benaderen en uit te werken. Lijst met afkortingen.................................................................................................................................. 7 Verklarende woordenlijst ......................................................................................................................... 8 Inleiding ........................................................................................................................................................ 9 1. 2. 3. Functioneel Programmeren............................................................................................................ 10 1.1. Wat is functioneel programmeren? ..................................................................................... 10 1.2. Hogere-ordefuncties (Higher order functions) ................................................................. 12 1.3. Luie evaluatie (Lazy evaluation) ........................................................................................... 13 1.4. Waarom functioneel programmeren? ................................................................................. 14 1.4.1. Voordelen .........................................................................................................................................14 1.4.2. Nadelen .............................................................................................................................................17 1.4.3. Conclusie ..........................................................................................................................................18 Functioneel programmeren in JavaScript ................................................................................... 18 2.1. Recente populariteit functioneel programmeren.............................................................. 18 2.2. Wat is functioneel programmeren in JavaScript? ............................................................ 19 2.2.1. Higher order functions .................................................................................................................19 2.2.2. Itereren over arrays (map, filter, reduce) .................................................................................20 2.2.3. Function composition....................................................................................................................23 2.2.4. Recursie .............................................................................................................................................24 2.2.5. Luie evaluatie (lazy evaluation) ...................................................................................................25 Implementaties in JavaScript ......................................................................................................... 25 3.1. Lodash, Ramda en Underscore .............................................................................................. 26 3.2. React en Redux ......................................................................................................................... 29 3.3. ImmutableJS .............................................................................................................................. 31 4. Waarom functionele stijl in JavaScript?...................................................................................... 32 5. Conclusie ............................................................................................................................................ 34 Literatuurlijst ............................................................................................................................................. 35 6 OOP Object Oriented Programming MVC Model View Controller UI User Interface HTML Hyper Text Markup Language JSX JavaScript Serialization to XML DOM Document Object Model AJAX Asynchronous JavaScript and XML 7 Quicksort Een veelgebruikt algoritme om een lijst, array, etc. te sorteren. NodeJS Een softwareplatform waarop men applicaties geschreven in JavaScript kan ontwikkelen. Call stack Een data structuur van de JavaScript engine die calls naar functies bijhoudt in het geheugen tot er een return statement volledig uitgevoerd is in die functie AngularJS Webapplicatieframework voor Single Page Applications Ember Webapplicatieframework voor Single Page Applications 8 De laatste tijd worden concepten van functioneel programmeren meer en meer toegepast bij web development. Deze bachelorproef is een onderzoek naar waarom functioneel programmeren in web development populairder wordt en of die recente populariteit wel gerechtvaardigd is. M.a.w., welke problemen – bij het ontwikkelen van web applicaties – lost het gebruik van concepten uit functioneel programmeren al dan niet (beter) op, vergeleken met imperatief programmeren? Op welke vlakken kan denken in concepten van functioneel programmeren de (frontend en/of backend) developer al dan niet helpen bij het ontwikkelen van een web applicatie? Zorgt het toepassen van bovenstaande concepten ervoor dat je een beter overzicht hebt over de werking en/of flow van je applicatie, zorgt het toepassen van bovenstaande concepten ervoor dat je efficiënter gaat werken bij complexere codebases? Is er enig verschil in productiviteit wanneer functioneel programmeren in JavaScript gebruikt wordt in teamverband? Een al dan niet positief antwoord op elk van deze vragen volgt. Er wordt verwacht dat de lezer van deze bachelorproef over enige basiskennis JavaScript en programmeren in het algemeen bezit. 9 Functioneel programmeren is een programmeerparadigma die, zoals de naam doet vermoeden, grotendeels gebaseerd is op het werken met functies of expressies om applicaties te construeren. Deze functies zijn gelijkaardig aan de gekende wiskundige functies, en nog specifieker, hebben als belangrijkste inspiratie de lambda calculus. Zonder al teveel in detail te gaan, de lambda calculus is voorgesteld in 1930 door Alonzo Church als een systeem om onderzoek te doen naar berekenbare functies. (Allen & Moronuki, 2015) Om een wat duidelijker beeld te geven op wat functioneel programmeren juist is, is het beter om het te vergelijken met een meer gekend programmeerparadigma, namelijk imperatief programmeren. Enkele voorbeelden van imperatieve talen zijn bijv. C, Java, Go etc. Deze talen zijn allemaal gekend als imperatieve talen omdat ze op een imperatieve manier, met een gebiedende wijs, een programma definiëren als een sequentiële opvolging van statements die in éénzelfde volgorde ook geëxecuteerd worden. Deze statements worden dan gebruikt om de state van een programma te manipuleren. Anders geformuleerd, imperatieve talen beschrijven hoe een programma functioneert. (Wikipedia, 2016) Het vinden van de som van alle nummers gevonden in een array kan als voorbeeld genomen worden om bovenstaande te verduidelijken: //C# //veronderstel: array = [1, 2, 3, 4, 5] int som = 0; for (int i = 0; i < array.length; i++) { som += array[i]; } In deze code is er een state, namelijk de variabele som die elke iteratie van de for-loop gemuteerd wordt. Verder is het duidelijk dat er een opeenvolging van commando’s is, namelijk elke iteratie, tijdens de executie van deze code. Functioneel programmeren daarentegen zal niet beschrijven hoe een programma functioneert, maar zal eerder beschrijven wat een programma is en welke problemen en deelproblemen het oplost. Het zal beschrijven hoe data getransformeerd wordt en nieuwe data geproduceerd wordt door het gebruik van expressies gebaseerd op de eerder vermelde lambda calculus. Net zoals deze mathematische functies gespecifieerd in de lambda calculus nemen functies in een functionele programmeertaal een aantal argumenten en geven ze een nieuwe waarde terug na het eventuele transformeren van de verkregen argumenten. Deze functies zullen altijd dezelfde waarde teruggeven wanneer dezelfde argumenten worden aangeleverd. Dit soort functies worden ook wel eens pure functies genoemd. (Wikipedia, 2016) 10 Omdat programma’s geschreven in een functionele programmeertaal bestaan uit exclusief expressies – code die altijd tot een nieuwe waarde evalueert – is het nagenoeg onmogelijk – er zijn uitzonderingen – om bestaande variabelen te muteren. Dit is ook wel gekend als ‘immutability’. In combinatie met het concept van pure functies zorgt het voorgaande ervoor dat er geen bijwerkingen ontstaan wanneer een functie uitgevoerd wordt. Met bijwerkingen wordt er in deze context bedoeld dat wanneer een functie meerdere keren uitgevoerd wordt binnen een programma, de staat waarin dit programma verkeert altijd exact hetzelfde blijft. Het merendeel van de functionele programmeertalen laat de programmeur toe om in kleinere mate bijwerkingen te hebben, maar het ultieme streefdoel is toch uiteindelijk geen bijwerkingen (eg. Haskell). Ter verduidelijking, de equivalent van het vorige codevoorbeeld geschreven in een functionele programmeertaal: -- Haskell -- veronderstel: array = [1, 2, 3, 4, 5] som = sum(array) De functie sum neemt 1 argument en geeft de som van alle waarden terug. Er zijn geen bijwerkingen en er worden geen waarden gemuteerd, alleen maar geïnitialiseerd. Merk op dat de taal zelf de developer een abstractie geeft – namelijk sum – en ervoor zorgt dat de developer zich geen zorgen moet maken over hoe de som van een array gevonden wordt. Dit alles in tegenstelling tot imperatieve talen waar bijwerkingen bij functies perfect mogelijk zijn: //C# int nummer = 1 public int functieMetBijwerkingen(n) { nummer++; return nummer * n; } functieMetBijwerkingen(5); // = 10 functieMetBijwerkingen(5); // = 15 Aangezien de meeste talen over expressies beschikken kan de aandachtige lezer opmerken dat het niet onmogelijk is om het merendeel van de tijd zonder bijwerkingen en mutaties van variabelen te werken met imperatieve programmeertalen. Dit is een correcte opmerking, maar, proberen om in een functionele stijl in een van deze talen te werken geeft de developer 11 een veel minder aangename ervaring dan in een functionele programmeertaal, aangezien deze een aantal features aanbieden die ervoor zorgen dat een functionele programmeertaal toch uniek is. Dit zijn features zoals hogere-ordefuncties (higher order functions), luie evaluatie (lazy evaluation) en recursie. Er zijn nog vele meerdere aspecten van het functionele programmeren die het uniek maken – pattern matching, abstract data structures, etc. - maar deze zijn buiten het bereik van deze scriptie of minder relevant voor het volgende hoofdstuk. (Hudak, 1989) Hogere-ordefuncties zijn functies die als argument één of meerdere functies kunnen nemen en/of een functie teruggeven. Dit is mogelijk in functionele programmeertalen omdat functies gezien worden als first-class citizens, m.a.w., in de ogen van een functionele programmeertaal zijn functies equivalent aan bijvoorbeeld een nummer. Zoals een nummer kan doorgegeven worden als argument aan een functie, teruggegeven kan worden door een functie en toegewezen kan worden aan variabele, zo ook kan dit allemaal met een functie gebeuren. (Lipovača, 2016) Ter verduidelijking, een voorbeeld van een hogere-ordefunctie: -- Haskell plusEen x = x + 1 resultaat = map plusEen [1, 2, 3, 4, 5] -- [2, 3, 4, 5, 6] plusEen is een functie die 1 argument neemt, namelijk x en die simpelweg 1 bij x optelt en vervolgens het resultaat teruggeeft. Map is in dit geval de hogere-ordefunctie die plusEen als argument neemt en deze toepast op alle waarden uit de lijst [1, 2, 3, 4, 5]. Dit concept van hogere-orde functies zorgt ervoor dat men gebruik kan maken van partial application. Dit wil zeggen dat wanneer men aan een functie die bijv. 2 argumenten verwacht maar 1 argument meegeeft, er een functie wordt teruggeven die als argument het eerder niet gegeven tweede argument verwacht. Dit wordt o.a. toegepast om acties of functionaliteiten die meerdere keren plaats kunnen vinden te abstraheren en deze vervolgens partieel te appliqueren om nieuwe functies te creëren (Wiki.Haskell, 2016) Bijvoorbeeld : -- Haskell plusTwee x = x + 2 plusDrie x = x + 3 -----------------plus n x = n + x plusTwee = plus 2 12 plusDrie = plus 3 plusTwee 3 -- 5 Hier zijn er in de bovenste helft twee functies gedefinieerd, namelijk plusTwee en plusDrie. Ze nemen beide een argument x en tellen daarbij 2 of 3. Het is duidelijk dat beide functies éénzelfde actie uitvoeren (optellen), maar simpelweg met andere getallen. Partial application laat ons toe om dit gedrag te abstraheren, namelijk in de functie plus. Deze functie neemt 2 getallen en telt deze op. Wanneer er vervolgens maar 1 argument wordt toegepast op plus, geeft deze een functie terug die een argument verwacht om deze uiteindelijk op te tellen bij het originele argument. Hogere-ordefuncties en het abstraheren van gemeenschappelijke gedragen zorgt ervoor dat applicaties korter geschreven kunnen worden en zorgen er ook voor dat de code simpeler is om te begrijpen, aangezien ten eerste de actie duidelijker omschreven kan worden (bijvoorbeeld bij een functie zoals map zie je duidelijk dat er een bepaalde functie zal toegepast worden op een lijst terwijl dat bij een simpele for-lus de intentie van de code vaak niet compleet duidelijk is) en ten tweede, omdat functies bij functionele programmeertalen functies als paramater kunnen nemen, heeft dit als gevolg dat ze herbruikbaarder zijn dan bij imperatieve programmeertalen. (Erlang, 2016) Wanneer in o.a. een imperatieve taal code wordt geëvalueerd, worden alle expressies onmiddellijk geëvalueerd, bijvoorbeeld: //pseudocode function delen(teller, noemer) { if (noemer == 0) return null; return teller / noemer; } delen((5+2), 0); In dit voorbeeld zal de expressie (5+2) geëvalueerd worden nog voor de body van de functie wordt uitgevoerd. Dit heet eager evaluation, eager in de zin dat expressies gretig worden geëvalueerd wanneer deze in de loop van de executie van een programma aangetroffen worden. In een functionele taal met lazy evaluation – niet alle functionele talen hebben lazy evaluation – worden expressies maar geëvalueerd eens ze nodig zijn, ook wel gekend als call-by-need. Indien bovenstaande pseudocode uitgevoerd zou worden in een functionele taal zoals Haskell dan zou (5+2) maar geëvalueerd worden eens de waarde daadwerkelijk nodig is bij teller / noemer, wat in bovenstaand code voorbeeld niet eens het geval is. (Wikipedia, 2016) 13 Het hebben van lazy evaluation is maar realistisch en bruikbaar in een taal waar men vrijwel alleen maar werkt met pure functies. Wanneer er namelijk met pure functies gewerkt wordt, heeft de volgorde waarin expressies geëvalueerd worden geen gevolg op het uiteindelijke resultaat. Bij een imperatieve taal daarentegen kan men dit niet garanderen omwille van de eventuele aanwezigheid van bijwerkingen (side-effects). (Delft University of Technology, 2014) Deze manier van evalueren brengt enkele voordelen met zich mee. Ten eerste voorkomt het onnodige evaluaties van expressies, ten tweede geeft het de developer de mogelijkheid om te werken met oneindige lijsten en ten derde, omdat het nooit meer werk doet dan noodzakelijk, wordt je code meer modulair. (Delft University of Technology, 2014) Om het eerste voordeel te verduidelijken staat hier de implementatie van het QuickSort algoritme in Haskell geschreven (Eidhof, 2008): -- Haskell quickSort [] = [] quickSort (x:xs) = quickSort (filter (< x) xs) ++ [x] ++ quickSort (filter (>= x) xs) minimum ls = head (quickSort ls) De implementatie van het algoritme op zich is hier niet van belang, buiten het feit dat bij QuickSort de gegeven list recursief in twee wordt gesplitst op basis van een pivot (hier voorgesteld door x) en zo gesorteerd wordt. In de minimum functie wordt de functie head gebruikt, die, gegeven een list, het eerste element teruggeeft. Omdat Haskell lazy evaluation gebruikt, en men alleen maar het eerste element nodig heeft uit de – nog niet bestaande – finale gesorteerde lijst zal telkens maar het eerste deel van de quickSort functie (aangeduid met pijl) uitgevoerd worden tot het uiteindelijk het eerste element vindt. In een taal met eager evaluation zou eerst de gehele lijst gesorteerd worden om vervolgens het eerste element te nemen. Natuurlijk heeft dit ook zijn nadelen. Aangezien je niet altijd weet welke expressie wanneer geëvalueerd zal worden, is het moeilijk om te redeneren over het aantal geheugen dat een applicatie zou gebruiken met lazy evaluation. Dit zorgt er, in het grote merendeel van de gevallen, niet voor dat je code minder performant is, maar het is goed om te weten. Er moet ook rekening gehouden worden met het feit dat – zoals al eerder werd aangehaald – lazy evaluation de developer dwingt om volledig puur (zonder side-effects) te werken. (University of Pennsylvania, 2014) 14 Zoals eerder al is uitgelegd, is het concept van het toewijzen van een waarde aan een variabele in een functionele programmeertaal onbestaand. Bij een expressie als deze wordt de waarde 5 niet toegewezen aan x d.m.v. =. x = 5 Het gelijk-aanteken betekent in dit geval dat het linker- en rechtergedeelte verwisselbaar zijn, ze zijn volledig gelijk aan elkaar. Het is dan ook logisch dat de variabele x onveranderlijk (of ‘immutable’) is, aangezien we expliciet vermelden in de code dat x gelijk is aan 5, op een zelfde manier dat 1 = 1. Dit heeft als gevolg dat een evaluatie in eender welke context altijd naar dezelfde waarde evalueert, een concept dat ook wel gekend is als ‘referential transparency’. Bovenstaande in combinatie met de notie van pure functies in functionele programmeertalen vermindert het risico op bugs of dergelijke op een drastische manier. Je zou zelfs kunnen stellen dat het merendeel van de bugs die gevonden worden in applicaties een direct gevolg zijn van functies met bijwerkingen. Met functionele talen heeft de developer absolute zekerheid in welke staat een programma op een gegeven moment zich begeeft. Lazy evaluation op zijn beurt elimineert de nood aan een vorm van flow control die men normaal gezien nodig heeft bij een imperatieve taal. (Haskell Wiki, 2016) Ook zijn eenzelfde programma’s geschreven in een functionele taal gemiddeld 2 tot wel 10 keer korter dan een variant geschreven in een imperatieve taal. Dit haakt terug op het eerder vermelde verschil tussen imperatieve en functionele programmeertalen, namelijk dat functionele talen de nadruk leggen op wat een programma zal berekenen in plaats van hoe een programma iets zal berekenen. Functionele talen zijn in dit opzicht ‘high-level languages’, waarmee bedoeld wordt dat functionele talen zeer veel handige functionaliteit abstraheren van de machine taal die uiteindelijk door de computer uitgevoerd wordt, veel meer dan imperatieve talen die gaande van C – zeer low-level, weinig abstractie – tot een taal zoals C#, in zekere zin nog altijd wat abstractie hebben, maar niet op hetzelfde niveau als functionele programmeertalen. Code geschreven in een functionele taal leest eerder als een algoritme dan een stel instructies gegeven aan de computer. Bijvoorbeeld, hier volgt, opnieuw, het QuickSort algoritme, maar dan geschreven in C++ en Haskell, door de Haskell Wiki (Haskell Wiki, 2016): //C++ template <typename T> void qsort (T *result, T *list, int n) { if (n == 0) return; T *smallerList, *largerList; smallerList = new T[n]; largerList = new T[n]; T pivot = list[0]; int numSmaller=0, numLarger=0; for (int i = 1; i < n; i++) if (list[i] < pivot) 15 smallerList[numSmaller++] = list[i]; else largerList[numLarger++] = list[i]; qsort(smallerList,smallerList,numSmaller); qsort(largerList,largerList,numLarger); int pos = 0; for ( int i = 0; i < numSmaller; i++) result[pos++] = smallerList[i]; result[pos++] = pivot; for ( int i = 0; i < numLarger; i++) result[pos++] = largerList[i]; delete [] smallerList; delete [] largerList; }; -- Haskell qsort [] = [] qsort (x:xs) = qsort less ++ [x] ++ qsort more where less = filter (<x) xs more = filter (>=x) xs Merk op dat de C++ code stukken langer en complexer is dan de Haskell code. Een developer die nog nooit QuickSort heeft gezien zal a.d.h.v. de C++ code niet onmiddellijk snappen hoe dit algoritme werkt, mede omdat de taal nog altijd omgaat met enkele low-level features. Men kan vaag afleiden uit de code welke stappen de computer gaat ondernemen ‘under the hood’, i.p.v. zich volledig op de probleemstelling te richten. De Haskell code daarentegen is stukken korter en toont de developer wat het berekent i.p.v. in detail te gaan over hoe het resultaat berekent wordt. Ook is de code te begrijpen in één oogopslag, dit door de eerder al vermelde high-level abstracties. Dezer tijden wordt computer snelheid ook almaar goedkoper, terwijl de tijd dat een developer spendeert aan een project juist duurder wordt. Programma’s geschreven in een functionele programmeertaal zijn dan misschien wel niet de meest performante programma’s – ze zijn ook niet de minst performante – maar worden in een veel kortere tijd geproduceerd, wat uiteraard voordelig is voor de klant. Met andere woorden, hoe meer men streeft naar een hoger level van abstractie, hoe productiever de developer wordt. (Ford, 2013) Verder is concurrency (programmeren op meerdere threads) almaar belangrijker aan het worden. In vele imperatieve talen is het een ongelooflijke nachtmerrie om te werken met concurrency en tegelijkertijd een duidelijk overzicht te hebben van de flow van data doorheen je applicatie, aangezien de developer altijd op zijn hoede moet zijn omtrent data en de huidige 16 state ervan. Immutability, pure functies etc., concepten die standaard beschikbaar zijn in een functionele programmeertaal, maken het leven van de developer stukken gemakkelijker bij het werken met concurrency of parellelisme. Een laatste reden om in een functionele stijl te programmeren is hoe sterk modulariteit aanwezig is. Modulariteit niet alleen in de zin van verschillende modules, maar ook hoe eenvoudig het is om probleem op te delen in – eventueel herbruikbare – deelproblemen. Waarom is een functionele taal dan zo geschikt om aan modulair programmeren te doen? Volgens John Hughes is de reden als volgt: Languages which aim to improve productivity must support modular programming well. But new scope rules and mechanisms for separate compilation are not enough modularity means more than modules. Our ability to decompose a problem into parts depends directly on our ability to glue solutions together. To assist modular programming, a language must provide good glue. Functional programming languages provide two new kinds of glue - higher-order functions and lazy evaluation. (Hughes, 1984) Indien we nu eerst even een stap terugnemen en een programma geschreven in een functionele taal op een wat abstractere wijze observeren, blijkt het dat deze één grote functie is die een input neemt en een output produceert. Wanneer dit programma terug onder de loep genomen wordt, valt op dat deze volledig opgebouwd is uit verschillende functies die op hun beurt ook opgebouwd zijn uit verschillende functies, allemaal samengehouden door de zogenaamde ‘glue’ van het functionele programmeren, higher-order functions en lazy evaluation. Zo kan een developer een applicatie continu opsplitsen in meerdere deelproblemen tot een deelprobleem niet verder dient opgesplitst te worden omdat deze zich volledig focust op één enkel, klein en handelbaar probleem dat vertaalt kan worden in een korte en pure functie. Later kunnen al deze deelproblemen samengesteld worden met behulp van de zogenaamde ‘glue’ – ook wel composing genaamd – om zo uiteindelijk een resultaat te bekomen dat als antwoord dient op het oorspronkelijke probleem dat de applicatie voorop bracht. Of zoals Rich Hickey ooit vermelde tijdens een presentatie: “Composing simple components, simple in that same respect, is the way we write robust software.” (Hickey, 2011). Components in de zin van deelproblemen die volledig individueel capabel zijn en niet afhankelijk zijn van andere deelproblemen. Zoals alles komt functioneel programmeren natuurlijk ook met zijn nadelen. Een daarvan is al eerder aangehaald bij het onderdeel over lazy evaluation, namelijk dat het zeer moeilijk is om op voorhand te weten hoeveel geheugen of tijd een programma geschreven in een functionele taal exact nodig heeft. Ook is functioneel programmeren minder geschikt voor embedded systems of andere omgevingen waar het nodig is om low-level te gaan werken, 17 omdat ten eerste – zoals eerder vermeld – vrijwel alles low-level is geabstraheerd in een functionele taal, en ten tweede een functionele programmeertaal niet het model van de werking van een computer volgt. Deze werking is fundamenteel imperatief, in die zin dat het state heeft en met elke instructie deze state aangepast wordt. Als gevolg is het dan ook veel moeilijker om de snelheid van een in een functionele taal geschreven programma te optimaliseren zoals een imperatieve taal als C of C++ dit zou kunnen doen. (Duchene, 2014) Uiteindelijk is de instapdrempel van een functionele programmeertaal vrij hoog voor de gemiddelde developer die vrijwel zijn gehele carrière met imperatieve talen gewerkt heeft. Niet alleen omdat het een nieuwe syntax heeft maar, belangrijker, omdat vele concepten toegepast in een imperatieve taal – zelfs iets simpel als de notie van variabelen – volledig opnieuw aangeleerd moeten worden. Ook moet er natuurlijk rekening gehouden worden met het feit dat de huidige ecosystemen van de meeste functionele programmeertalen niet kunnen tippen aan die van talen zoals Java op gebied van open source libraries en dergelijke. (Jelvis, 2014) Samengevat, voor het merendeel van de programmeurs – die niet werken met embedded systems of dergelijke – is het voordeliger om een functionele taal te leren, ongeacht de initiële instapdrempel, omdat de productiviteit van de programmeur en de onderhoudbaarheid en stabiliteit van een programma er alleen maar vooruit opgaan in een functionele omgeving vergeleken met een imperatieve omgeving, vooral in teamverband. Het feit dat functionele talen minder performant zijn is wel degelijk nog altijd aanwezig, maar de almaar goedkopere aanwezigheid van computersnelheid en het almaar duurder worden van developer tijd zorgt er voor dat de voordelen van een functionele taal harder doorwegen dan de nadelen. Maar op het einde van de dag is er niks absoluut en moet er nog altijd gekozen worden op basis van het probleem, en niet wat er momenteel het meest populair is. Voor velen is JavaScript samen met C# of Java een van de eerste talen die ze leren. De lessen, tutorials, etc. die beginners nemen voor deze talen leggen vrijwel altijd de nadruk op een imperatieve stijl of nog specifieker een OOP stijl – ook al is JavaScript vrij arm op het gebied van OOP – en krijgen maar veel later de memo dat JavaScript meer dan geschikt is om in een functionele stijl te gaan programmeren. De syntax is dan wel gebaseerd op C, conceptueel gezien lijkt JavaScript meer op functionele programmeertalen zoals Lisp of Scheme. (Wikipedia , 2016) Recent is het zo dat functioneel programmeren in JavaScript steevast meer populariteit geniet van web developers, frontend en backend (in het geval van NodeJS). Deze opmars heeft meerdere oorzaken. Een ervan kan toegewijd worden aan het team achter JavaScript, en nog 18 specifieker ECMAScript, Ecma International. Dit is simpel gezegd een groep die de standaarden opmaakt omtrent de syntax en implementatie van de taal. Historisch gezien volgden aanpassingen aan ECMAScript zich zeer traag op – tot zelfs 10 jaar tussen ECMAScript 3 en ECMAScript 5 (ECMAScript 4 is geschrapt geweest) – maar hier is ondertussen al enig schot in gekomen, met een geplande nieuwe versie elk jaar. Sinds ECMAScript 5 zijn het merendeel van de nieuwe standaarden gericht op een functionele programmeerstijl, met een nog grotere nadruk in ECMAScript 6. Een tweede oorzaak van deze recente populariteit zijn libraries zoals Lodash, Underscore & Ramda die veel functionaliteit abstraheren in meerdere handige functies met een focus op functioneel programmeren. En een derde maar niet laatste oorzaak is de opmars van React. React op zichzelf is zeer populair geworden in vrij korte tijd, en mede door hype zijn enorm veel web developers op de spreekwoordelijke React-trein gesprongen. De implementatie van React op zich echter is verrassend vol van functionele concepten, zo ook zijn er enkele libraries die het omgaan met data bij o.a. React definiëren – zoals Redux – die gebaseerd zijn op bestaande functionele programmeertechnieken. Op deze manier worden veel programmeurs geïntroduceerd tot de wereld van functioneel programmeren. Voor er dieper wordt ingegaan op bovenstaande implementaties, moet het volgende wat nader verklaard worden, namelijk, wat houdt functioneel programmeren in JavaScript nu juist in? Net omdat JavaScript – zoals eerder al is vermeld – gebaseerd is op functionele programmeertalen zoals Lisp en Scheme, heeft het enkele eigenschappen die ervoor zorgen dat men vrijwel zonder enige nood aan externe libraries in een functionele stijl applicaties kan creëren. Let wel op, het is niet de bedoeling in dit hoofdstuk om aan te tonen dat JavaScript gebruikt kan worden als een volwaardige functionele programmeertaal zoals Haskell dat is. Het is echter wel de bedoeling om aan te tonen dat JavaScript een taal is die het mogelijk maakt om met meerdere concepten uit het functioneel programmeren te werken. De hele reden waarom een functionele programmeerstijl mogelijk is in JavaScript, is het feit dat functies gezien worden als first-class variables. var plus = function (a, b) { return a + b; } console.log(plus); //[Function] Logischerwijze kunnen we hieruit afleiden dat JavaScript ook kan werken met higher order functions, één en dezelfde higher order functions als in het voorgaande hoofdstuk. 19 Ondertussen zien we ook onmiddellijk dat JavaScript anonymous functions heeft, of anders genaamd, lambdas! var plus = function (a) { return function (b) { return a + b; } } var plus2 = plus(2); plus2(4) //6 Uit dit voorbeeld kunnen we nog een bouwstuk halen die JavaScript bezit om in een functionele manier te gaan programmeren, namelijk closures. Wanneer de functie plus gecalled met waarde 2 zal deze een functie teruggeven die deze waarde in het geheugen houdt, tot die functie later zelf gecalled wordt. Zonder closures zou partial application in JavaScript vrij nutteloos zijn. Jammer genoeg ziet de syntax van de anonymous functions er ietwat opgeblazen uit. Gelukkig is er in ECMAScript 6 (ES6) een nieuwe manier om anonymous functions te schrijven, op een manier die meer op een echte lambda gelijkt. var plus = a => b => a + b; var plus2 = plus(2); plus2(4) //6 Dit zijn arrow functions, waarbij alles voor de pijl argumenten zijn en alles erna de function body. Indien er geen accolades zijn, wordt de function body automatisch gereturned. Ook biedt JavaScript zelf ons ook enkele fundamentele functioneel georiënteerde functies aan om in een wat meer functionele stijl te programmeren. De meeste populaire techniek om over een array te itereren is de gekende for-loop. let array = ['cola', 'fanta', 'sprite']; for (var i = 0; i < array.length; i++) { array[i] = array[i].toUpperCase()); } Dit is duidelijk geïmplementeerd in een imperatieve manier. Er wordt veel code gebruikt om uit te drukken hoe we bovenstaande array willen overlopen, veel code die grotendeels 20 irrelevant is in relatie tot ons doel. Tegelijkertijd zorgt een for-loop in dit geval ervoor dat er veel meer ruimte is om fouten te maken. De functionele manier zou zijn om deze functionaliteit te abstraheren om uiteindelijk een duidelijkere en meer leesbare implementatie te hebben. Vanaf ES5 biedt JavaScript dergelijke abstracties van de for-loop aan, abstracties die fundamenteel zijn in functionele programmeertalen. Dit zijn higher order functions zoals map (eerder al gezien in het eerste hoofdstuk), reduce en filter (forEach abstraheert ook een forloop maar is geen fundamentele functie in een functionele programmeertaal omdat het niet resulteert in een nieuwe waarde en aanzet tot het gebruiken van side effects, maar het is wél een higher order function). Hoe zou bovenstaande code voorbeeld eruitzien wanneer we de map functie gebruiken? const array = ['cola', 'fanta', 'sprite']; const arrayMetHoofdLetters = array.map(soda => soda.toUpperCase()); Dit is korter en – nog belangrijker – het is declaratiever. Met andere woorden, deze code drukt uit wat we willen doen, namelijk, pas de anonymous function soda => soda.toUpperCase() toe op alle elementen in de array en geef mij het resultaat hiervan terug. Deze manier helpt ons ook om immutability toe te passen. De functie map muteert niet de oorspronkelijke array, maar geeft in de plaats een volledige nieuwe array. Een tweede abstractie die standaard te vinden is in JavaScript is filter, net zoals map een higher order function. Zoals de naam waarschijnlijk al doet vermoeden, zal deze abstractie een array filteren op basis van een gegeven functie die een boolean waarde teruggeeft. const array = [{naam: 'John', leeftijd: 21}, {naam: 'Peter', leeftijd: 44}, {naam: 'Carl', leeftijd: 12}]; const ouderDan13 = array.filter(person => person.leeftijd > 13); // [{naam: 'John', leeftijd: 21}, {naam: 'Peter', leeftijd: 44}] Indien dit met een for-loop geïmplementeerd zou worden zou er logica geschreven moeten worden om een nieuwe lijst aan te maken met personen ouder dan 13 of logica om personen jonger dan 13 uit de oorspronkelijke array te verwijderen. Opnieuw, omdat deze logica al geabstraheerd is in de filter functie is het duidelijker en declaratiever om het te gebruiken. Waarschijnlijk de krachtigste abstractie aanwezig op het array prototype is reduce, ook wel gekend als fold in vele functionele programmeertalen. Reduce is een functie die een array 21 neemt (in het geval van JavaScript neemt het niet letterlijk een array, het is eerder een methode op Array.prototype) en die één enkele waarde teruggeeft – eender wat – op basis van een functie die toegepast wordt op elk element uit deze array en de teruggegeven waarde van de vorige loop, ook wel de accumulator genaamd. (Mozilla, 2016) Een simpel voorbeeld hiervan is het optellen van alle nummers uit een array: const array = [1, 2, 3, 4, 5]; array.reduce((vorigeWaarde, huidigeWaarde) => vorigeWaarde + huidigeWaarde); // 15 Dit voorbeeld kan ervoor zorgen dat de lezer een verkeerd beeld krijgt van reduce, namelijk dat het alle waarden in een array als het ware samenperst tot één waarde die van hetzelfde type is van de elementen. Niets is minder waar. Reduce kan namelijk een extra parameter nemen die de initiële waarde – of in het vorige code voorbeeld vorigeWaarde – vertegenwoordigd. In volgend codevoorbeeld is de initiële waarde de index van een nog niet gevonden waarde in een array, -1. Bij elke iteratie wordt gecontroleerd of de huidige waarde onze gezochte waarde is of niet, en wordt er eventueel de index teruggegeven als de huidige waarde daadwerkelijk onze gezochte waarde is. (UILab, 2016) const indexVan = (zoek, array) => { return arr.reduce((vorigeWaarde, huidigeWaarde, index) => { return huidigeWaarde === zoek ? index : vorigeWaarde; }, -1); } indexVan('druif', ['appel', 'banaan', 'sinaasappel']); // -1 indexVan('banaan', ['appel', 'banaan', 'sinaasappel']); // 1 Vrijwel alle methodes die beschikbaar zijn op het prototype van Array die standaard aanwezig zijn in JavaScript kunnen met reduce herschreven worden, terwijl geen enkele van die methodes reduce kan implementeren, zelfs niet map. Dit is mogelijk net omdat reduce éénder wat kan teruggeven in plaats van simpelweg een nieuwe array. Stel dat we filter zouden herschrijven met reduce, hoe zou dit eruit zien? (UILab, 2016) const filter = (array, fn) => { return array.reduce((vorigeWaarde, huidigeWaarde) => vorigeWaarde.concat(fn(huidigeWaarde) ? [huidigeWaarde] : []), []); } filter([2, 4, 8, 9], (n) => n % 2 === 0); // [2, 4, 8] Het is duidelijk dat reduce een van de krachtigere functies is die JavaScript bezit om een array te itereren. Het nemen van een array om per element iets te aggregeren tot een eindresultaat 22 is een zeer handig concept die in vele situaties kan toegepast worden waar het merendeel van de programmeurs een for-loop zou gebruiken. Arrays (en gelijkaardige data structuren) worden enorm veel gebruikt in code, JavaScript of niet, en dus is de potentiële impact die het reguliere gebruiken van deze abstracties in code in plaats van for-loops zeker niet te onderschatten. Er moet natuurlijk wel vermeld worden dat for-loop’s nog altijd performanter zijn dan forEach, map, reduce etc. (JSPerf, 2016) In een vorig hoofdstuk was er een vrij grote nadruk op composition, het samenvoegen van meerdere, kleine, simpele en – het meest belangrijke – pure functies om een functie te verkrijgen die een complex probleem kan oplossen. Vervolgens kunnen deze compositions ook gebruikt worden in een andere composition en zo verder. Op deze manier kan eerder welke gewenste functionaliteit geconstrueerd worden, en kan de flow van data doorheen deze functies op een simpele manier gevolgd worden aangezien de volgorde expliciet wordt uitgedrukt. Het aanwezig zijn van composition zorgt ervoor dat er meer herbruikbare code geschreven wordt die kan gebruikt worden in meerdere verschillende function compositions, wat op zijn beurt unit testen gemakkelijker maakt. Een voorbeeld hiervan: var plus1 = a => a + 1; var maal2 = b => b * 2; var maal2plus1 = c => plus1(maal2(c)); maal2plus1(4); //9 In dit geval komt dit zeer simpel over, maar wanneer men abstracties gaat composen die een duidelijke naam hebben kan dit een zeer declaratief overzicht van je code geven. Bijvoorbeeld, er is een lijst die zelf enkele lijsten heeft van gegroepeerde taken die twee properties hebben, namelijk de id en of de taak al vervolledigd is. Nu wordt er gevraagd om alleen maar de taken te nemen die vervolledigd zijn en vervolgens deze te sorteren volgens de id. Dit kan als volgt geïmplementeerd worden: (Dit is momenteel nog geen correct JavaScript, aangezien de functies compose, filter, sorteer en map niet standaard in JavaScript zitten. Deze code is puur ter illustratie om de elegantie van compositie aan te tonen.) var vervolledigdEnGesorteerd = compose(sorteer(taak => taak.id), filter(taak => taak.vervolledigd == true)); map(vervolledigdEnGesorteerd, taken); 23 Compose(sorteer(…), filter(…)) hier is het equivalent van sorteer(taak => taak.id, filter(taak => taak.vervolledigd, [hier komt de variabele met taken]), compose zorgt ervoor dat de code in dit geval wat leesbaarder is. Merk ook op dat de functies sorteer en filter partieel geappliqueerd zijn. Wanneer deze functies gecalled worden met enkel de functie die uitgevoerd zal worden voor elke taak – ook wel het predicaat genoemd – zullen deze functies simpelweg een functie teruggeven die taken verwacht. Dus wanneer de functie vervolledigdEnGesorteerd vervolgens gebruikt wordt, verwacht deze taken. Die taken worden gebruikt in de filter functie die vervolgens zijn resultaat – gefilterde taken – doorgeeft aan sorteer die op zijn beurt ook een functie is die wacht op taken. Uiteindelijk gebruiken we onze functie vervolledigdEnGesorteerd in een map functie – die hetzelfde werkt als de gekende array.map() functie – die elke keer de composed functie toepast op een lijst van gegroepeerde taken. Indien dit probleem op een wat imperatievere manier was opgelost, met for-loops en gemuteerde variabelen zou de code veel minder duidelijk en kort zijn als in dit geval. Hier hebben we simpelweg – zoals al eerder beschreven is – enkele gekende abstracties genomen, namelijk sorteer en filter, en deze gecomposed tot een nieuwe herbruikbare functie, die op zijn beurt ook een abstractie is van onze probleemstelling: neem alleen de vervolledigde taken en sorteer ze volgens id. Omdat functionele programmeertalen geen equivalent van while of for hebben, worden alle loops die niet over een lijst of dergelijke gaan uitgevoerd door recursie. Korte herhaling, recursie is een concept waarbij een functie zichzelf oproept, en zo verder, tot er een zogenaamde ‘edge case’ is bereikt - simpele if (predicaat) - die de recursie stopt. Recursie is een van de bouwstenen van functioneel programmeren. In JavaScript daarentegen is het werken met recursie stukken risicovoller. Als een bepaalde functie teveel recursief wordt gecalled zal JavaScript ervoor zorgen dat de applicatie stopgezet wordt, aangezien er teveel onafgewerkte functies op de call stack. Dit wil niet zeggen dat recursie niet gebruikt kan worden in JavaScript. ES6 heeft er namelijk voor gezorgd dat tail call recursie mogelijk is zonder dat de call stack gevuld wordt met nog niet afgelopen functies. Met tail call wordt er bedoelt dat wanneer een functie zichzelf oproept, deze statement de laatste is in de functie en gereturned wordt. Bijvoorbeeld: var telAfNaar0 = getal => { if (getal < 0) return; console.log(getal); return telAfNaar0(getal - 1); } telAfNaar0(5); // 5 4 3 2 1 0 24 In ES5 zou de call stack gevuld zijn met 5 calls naar telAfNaar0, in ES6 wordt dit geoptimaliseerd en is er bij iedere recursie maar 1 call op de stack, namelijk de huidige. Maar waarom gebruik maken van recursie in de eerste plaats? Alles dat met een for statement geschreven is kan ook met recursie gecodeerd worden, maar, niet alles dat recursief geprogrammeerd is kan daadwerkelijk met een for loop geschreven worden. Stel – dit is vrij abstract uitgelegd – er is een object dat een dynamisch aantal geneste objecten heeft – conceptueel gelijkaardig aan een tree diagram of een DOM-structuur bijvoorbeeld – en het is nodig om elk individueel object te onderzoeken. Dit is vrijwel onmogelijk met for-loops omdat het ongekend is hoe diep een object genest kan zijn. (Johansson, Recursion - Part 7 of Functional Programming in JavaScript, 2015) Maar dit soort gecompliceerd probleem komt niet vaak voor in het dagdagelijkse leven van een developer. Los hiervan, recursie is een elegantere manier om een loop uit te drukken, omdat het – dit is al meerdere keer aangehaald in deze scriptie – duidelijk maakt wat er moet gebeuren in plaats van hoe er iets moet gebeuren. Dit kan opnieuw duidelijk gemaakt worden door telAfNaar0 imperatief te implementeren (in dit geval is er maar één for loop, maar wanneer er meerdere geneste for loops zijn, kan dit een stuk oneleganter zijn dan recursie): var telAfNaar0 = getal => { for (var i = getal; i >= 0; i--) { console.log(i); } } Lazy evaluation was een van de fundamentele features van - het merendeel van – de functionele programmeertalen. Standaard is dit niet aanwezig in JavaScript. Dit heeft als nadeel dat wanneer er bijvoorbeeld met een zeer grote array gewerkt wordt en wordt een vrij lange chain van operaties op die array toegepast (meerdere array methodes zoals map, filter, reduce etc.), dan zal bij elke stap van de chain de gehele array overlopen worden, terwijl op het einde van de chain misschien maar het eerste element uit het resultaat vereist is. Er zijn enkele externe libraries die dit probleem verhelpen, maar meer daarover in het volgende hoofdstuk. In het vorige hoofdstuk is er aangetoond dat standaard JavaScript zich leent tot het toepassen van concepten uit het functioneel programmeren, maar in zekere zin is het nog vrij laks. Als gevolg van de globale populariteit van JavaScript is er een enorme open source cultuur, met alsmaar nieuwe frameworks, libraries, tools etc. die inspiratie nemen uit bestaande talen of dergelijke. Het is dan ook niet toevallig dat de recente populariteit van een functionele programmeerstijl in JavaScript grotendeels toe te wijden is aan enkele van deze nieuwe open source projecten die dergelijke programmeerstijl promoten, gepaard met de – al dan niet verdiende – hype die deze recente projecten genieten, een eigenschap eigen aan het 25 gigantische JavaScript open source ecosysteem. Hier volgt een selectie van dergelijke projecten. A programmer armed with a repertoire of fundamental functions and, more importantly, the knowledge on how to use them, is much more effective than one who starts from scratch. Unfortunately, a standard JavaScript environment comes with deplorably few essential functions, so we have to write them ourselves or, which is often preferable, make use of somebody else’s code. (Haverbeke, 2011) Libraries die dergelijke essentiële functies ter beschikking stellen zijn ook wel gekend als utility libraries. De meest bekende hiervan zijn Underscore en Lodash. Deze bieden handige functies aan om over objecten, arrays en strings te itereren en om eenvoudiger met objecten te kunnen werken. Ook zijn er meerdere handige predicaatfuncties, zoals bijvoorbeeld isUndefined() i.p.v. een if statement (handig voor composition), meerdere utility functions voor functies zelf en nog veel meer. Ondanks wat de documentatie van Underscore vertelt, is het geen volledig functioneel programmeergerichte utility library, omdat in tegenstelling tot de andere twee alternatieven – Lodash en Ramda - de helper functies niet automatisch gecurried worden (vergelijkbaar met partieel appliqueren, een gecurriede functie geeft altijd een nieuwe functie terug tot het al zijn argumenten verkregen heeft) en is de data niet het laatste argument bij functies. Er moet opgemerkt worden dat Lodash op zich ook niet deze eigenschappen bezit, maar het stelt een aparte versie ter beschikking – Lodash/FP – dat deze eigenschappen wel beschikt. Om deze verschillen te illustreren volgt een vergelijking tussen Underscore en Lodash/FP. (Lonsdorf, Hey Underscore, You're Doing It Wrong!, 2013) //Underscore var eersteTweeLetters = function (woorden) { return _.map(words, function (woord) { return _.first(woord, 2); }); }; //Lodash/fp var eersteTweeLetters = _.map(_.take(2)); eersteTweeLetters(['mike', 'sara']); // ['mi', 'sa']; (_.first() uit Underscore en _.take() uit Lodash hebben in deze situatie dezelfde functionaliteit, ze nemen een array – of in dit geval een string – en nemen een getal dat aanduidt hoeveel elementen van de array het teruggeeft. Hier is het Lodash/fp voorbeeld een stuk korter, omdat het – volgens de documentatie – “Immutable auto-curried iteratee-first data-last methods” zijn. (Lodash, 2016) Argumenten 26 die later gegeven zullen worden, worden niet vermeld omdat _.map() en _.take() beide al partieel geappliqueerd zijn en simpelweg functies zijn die de nodige data verwachten. Deze stijl van programmeren is ook wel gekend als ‘point-free programming’, een onderdeel van functioneel programmeren, waarbij functies de data argumenten waarmee ze werken niet vermelden. (Wikipedia, 2016) Deze stijl van programmeren heeft ook nadelen, namelijk dat het kan leiden tot enige obfuscatie. Het kan moeilijk zijn om de intentie van een functie af te leiden indien deze functie ongekend is door de developer. Daarom is het soms beter om met gewone functies te werken wanneer nodig. (Lonsdorf, Professor Frisby's Mostly Adequate Guide To Functional Programming, 2016) Verder heeft Lodash/FP enkele functies die de developer helpen om op een meer functionele manier te schrijven. Een van deze functies is al eerder de revue gepasseerd in het vorige hoofdstuk – maar waarvan er toen gezegd was dat het geen werkende standaard JavaScript code was – namelijk compose. Wat compose juist is, is al uitgelegd. Currying en composition gaan hand in hand wanneer er in een functionele stijl geprogrammeerd wil worden en zorgen ervoor dat er beknopte statements geschreven kunnen worden die eruitzien alsof er data doorheen een compose functie gesluisd wordt. //onbestaande functies var pasAndereAbstractieToe = _.compose(neemEnkeleElementen, sorteerIets) var pasAbstractieToe = _.compose(reduceIets, pasAndereAbstractieToe); var resultaat = _.compose(pasAbstractieToe, mapIets, filterIets); Dit in tegenstelling tot de manier die Underscore aanraadt om meerdere abstracties in één statement uit te voeren, namelijk met de _.chain() functie die een data argument neemt en deze vervolgens verpakt of ‘wrapped’ in een object waardoor er continu een ‘.’ kan gebruikt worden om als het ware statements te chainen. _.compose() op zich is meer functioneel – en de duidelijke keuze bij het programmeren in een functionele stijl – en is meestal iets beknopter maar uiteindelijk zijn beide _.compose() en _.chain() een manier om meer leesbare code te schrijven in JavaScript, kennelijk dienen ze hetzelfde doel. (Lonsdorf, Hey Underscore, You're Doing It Wrong!, 2013) Om te illustreren dat het gebruiken van abstracties uit deze utility libraries en het gebruiken van curried functies code meer expressiever en leesbaarder maken volgt hier een codevoorbeeld dat een typisch scenario uitbeeldt, een scenario dat elke developer waarschijnlijk ooit wel in een of andere vorm zal aantreffen. Deze code komt uit een blogpost geschreven door één van de makers van Ramda. (Sauyet, 2016) Ramda is een library die nog een stapje extra neemt in de richting van functioneel programmeren. Het is momenteel nog niet aangeraden om het te gebruiken in productie omdat het nog geen 1.0 versie heeft. Dit wil zeggen dat de library vaak drastische aanpassingen maakt die functionaliteit breekt. Stel, er is een request naar een REST API die enkele taken teruggeeft voor een specifieke user. 27 var data = { tasks: [ {id: 104, complete: false, dueDate: "2013-11-29", title: "Do something", {id: 105, complete: false, dueDate: "2013-11-22", title: "Do something else", {id: 107, complete: true, dueDate: "2013-11-22", title: "Fix the foo", ] }; priority: "high", username: "Scott", created: "9/22/2013"}, priority: "medium", username: "Lena", created: "9/22/2013"}, priority: "high", username: "Mike", created: "9/22/2013"} Er is een functie nodig die alle onvoltooide taken neemt, daarvan alleen maar enkele nodige properties neemt en die dit vervolgens sorteert op de deadline. De volgende code is al in een functionele stijl geschreven, maar kan het nog beknopter en leesbaarder? var getIncompleteTaskSummaries = function (membername) { return fetchData().then(function(data) { return data.tasks.filter(function(t){ return t.username == membername && !t.complete; }).map(function(t){ var copy={}, props=["id","dueDate","title","priority"], p; while(p=props.pop()){ copy[p] = t[p] } return copy; }).sort(function(first, second) { return first.dueDate - second.dueDate; }); }); }; Indien dit probleem opgelost zou worden door een beginnende programmeur zou het nog langer, onduidelijker en imperatiever zijn. Dit probleem geïmplementeerd met utility functions uit de Ramda library – die gelijkaardig met Lodash/FP automatisch gecurried zijn – zou er als volgt uitzien: var getIncompleteTaskSummaries = function (membername) { return fetchData() .then(R.get('tasks')) .then(R.filter(R.propEq('username', membername))) .then(R.reject(R.propEq('complete', true))) .then(R.map(R.pick(['id', 'dueDate', 'title', 'priority']))) .then(R.sortBy(R.get('dueDate'))); }; 28 Qua lengte is er in dit geval een miniem verschil, maar volgens de schrijver van de blogpost is dit veel expressiever en duidelijker. Hoe complexer het probleem wordt, hoe handiger het wordt om het op te splitsen in deelproblemen die beknopter – met minder boilerplate die niet relevant zijn aan het probleem – kunnen uitgedrukt worden a.d.h.v. dergelijke utility functions en functies zoals _.compose(). Wie weet moet er een maand nadat een bestaande app gereleaset is een nieuwe functionaliteit toegevoegd worden die verder bouwt op bestaande functionaliteit. Het reeds hebben van meerdere herbruikbare abstracties – die soms dataagnostisch zijn – kan van pas komen om de gerelateerde nieuwe functionaliteit snel toe te voegen. Er moet opgemerkt worden dat wanneer bovenstaand probleem opgelost wordt in ES6 samen met een library zoals Underscore of Lodash, dat het ook compact en leesbaar geïmplementeerd kan worden met behulp van de nieuwe arrow functies. Het lijkt misschien wat minder op traditionele functionele programmeertalen als Haskell, maar opnieuw, beide manieren streven éénzelfde doel na, duidelijke code schrijven. De keuze ligt aan de developer op welke manier deze het liefst leesbare code schrijft – ‘point-free’ of klassiek met ES6 – zolang het doel blijft om op een beknopte en expressieve wijze problemen op te lossen, en hierbij kunnen utility libraries zoals Lodash met zeer veel handige abstracties een enorme hulp zijn, functionele versie of niet. Eerder werd er ook vermeld dat standaard JavaScript niet beschikt over lazy evaluation. Lodash & Ramda beschikken in zekere zin over lazy evaluation met enkele methodes, en in het geval van Lodash wordt lazy evaluation maar toegepast eens er gewerkt wordt met arrays of dergelijke met meer dan 200 elementen. Een andere library die zich volledig focust op lazy evaluation en die gelijkaardige utility functions heeft als Underscore of Lodash is Lazy.js. (LazyJS, 2016) React is – ondanks wat velen denken – geen framework zoals AngularJS er een is. Het is simpelweg een library die jou een manier geeft om ‘componenten’ te maken in een template taal (JSX) die HTML renderen en waarvoor enkele lifecycle functies of dergelijke worden aangeboden. Het wordt ook wel gezien als de V (view) in MVC en dus is het vanzelfsprekend niet genoeg om een gelijkaardige applicatie met alleen maar React te maken zoals dit zou kunnen gemaakt worden met een framework zoals AngularJS of Ember. Voor een wat diepere kijk op React kan de lezer terecht op de officiële pagina. (React, 2016) Deze library wordt hier aangehaald omdat het de eerste enorm populaire library is die concepten uit het functioneel programmeren nadrukkelijk promoot. Vanuit een zeer abstract opzicht is het werken met React components al een vorm van composition. <Menu> <MenuItem>Product</MenuItem> <MenuItem>Over Ons</MenuItem> </Menu> 29 Componenten in JSX (bijvoorbeeld <Menu>) kunnen ook gezien worden als functies die componenten creëren aan de hand van verkregen argumenten ook wel gekend als props in het React ecosysteem. Hier kan het concept van pure functies perfect gebruikt worden. Geef dezelfde props aan een component en de developer kan er 100% zeker van zijn dat altijd hetzelfde resultaat gerendered zal worden. Het merendeel van de componenten in een volledige applicatie kunnen geschreven worden als een pure functie en om deze reden heeft React recent ook een nieuwe syntax geïntroduceerd die ervoor zorgt dat de developer een React component letterlijk als een functie kan schrijven. const Text = ({ content }) => <p>{content}</p>; Buiten het feit dat dit soort componenten React-agnostisch zijn en dus gebruikt kunnen worden als eender elke andere functie zijn er nog enkele extra voordelen. Als men het gebruik van deze soort componenten kan maximaliseren is het vanzelfsprekend dat het gemakkelijker wordt om te redeneren over de werking en data flow van een React applicatie. Een tweede voordeel, gegeven door het React team in een blogpost over een nieuwe release van React gaat als volgt: This pattern is designed to encourage the creation of these simple components that should comprise large portions of your apps. In the future, we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations. (React, 2015) Zoals eerder vermeld is, is React enkel maar een UI library, dus kan onder andere het omgaan met state in je applicatie geïmplementeerd worden met een andere library. Een van deze libraries is Redux, gebaseerd op het patroon Flux dat oorspronkelijk door Facebook gelijklopend met React is geïntroduceerd om de data flow uni-directioneel te organiseren (voor meer informatie kan de lezer terecht op de documentatie van Flux (Flux, 2016)). Redux zorgt ervoor dat alle state voor componenten in één object zit in een zogenaamde store, ook wel gekend als de ‘single source of truth’. De enige manier om de state aan te passen is als er ergens een action wordt ge-emit. Een action beschrijft wat er gebeurd is, bijvoorbeeld NEW_MESSAGE, en kan eventueel nieuwe data met zich meegeven. Deze actions worden verwerkt door reducers (zie vorig hoofdstuk), functies die a.d.h.v. een action de huidige state uit de store nemen en deze reducen tot de nieuwe state met optionele data meegegeven met de action. Vervolgens wordt het state object in de store vervangen door deze nieuwe state. (Redux, 2016) Deze library is duidelijk gebaseerd op enkele functionele concepten. Een eerste concept is immutability. Elke keer een action wordt ge-emit wordt de oorspronkelijke state niet gemuteerd, maar wordt eerder volledig opnieuw aangemaakt als er aanpassingen zijn. Dit zorgt ervoor dat de state als ‘read-only’ gezien kan worden, buiten het emitten van actions. Op deze manier is er een duidelijk overzicht over mogelijke veranderingen in de state en over de strikte volgorde van deze actions. Dit gaat hand in hand met een tweede concept, namelijk dat reducers pure functies zijn die ervoor zorgen dat aanpassingen aan de state zeer 30 voorspelbaar zijn. Dit in vergelijking met de huidige stand van zaken waar state bij front end applicaties vaak niet betrouwbaar is omdat bijvoorbeeld een bepaald model een view kan updaten die op zijn beurt een ander model updatet. Dit kan zeer verwarrend zijn, een probleem dat Redux probeert te verhelpen, door alle state op één plaats te hebben waarop gecontroleerd aanpassingen gemaakt kunnen worden. (Redux, 2016) Vervolgens zijn deze actions ook simpele JavaScript objecten, waardoor deze kunnen opgeslagen worden om later eventueel opnieuw uit te voeren, bij het debuggen of dergelijke. (Redux, 2016) Een van de fundamentele concepten uit het functioneel programmeren die in deze scriptie nog niet besproken is met betrekking tot JavaScript is immutability. Immutability was één van de redenen gegeven waarom programma’s geschreven in een pure functionele programmeertaal stabieler zijn, waarom het eenvoudiger is om over de werking van de code te redeneren (bijvoorbeeld niet nodig om mentaal doorheen een programma te stappen — gelijkaardig aan een debugger — om over de huidige state van een object te redeneren) en waarom er minder potentiële bugs zijn in een dergelijk programma met grotendeels onveranderlijke datastructuren. Verder zorgt immutability er ook grotendeels voor dat het programmeren met meerdere threads — concurrent programming — veel eenvoudiger wordt vergeleken met concurrency bij bijvoorbeeld Java, waar waardes vanop verschillende threads gemuteerd kunnen worden. Dit voordeel echter kan niet toegepast worden in het JavaScript ecosysteem omdat JavaScript single-threaded is, maar het houdt de developer niet tegen om immutability te gebruiken voor enkele andere voordelen. Nu zijn er standaard geen onveranderlijke datastructuren in JavaScript, zelfs niet const (const is geen onveranderlijke waarde, maar eerder een onveranderlijke referentie naar een waarde), maar er zijn wel enkele libraries die onveranderlijke datastructuren ter beschikking stellen. Een van deze libraries is ImmutableJS, grotendeels geschreven door Lee Byron. Het heeft datastructuren zoals Map, List, Set, Stack, etc. en heeft van nature lazy evaluation (Seq). (Facebook, 2016) var map1 = Immutable.Map({a:1, b:2, c:3}); var map2 = map1.set('b', 50); map1.get('b'); // 2 map2.get('b'); // 50 Een library zoals ImmutableJS is vooral handig bij recente libraries zoals React of dergelijke die de developer toelaten om te kiezen om een component maar opnieuw renderen wanneer deze nieuwe props of state krijgt. Indien dit geïmplementeerd zou zijn met standaard JavaScript objecten zou een ‘deep comparison’ nodig zijn, waarbij heel de structuur van een object overlopen moet worden. Aangezien een component zeer vaak zal kijken of het moet 31 opnieuw renderen is dit niet performant. Wanneer er onveranderlijke datastructuren gebruikt worden uit bijvoorbeeld ImmutableJS is dergelijke ‘deep comparison’ niet nodig. Er kan simpelweg gebruik gemaakt worden van een ===. Als de reference van de twee onveranderlijke datastructuren verschillen kunnen we zeker zijn dat er iets veranderd is en omgekeerd. Dit is een stuk performanter en kan in sommige gevallen zorgen voor een drastisch kleiner aantal keren dat components opnieuw gerendered moeten worden. Jammer genoeg is de API van ImmutableJS niet hetzelfde als deze van standaard JavaScript datastructuren en dus kan het in een grotere codebase soms onduidelijk zijn of een bepaald argument ergens een ImmutableJS datastructuur is of eerder een standaard JavaScript datastructuur is. (Longster, 2015) var neemEigenschap = function(map, eigenschap) { return map.get(eigenschap); } neemEigenschap({name: 'Tom'}, 'name'); // Error: map.get is not a function Zoals al eerder aangehaald is in het begin van hoofdstuk 3 is JavaScript op zich geen volwaardige functionele programmeertaal zoals Haskell of Clojure er zijn. Het mist een aantal fundamentele concepten die in vrijwel alle functionele talen aanwezig zijn, zowel met JavaScript als met externe libraries. Dit zijn features als ‘pattern matching’, algebraïsche datatypes, etc. Verder is JavaScript ook niet geschreven als een functionele programmeertaal, maar is het eerder een taal die meerdere programmeerparadigma’s ondersteunt, zoals functioneel en imperatief. Dit soort talen zijn ook wel gekend als multiparadigmaprogrammeertalen. (Wikipedia , 2016) Er zijn nog enkele concepten uit het functioneel programmeren die in deze bachelorproef niet aan bod zijn gekomen, mede door een tekort aan woorden, maar grotendeels omdat ze buiten de complexiteit van deze bachelorproef vallen. Concepten zoals ‘Monads’, ‘Functors’, etc. Net omwille van het feit dat JavaScript een multi-paradigmaprogrammeertaal is, is het niet onmiddellijk aan te raden om volledig functioneel te programmeren in JavaScript, vooral als de developer nog geen eerdere ervaring heeft met functioneel programmeren. Dit gaat samen met de vele mogelijke side-effects die van nature aanwezig zijn bij web development, zoals DOM operaties, AJAX calls, etc. Maar niks weerhoudt een developer om – waar toepasselijk – concepten uit het functioneel programmeren toe te passen, concepten die in de loop van deze bachelorproef toegelicht zijn. Er werd bij elk concept duidelijk gemaakt wat de voordelen ervan zijn, voordelen die allemaal een gemeenschappelijk doel hebben: kortere, declaratievere, stabielere en meer voorspelbare code schrijven. 32 Het gehele idee van code schrijven in een functionele programmeerstijl in JavaScript kan vrij kort samengevat worden. Ten eerste kan er geprobeerd worden om zoveel mogelijk pure functies te schrijven, maar waar nodig – bijvoorbeeld AJAX calls – kan er natuurlijk een functie geschreven worden die side-effects heeft. Tegelijkertijd is het aangeraden om zo weinig mogelijk te vertrouwen op ongecontroleerde globale state – in tegenstelling tot Redux - maar opnieuw, wanneer absoluut nodig moet de developer niet terughoudend zijn om deze te muteren. Meer pure functies en minder globale state zorgt voor een duidelijker overzicht van de data flow doorheen een programma en heeft als extra gevolg dat er meer en simpelere unit testen geschreven kunnen worden. En ten tweede is het de bedoeling om met – al dan niet zelfgeschreven – abstracties op een zo high-level mogelijke manier programma’s proberen te schrijven, waar de nadruk van de code ligt op wat de probleemstelling van het programma is, en niet op hoe het probleem stap voor stap opgelost wordt. Bovenstaande zijn dan ook maar richtlijnen en geen verplichtingen. Indien voor een bepaald probleem een OOP-stijl het beste past, heeft het natuurlijk geen nut om toch zichzelf te forceren om in een functionele stijl te schreven. Dit benadrukt vervolgens ook de veelzijdigheid van JavaScript. Afhankelijk van de situatie kan in een verschillende programmeerstijl geschreven worden, een programmeerstijl die zich er het best toe leent om een probleem op te lossen. Hier kan opnieuw ook hetzelfde argument aangehaald worden als in het eerste hoofdstuk om te verklaren waarom je als developer juist niet in een functionele stijl zou programmeren, namelijk het feit dat deze stijl vaak ietwat minder performant is dan imperatief JavaScript. Dit kan belangrijk zijn bij web applicaties die als grootste prioriteit performantie hebben (bijvoorbeeld online games). 33 In conclusie, de recente populariteit van concepten uit het functioneel programmeren bij web development is meer dan een recente gril omdat het wel degelijk het leven van een developer eenvoudiger maakt wanneer deze aan een moderne web applicaties werkt die almaar complexer worden. De instapdrempel tot het gebruiken van simpele functionele concepten is vrij laag bij JavaScript vergeleken met volwaardige functionele programmeertalen, en geleidelijk aan kan de developer extra concepten beginnen te gebruiken naarmate zijn kennis omtrent deze functionele concepten toeneemt. Vervolgens wordt deze transitie naar een meer functionele stijl van programmeren niet alleen bekrachtigd door alom bekende libraries zoals React, maar – belangrijker nog – ook door Ecma – de onderhouders van ECMAScript – zelf. Het is aangeraden om waar toepasselijk een functionele stijl te gebruiken. 34 Allen, C., & Moronuki, J. (2015). Haskell Programming from first principles. Delft University of Technology. (2014, September 4). Programming in Haskell chapter 11 Lazy Evaluation. Opgehaald van http://ocw.tudelft.nl/courses/computerscience/introduction-to-functionalprogramming/lazy-evaluation/ Duchene, A. (2014, April 4). What are some limitations/disadvantages of functional programming? Opgehaald van Quora: https://www.quora.com/What-are-somelimitations-disadvantages-of-functional-programming/answer/Alexander-Duchene Eidhof, C. (2008, November 12). Vraag over lazy evaluation. Opgehaald van Stackoverflow: http://stackoverflow.com/a/284180/1243641 Erlang. (2016, Februari 25). Info over Higher Order Functions bij Erlang. Opgehaald van Website van Higher Order Functions bij Erlang: http://erlang.org/documentation/doc-4.8.2/doc/extensions/funs.html Facebook. (2016, Maart 21). ImmutableJS. Opgehaald van ImmutableJS: https://facebook.github.io/immutable-js/%5D Flux. (2016, Maart 22). Flux documentation overview. Opgehaald van Flux: https://facebook.github.io/flux/docs/overview.html Ford, N. (2013, Januari 29). Functional thinking: Why functional programming is on the rise. Opgehaald van IBM developerWorks: http://www.ibm.com/developerworks/library/j-ft20/ Haskell Wiki. (2016, Maart 8). Why Haskell Matters. Opgehaald van Haskell Wiki: https://wiki.haskell.org/Why_Haskell_matters Haverbeke, M. (2011, Februari 6). Chapter 6 Functional Programming. Opgehaald van Eloquent JavaScript: http://eloquentjavascript.net/1st_edition/chapter6.html Hickey, R. (2011, October 13). Simple Made Easy. Opgehaald van InfoQ: http://www.infoq.com/presentations/Simple-Made-Easy Hudak, P. (1989). Conception, Evolution, and Application of Functional Programming Languages. ACM Computing Surveys, 361-391. Hughes, J. (1984). Why Functional Programming Matters. 22. Opgehaald van Chalmers: http://www.cse.chalmers.se/~rjmh/Papers/whyfp.pdf Jelvis, T. (2014, April 1). What are some limitations/disadvantages of functional programming? Opgehaald van Quora: https://www.quora.com/What-are-some-limitationsdisadvantages-of-functional-programming/answer/Tikhon-Jelvis Johansson, M. P. (2015, Augustus 24). Recursion - Part 7 of Functional Programming in JavaScript. Opgehaald van YouTube: https://www.youtube.com/watch?v=k7-N8R0KY4 Johansson, M. P. (2015, Augustus 24). Recursion - Part 7 of Functional Programming in JavaScript. Opgehaald van YouTube: https://www.youtube.com/watch?v=k7-N8R0KY4 JSPerf. (2016, Maart 15). for vs forEach. Opgehaald van JSPerf: https://jsperf.com/for-vsforeach/494 LazyJS. (2016, Maart 21). LazyJS. Opgehaald van LazyJS: http://danieltao.com/lazy.js/ Lipovača, M. (2016, Februari 25). Info over Learn You A Haskell. Opgehaald van Website van Learn You A Haskell: http://learnyouahaskell.com/higher-order-functions 35 Lodash. (2016, Maart 20). FP Guide. Opgehaald van GitHub Lodash Wiki: https://github.com/lodash/lodash/wiki/FP-Guide#mapping Longster, J. (2015, Oktober 1). Using Immutable Data Structures in JavaScript. Opgehaald van jlongster: http://jlongster.com/Using-Immutable-Data-Structures-in-JavaScript Lonsdorf, B. (2013, Mei 21). Hey Underscore, You're Doing It Wrong! Opgehaald van YouTube: https://www.youtube.com/watch?v=m3svKOdZijA&app=desktop Lonsdorf, B. (2016, Maart 20). Professor Frisby's Mostly Adequate Guide To Functional Programming. Opgehaald van GitBook: https://drboolean.gitbooks.io/mostlyadequate-guide/content/ch5.html Mozilla. (2016, Maart 03). Array.prototype.reduce(). Opgehaald van Mozilla Development Network: https://developer.mozilla.org/enUS/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce React. (2015, September 10). React Blog. Opgehaald van React: https://facebook.github.io/react/blog/2015/09/10/react-v0.14-rc1.html#statelessfunction-components React. (2016, Maart 24). Advanced Performance. Opgehaald van React: https://facebook.github.io/react/docs/advanced-performance.html React. (2016, Maart 22). React: Getting started. Opgehaald van React: https://facebook.github.io/react/docs/getting-started.html Redux. (2016, Maart 22). Redux. Opgehaald van Redux: http://redux.js.org/ Redux. (2016, Maart 22). Redux motivation. Opgehaald van Redux: http://redux.js.org/docs/introduction/Motivation.html Redux. (2016, Maart 22). Redux: Three Principles. Opgehaald van Redux: http://redux.js.org/docs/introduction/ThreePrinciples.html Sauyet, S. (2016, Maart 21). Favoring Curry. Opgehaald van Frumio: http://fr.umio.us/favoring-curry/ UILab. (2016, Maart 16). Array Reduce Like You (may) Have Never Seen It. Opgehaald van UILab: http://www.uilab.io/posts/array-reduce-like-you-may-have-never-seen-it University of Pennsylvania. (2014, Oktober 16). Being Lazy with Class. Opgehaald van CIS 194: Introduction to Haskell (Spring 2015): http://www.seas.upenn.edu/~cis194/lectures/06-laziness.html Wiki.Haskell. (2016, Februari 25). Info over Partial Application. Opgehaald van Website van Haskell Wiki: https://wiki.haskell.org/Partial_application Wikipedia . (2016, Maart 13). JavaScript. Opgehaald van Wikipedia: https://en.wikipedia.org/wiki/JavaScript Wikipedia. (2016, Februari 12). Info over Functioneel Programmeren. Opgehaald van Website van Wikipedia: https://en.wikipedia.org/wiki/Functional_programming Wikipedia. (2016, Februari 12). Info over Imperatief Programmeren. Opgehaald van Website van Wikipedia: https://en.wikipedia.org/wiki/Imperative_programming Wikipedia. (2016, Maart 6). Info over Lazy Evaluation. Opgehaald van Website van Wikipedia: https://en.wikipedia.org/wiki/Lazy_evaluation Wikipedia. (2016, Maart 20). Tacit Programming. Opgehaald van Wikipedia: https://en.wikipedia.org/wiki/Tacit_programming 36