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:

  1. return a >>= h is hetzelfde als h a
  2. m >>= return is hetzelfde als m
  3. (m >>= g) >>= h is hetzelfde als m >>= (\x -> g x >>= h)

Aaaa, leestekens!!

Wat er dus eigenlijk staat is:

  1. wrap(a).map(h) == h(a): eigenlijk wrap(h(a)) in b.v.b. Rust, maar dat hoeft in Haskell niet omdat >>= niet alleen map 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.
  2. 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 een Optional<Optional> zou krijgen.
  3. 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:

Diagram van monad transformaties

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.

2023-07-19 in blog #Haskell #monads