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
!
-
Dit is bij het lezen van de code minder duidelijk, omdat
Select
als extension method is geïmplementeerd. Wie de method echter inspecteert - druk opF12
in Visual Studio of bekijk de documentatie - ziet dat dit inderdaad het geval is. ↩︎ -
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