Het treurige verhaal van de Monad
1989. Je bent een universitaire onderzoeker die een patroon heeft ontdekt in een nieuwe programmeertaal, Huskshell. Het patroon gaat als volgt:
I = 0;
N = 10;
LOOP:
IF (I < N) GOTO ENDLOOP;
PRINT(I);
I = I + 1;
GOTO LOOP;
ENDLOOP:
Je tekent een diagram van dit softwarepatroon en herkent het direct als een xnopyt, een zekere lineare reïficatie van de set van xnopoïdische iteratiestructuren (een onderwerp binnen “fizzbuzz theory” (een abstract wiskundig onderzoeksveld)) die voor de duidelijkheid niet echt bestaan (ik heb ze verzonnen voor dit verhaal).
Direct is het een hit - binnen de hoogst academische niche van onderzoekers van procedurele talen zoals Huskshell dan. Voor de rest blijft het concept redelijk obscuur en de software wereld gaat zijn eigen, andere richting in. Jaren later worden xnopyts weer ontdekt, en iedereen maakt ze prompt tamelijk belachelijk.
Vanwege hun nauwe verbinding met hoog abstracte wiskunde hebben xnpyts een zeker esoterisch aspect, verwarrende bagage waardoor ontspoorde programmeurs ze vergelijken met burrito's vanwege hun rollende/herhalende kenmerk. Het ironische is dat overal in de software industrie miljoenen xnopyts worden gebruikt op een dag, maar niemand die het door heeft: de “for-loop”. Maar de syntactische limitaties (en überhaupt de ongebruikelijke syntax) van Huskshell maken het onmogelijk voor de gemiddelde software developer om deze gelijkenis te zien.
Dat is het verhaal van de Monad. Het is een software-patroon die je
terug ziet in moderne talen in optional types, error types, arrays en zo
nog wat dingen. In die talen komt het naar voren dat je een
.map()
hebt op die objecten, of een ?
operator, of iets dergelijks. Ze zijn in een zekere zin goed te
componeren. En dat is alles wat een monad is, het is gewoon een design
patroon met een hele dure naam. (zeker met die inflatie de laatste tijd, sjonge jonge)
Je kan pedantisch doen over de drie Monad Laws die Haskell definiëert, maar letterlijk genomen hebben die weinig waarde buiten Haskell omdat Haskell zo’n extreem unieke taal is. Alles is er uitgedrukt in termen van kettingen van geneste functies/closures, mede omdat records/structs niet echt de stijl zijn van Haskell, en mede omdat alle functies in Haskell maar 1 argument mogen nemen. Maar goed, ik zal het proberen.
De 3 monad laws zijn:
return a >>= h
is hetzelfde alsh a
m >>= return
is hetzelfde alsm
(m >>= g) >>= h
is hetzelfde alsm >>= (\x -> g x >>= h)
Aaaa, leestekens!!
return a
is Haskell voorwrap(a)
.
In Rust zou dat dan bijvoorbeeldSome(3)
kunnen zijn met deOptional<T>
monad, maar in Haskell hoef je niet te zeggen wat voor monad je wrapt -return
is een polymorphische functie. In de type signature staat al welke monad, dan hoeft het niet nog een keer in de implementatie herhaald te worden.a >>= b
(bind) is Haskell voora.map(b)
.\x bla
is Haskell voorfn(x){a}
.f x
isf(x)
.f
,h
,g
zijn standaard placeholder namen voor functies,m
voor monad, ena
voor een waarde enx
voor een functieargument.
Wat er dus eigenlijk staat is:
wrap(a).map(h) == h(a)
: eigenlijkwrap(h(a))
in b.v.b. Rust, maar dat hoeft in Haskell niet omdat>>=
niet alleenmap
is, maar ook meteen een unwrap. Deze wet betekent dat eerst een calculatie en daarna een wrap hetzelfde is als een map op de wrap.m.map(wrap) == m
, oftewel een monad opnieuw inpakken geeft een monad. Nogmaals steekt hoe Haskell werkt hier z’n kop boven het water. Vandaar dat je in Haskell niet een dubbele monad krijgt, terwijl je in Rust eenOptional<Optional>
zou krijgen.m.map(g).map(h) == m.map((x) -> g(x).map(h))
, oftewel een monad eerst met g en dan h mappen is hetzelfde als de monad één keer mappen met een compositie van g en h.
En dat is het gewoon. Zie het als een interface
. Niet zo
heel lastig dus. Maar:
Daarnaast zijn monaden dus redelijk abstract. Vragen wat “een” monad is, is een beetje alsof je vraagt wat “een” observer pattern is. Het is een abstract idee, een bepaalde manier om een klein geïsoleerd stukje gedrag op een gestandaardiseerde manier in code vorm te geven, en daar zijn duizenden verschillende (maar dus wel gelijkende) concrete implementaties van te vinden.
En dat alles maakt monads zo ongelooflijk ontoegankelijk. Terwijl je ze waarschijnlijk al duizenden keren hebt gebruikt zonder het door te hebben.