The Ultimate Power of Currying with Closures

 
07 augustus 2009

In mijn tweede post over het onder de aandacht brengen van functioneel programmeren gaan we een functionele techniek nader bekijken, de combinatie van currying en closures. Nooit van gehoord? Check it out now! Je kunt er leuke dingen mee doen plus zijn het aardige woorden om uw vocabulaire mee te verrijken. Ik hoop de technieken tussen de regels uit te leggen. Veel leesplezier!

F#

Tijdens mijn onderzoek naar de toepassing van F# binnen Sogyo kwam ik op een interessante language feature. Laat ik het met een voorbeeld duidelijk maken.

01. let add a b = a + b
02. val add : int -> int -> int
03.
04. add 10 20
05. val it : int = 30
06.
07. let add10 = add 10
08. val add10 : (int -> int)
09.
10. add10 20
11. val it : int = 30

Dit behoeft wellicht enige uitleg. Bij (#1) definiëren we een functie met twee argumenten a en b die we vervolgens bij elkaar optellen. Zoals u ziet zijn haakjes en komma’s niet nodig. F# geeft ons dan wat cryptische informatie terug (#2) (int returns int returns int). Apart, want je zou zeggen dat er maar 1 keer een integer wordt geretourneerd. Wanneer we de functie uitvoeren met twee parameters (#4) blijkt onze veronderstelling waar te zijn. We krijgen 1 integer terug die de som is van beide parameters (#5). Maar bij (#7) maken we een nieuwe functie add10 waarbij we maar 1 parameter meegeven, n.l. 10. F# geeft ons nu een functie terug die half is ingevuld. Deze nieuwe functie (int returns int) (#8) accepteert 1 parameter, telt daar 10 bij op en retourneert een integer als resultaat. Bij (#10) testen we dit uit met de parameter 20 en jawel, we krijgen netjes 30 terug (#11).


Currying is een functie die meerdere argumenten accepteert en wordt omgezet naar een functie die het eerste argument afhandelt en vervolgens een functie retourneert die het tweede argument afhandelt, enz. enz.. Vernoemt naar Haskell Curry.

Om het wat meer duidelijker te maken zouden we onze functie add ook zo kunnen schrijven (met een lambda functie):

01. let add = (fun a b -> a + b)
02. val add : int -> int -> int

Zoals te zien is geeft F# dezelfde signature terug als in het vorige voorbeeld. Het feit dat dit in F# niet expliciet vermeld hoeft te worden maakt de taal zo ontzettend bondig, krachtig, overzichtelijk, masterlijk en alles wat u maar onder het Engelse woord succinct verstaat.

Dus…

Leuk, interessant, boeiend, maar wat kunnen we er nu mee? F# is nog niet echt productierijp en stel dat u helemaal niets met .NET doet of zelfs heeft. Wat kan deze informatie u dan nog bieden. Wel, we gaan hiermee direct aan de slag en ik laat u zien dat ( let add a b = a + b ) u kan helpen bij het programmeren van real world applicaties. Om het echt real world te maken gaan we dit niet gebruiken in C#, of Java maar in de meest onderschatte taal in webontwikkeling. De taal die oorspronkelijk werd geboren onder de naam LiveScript, verder werd ontwikkeld als JavaScript en JScript (Microsoft) maar uiteindelijk gestandaardiseerd werd tot ECMAScript<versie> waar de rest dan ook compatible mee zouden moeten zijn. We zullen het voor het gemak maar gewoon JavaScript noemen. Nu we een leuke taal hebben willen we ook een leuke applicatie. Dat zou heel goed een sorteerfunctie kunnen zijn maar dat wordt zo langzamerhand een beetje saai. In mijn vorige blog hadden we ook al zoiets (filteren) en er zijn genoeg tutorials op het web te vinden die hier over gaan. We willen wel een beetje origineel blijven natuurlijk. Daarom maken we een webapplicatie die een gegeven tekst met zoveel mogelijk visuele effecten over het scherm laat stuiteren. Om aan te tonen dat het wel degelijk een zinnige applicatie is zou het bijvoorbeeld gebruikt kunnen worden op introductie dagen van informaticaopleidingen met een bordje erboven: ‘Dit kun jij over twee jaar ook’. Of misschien is het iets voor een inspiratieloze kunstenaar?

JavaScript

In JavaScript werkt ( let add a b = a + b ) ook, alleen moeten we expliciet aangeven dat we een functie terug willen geven.

01. function add(a) {
02.   return function(b) {return a + b;}
03. }
04.
05. add(10)(20)
06. 30
07.
08. var add10 = add(10);
09.
10. add10(20);
11. 30

Onze applicatie

Laten we eens kijken hoe we onze applicatie, die een gegeven tekst zo creatief mogelijk op het scherm laat dansen, gaan vormgeven. We kunnen met een combinatie van de coördinaten van de muis en de stijl eigenschappen van een tekstblok al genoeg bereiken. Wanneer we deze functie dan registreren bij de onmousemove eventhandler van het document zijn we al bijna waar we wezen moeten. We gaan er vanuit dat de array: target alle woorden van de ingevoerde tekst bevat die als onafhankelijke tekstnodes aan het document zijn toegevoegd.

01. document.onmousemove = handle;
02.
03. // Each mousemove calls this function
04. function handle(e) {
05.  for (var i = 0; i < target.length; i ++) {
06.   var ele = target[i];
07.   var x = e.pageX;
08.   var y = e.pageY;
09.
10.   // change style with x and y
11.   ele.style.fontSize = x + ‘pt’;
12.
13.   // do some more tricks
14.  }
15. }

Het ziet er al leuk uit maar de effecten zijn een beetje saai. We willen daarom per woord een visueel blokje (div element) toevoegen dat meebeweegt op het scherm. Alleen hebben we nu een probleem. De font-size attribuut werkt wel voor tekst maar zal bij een leeg div element geen effect opleveren. Daar moeten we veeleer de hoogte en breedte aanpassen wat op een tekstblok weer geen zichtbare effecten oplevert. Zo moeten we bij een div element de background-color aanpassen en voor een tekst element het color attribuut. We hebben dus tekstspecifieke en blok (= div) specifieke effecten. Maar ook effecten die op allebei van toepassing zijn zoals de hoogte van het element via het top attribuut. We kunnen onze functie hierop aanpassen:

01. document.onmousemove = handle;
02.
03. // Each mousemove calls this function
04. function handle(e) {
05.  for (var i = 0; i < target.length; i ++) {
06.   var ele = target[i];
07.   var x = e.pageX;
08.   var y = e.pageY;
09.
10.   // change style with x and y
11.   if (ele.className == ‘movingBlockElement’ && ele.className == ‘movingTextElement’) {
12.
13.    // block and text effects omitted
14.   } else if (ele.className == ‘movingTextElement’) {
15.
16.    // text effects omitted
17.   } else {
18.
19.    // block effects omitted
20.   }
21.  }
22. }

De code wordt al een heerlijk gedrocht en we zijn de zichtbare grens van effectief programmeren al lang verstreken. Voor wie deze mening niet deelt, er zit herhaling in de if-clausule (2x getest op tekstelementen) en de effecten zijn her en der verspreid plus komen dubbel voor. En bij elke aanpassing zal de handle functie even hard meegroeien en complexer worden. Nu wordt het nog erger want we vinden het nog steeds saai. We willen de visuele effecten dynamisch wijzigen wanneer de gebruiker met zijn muis klikt. We willen dus de effecten willekeurig aan de tekst- en blokelementen toewijzen. Hoe gaan we dat doen op deze imperatieve manier? We kunnen de array target steeds willekeurig sorteren maar dan moeten we listige algoritmes gaan verzinnen die genoeg variatie bieden. We kunnen ook gewoon stoppen met deze aanpak en het functioneel benaderen.


We moeten doen wat we eigenlijk zouden willen. Als we nu de visuele effecten apart definiëren:

01. // special effects
02. var effects = [];
03. effects[0] = function (ele, y) {ele.style.top = y + ‘px’;} // vertical move
04. effects[1] = function (ele, x) {ele.style.left = x + ‘px’;} // horizontal move
05. effects[2] = function (ele, f) {ele.style.fontSize = (f / 2) + ‘pt’;} // set fontsize
06. effects[3] = function (ele, w) {ele.style.width = w + ‘px’;} // set width

Daarna aangeven of de effecten nu werken voor tekst- dan wel blokelementen:

01. // object specific indexes
02. var textEffects = [0, 1, 2];
03. var blockEffects = [0, 1, 3];

Met een functie die dit regelt:

01. // get object specific index
02. function getObjectSpecificIndex(ele) {
03.  return (ele.className == ‘movingBlockElement’) ? blockEffects : textEffects;
04. }

En we een functie definiëren die een functie retourneert die vervolgens twee effecten toepast op het meegegeven element (ele) met als parameter de x of y coördinaat van de muis:

01. // set style function
02. function setStyle(ele, event) {
03.  var x = event.pageX;
04.  var y = event.pageY;
05.  return function(f1, f2) {f1(ele, x); f2(ele, y);};
06. }

Een closure is een functie die valt binnen de scope van de omgeving waarin deze is gedeclareerd. Daardoor heeft onze nieuwe functie toegang tot de variabelen: ele, x en y.

Dan kan onze handle functie een stuk korter:

01. // Each mousemove calls this function
02. function handle(event) {
03.  for (var i = 0; i < target.length; i ++) {
04.   var ele = target[i];
05.   var spec = getObjectSpecificIndex(ele);
06.   var max = spec.length;
07.   var j = i + mouseclicks;
08.
09.   var setFunc = setStyle(ele, event);
10.   setFunc(effects[spec[j % max]], effects[spec[(j + 1) % max]]);
11.  }
12. }

Hierbij is mouseclicks een globale variabele die het aantal muisklikken bijhoudt. Zo kan de gebruiker de visuele effecten veranderen door met zijn muis te klikken. In de laatste twee coderegels wordt er per element een specifieke functie gemaakt waarna we deze aanroepen en ‘willekeurige’ effecten meegeven.


Afronding

Als we de rest van het programma aanvullen plus de fantastische cross-browser bibliotheken van Michael Foster gebruiken hebben we een mooie applicatie ontwikkeld. Hieronder is alvast een screenshot te zien.

image001

Consolidatie

Verdere uitbreidingen aan visuele effecten zullen de core (= handle functie) van onze mini-applicatie niet complexer maken. Ralf Wolter had over dit onderwerp een interessante post . De code is prima uitbreidbaar doordat we de verantwoordelijkheden netjes hebben weten te verdelen. Want denkt u maar mee.

  • Wanneer een programmeur de visuele effecten wil uitbreiden hoeft hij geen knowhow van de hele applicatie te hebben. Hij kan eenvoudig zijn effectfunctie toevoegen aan de array effects.
  • Wanneer een programmeur een nieuw element wil toevoegen, bijvoorbeeld een span element hoeft hij nog steeds geen knowhow te hebben over hoe onze handle functie nu werkt. Hij kan zijn span-effect functies toevoegen aan de array effects, een nieuwe index array aanmaken die aangeeft welke effecten er op een span kunnen worden toegepast en als laatste moet hij de getObjectSpecificIndex functie aanpassen zodat deze functie ook controleert op span-elementen en vervolgens het juiste index array terug geeft.

Dit komt omdat onze core domweg een gegeven methode toepast op een gegeven element, wat dat dan ook mag zijn.

Source code

Helaas krijg ik de code niet in de pagina binnen een textarea omdat Wordpress de helft eruit filtert. Dus, mocht u interesse hebben in de source code: mail me dan even.

Als je hier nieuw bent, wil je je misschien inschrijven voor onze RSS feed.
Bedankt voor je bezoek!

Gerelateerde bijdragen


Werken met Andries Nieuwenhuize?
Kijk dan bij onze mogelijkheden voor starters en/of ervaren IT'ers.


Categorieën: Development

Tags: ,


Plaats een reactie

Jouw emailadres wordt nooit gepubliceerd of gedeeld. Benodigde velden zijn met * gemarkeerd.