Wat is een functor?

Wat is een functor, vraag je? Lang verhaal kort: functors zijn typen die een Map-functie implementeren.

Wat is een Map-functie, vraag je? Lang verhaal kort: ken je LINQ? - heb je wel eens Select gebruikt? - nou, dat dus.

Lijsten

Oké, misschien loont het zich er nét iets langer bij stil te staan. De Select-functie transformeert alle elementen in een lijst van het type T naar het type R. Of, in functionele notatie (zie deze blog): Select: (IEnumerable<T>, (T -> R)) -> IEnumerable<R>.

Een concreet voorbeeld:

var people = _peopleRepository.GetAll();
var ages = people.Select(p => p.Age);

De GetAll-functie op de _peopleRepository levert een IEnumerable<Person> op. De Select-functie accepteert die IEnumerable als eerste parameter.1 De tweede parameter is een Func<T, R>. In dit geval zegt die functie: geef me voor elke Person p de p.Age terug. Je stopt een IEnumerable<Person> in de Select-functie, en je krijgt een IEnmerable<Age> terug.2

Transformaties

Hoe moet je deze functionaliteit begrijpen? Je zou het zo kunnen zien: je transformeert als het ware de lijstwaarden (ook wel de gebonden variabele genoemd). Daar waar je eerst een lijst met personen had, heb je na de toepassing van de Select-functie een lijst met leeftijden.

Natuurlijk, dat klopt niet helemáál. LINQ omarmt immutability (zie deze blog), en dus krijg je eigenlijk een nieuwe lijst terug van een ander type.

Misschien is dit een betere manier om het te zeggen: je stelt, met als uitgangspunt lijst a en op basis van een conditie f, een lijst b samen. (Maar een nóg betere manier om het te zeggen zou waarschijnlijk met wat minder variabelen gepaard gaan!)

Opties

Dit idee blijft niet beperkt tot IEnumerable. Ook Options (zie deze en deze blog) hebben een soortgelijke functie. Alleen heet die functie in deze context doorgaans geen Select maar Map. Dit is de functionele notatie ervan: Map: (Option<T>, (T -> R)) -> Option<R>.

Merk op dat de signatuur van deze functie qua vorm identiek is aan die van Select. Het enige verschil is dat IEnumerable door Option is vervangen.

Je zou het zo kunnen zien: Option is, net als IEnumerable, een lijst met waarden. Maar daar waar IEnumerable honderden gebonden variabelen kan hebben, zijn er voor Options maar twee: wel of geen waarde.

Opnieuw, een concreet voorbeeld:

var person = _peopleRepository.GetById(1);
var age = person.Map(p => p.Age);

De GetById-functie op de _peopleRepository levert een Option<Person> op. De Map-functie accepteert die Option als eerste parameter. De tweede parameter is een Func<T, R>. In dit geval zegt die functie: geef me voor de Person p de p.Age terug. Er zijn twee mogelijke uitkomsten: ofwel er is een Person p, ofwel niet. Zo ja, dan krijg je p.Age terug; en zo nee, dan niet. Het resultaat van deze operatie is ofwel Option met daarin de een welbepaalde Age, ofwel een Option met als waarde None.

Functors

Natuurlijk blijft dit idee niet beperkt tot lijsten en Options alleen. Het idee van een type dat Map implementeert kan nog verder gegeneraliseerd worden tot veel meer types die als container voor een bepaalde waarde fungeren. Als we naar de functionele notatie kijken, is het enige dat ons rest om het concrete type weg te abstraheren: Map: (C<T>, (T -> R)) -> C<R>.

Map kan zo worden gedefinieerd als een functie die een container C<T> accepteert, en een functie f met als vorm (T -> R). Het retourneert een container C<R>, een wrapper voor de waarde(n) die het resultaat zijn van de toepassing van f op de gebonden variabelen van de container.

Voortgang

Die zin klinkt ontzettend krom, ik weet het, en dat komt omdat ik ’m min of meer letterlijk vertaald heb uit Enrico Buonanno’s Functional Programming in C# (Second Edition). Dat boek ploeg ik langzaam - en niet bijzonder zeker - door in een poging mijn C#-vaardigheden uit te breiden.

Het proces vordert langzaam, omdat het je eigen maken van een nieuw programmeerparadigma van je vraagt om je handen vuil te maken, zelf code te schrijven. Dat schuurt met hoe ik normaliter een boek lees - ver weg van mijn toetsenbord -, en daardoor ga ik er minder rap doorheen dan ik graag zou zien.

Maar ik maak voortgang, en dat doe ik naar goed gebruik in het openbaar. In deze GitHub-repository schrijf ik mee met Buonanno en leg mijn gedachten over waar ik mee bezig ben vast. Het is een vreemde mix van demo-code en dagboekaantekeningen, een eerste opstap naar demoprojecten en uiteindelijk productiecode.

We komen er wel, Func voor Func!


  1. Dit is bij het lezen van de code minder duidelijk, omdat Select als extension method is geïmplementeerd. Wie de method echter inspecteert - druk op F12 in Visual Studio of bekijk de documentatie - ziet dat dit inderdaad het geval is. ↩︎

  2. Of, wat in de praktijk waarschijnlijk vaker voorkomt: een IEnumerable<int>. Maar dat is een voorbeeld van primitive obsession! Zie ook deze blog↩︎

functioneel programmeren · functors · leermoment · linq · options