O noțiune care este de neevitat în programarea funcțională este aceea de monadă. În principiu, o monadă este un functor care poate condensa mai multe aplicări ale sale într-o singură aplicare într-un mod monoidal.
Cu alte cuvinte, o monadă este un monoid în categoria endofunctorilor din care aparține (ignorăm momentan posibilitatea de a nu avea categoria de endofunctori dacă există probleme de dimensionare). Bineînțeles, noțiunea
duală este cea de comonadă, un comonoid în categoria endofunctorilor din care aparține. O comonadă face exact inversul fată de o monadă, desfășoară aplicarea sa în mai multe aplicări.
Deși comonadele nu sunt atăt de discutate cum sunt monadele, aceste sunt extrem de utile și interesante. Vom vedea în cele ce urmează cum anume monadele și comonadele ne ajută.
Fie C o categorie. O monadă este un triplet (T,η,μ) unde T:C→C este un endofunctor, η:IdC⇒T și μ:T2=T∘T⇒T
sunt două transformări naturale, numite unitatea, respectiv multiplicarea monadei, cu proprietatea că următoarele diagrame comută:
Diagramele pot fi condensate în formulele:
μ∘ηT=μ∘Tη=idT
μ∘Tμ=μ∘μT
Practic, unitatea monadei duce obiecte în aplicări ale monadei iar multiplicarea condensează aplicări multiple astfel încât indiferent cum aplicăm transformările naturale obținem aceiași singură aplicare a monadei. Aceste două
transformări naturale sunt cunoscute ca funcțiile return :: a -> T a și join :: T (T a) -> T a din Haskell dar de obicei monadele sunt definite ca operațiile return și bind :: T a -> (a -> T b) -> T b. Operația de
bind este practic join aplicat după fmap. Alt nume pentru bind este flatMap pentru că asta și face, aplică o mapare și aplatizează (flattens) tipul de date, join este doar o operație de aplatizare și se mai numeste
flatten. Utilitate monadelor constă în faptul că unitatea ne permite să înglobăm date într-un functor iar multiplicarea agregă datele din mai multe aplicări sau contexte într-un singur context ca să lucram mai ușor cu instanța
functorului.
Fie C o categorie. O comonadă este un triplet (G,ϵ,δ) unde G:C→C este un endofunctor, ϵ:G⇒IdC și G⇒:G∘G=G2
sunt două transformări naturale, numite counitatea, respectiv comultiplicarea comonadei, cu proprietatea că următoarele diagrame comută:
Diagramele pot fi condensate în formulele:
ϵG∘δ=Gϵ∘δ=idG
Gδ∘δ=δG∘δ
Counitatea și comultiplicarea acționează în sensul invers ca la unitatea și multiplicarea monadei, acestea mai sunt cunoscute în limbajele de programare ca extract :: G a -> a și duplicate :: G a -> G (G a) având și dualul
lui bind ca cobind :: (a -> G b) -> G a -> G b. Ambele asigură că indiferent cum le aplicăm obținem aceleași tipuri de date imbricate. Comonadele sunt foarte utile pentru că counitatea poate extrage obiectul din
interiorul aplicării functorului, lucru care lipsește la monade, adică la nevoie putem accesa direct datele dintr-o comonadă dacă dorim. Comultiplicarea este de asemena foarte utilă pentru că ne permite să duplicăm date și să
le refolosim în contexte noi.
Un lucru important legat de (co)monade este faptul că acestea apar mereu dintr-o pereche de functori adjuncți. Dacă avem doi functori adjuncți L⊢R, L:C→D și R:D→C
atunci există o monadă T:D→D și o comondă G:C→C.
monada T este compusul functorilor R∘L, unitatea monadei este unitatea adjuncției η iar multiplicarea este data de counitatea adjuncției μ:T∘T=R∘L∘R∘LRϵLR∘L=T
comonada G este compusul functorilor L∘R, counitatea comonadei este counitatea adjuncției η iar comultiplicarea este data de unitatea adjuncției δ:L∘R=GLηRL∘R∘L∘R=G∘G
Canonic exista două categorii prin care putem defini adjuncții care produc monade, anume categoria Kleisli a unei monade și categoria Eilenberg-Moore sau categoria algebrelor pentru o monadă.
counitatea adjuncției este dată de multiplicarea și unitatea monadei pentru că ∀f∗:xT→yT∈Hom(CT), (ηy)∗∘Tf∗=(μy∘Tηy∘f)∗=(μy∘Tf∘ηx)∗=(Tf)∗∘T(ηx)∗
comultiplicarea acestei comonade este dată de deltax=(Tηx)∗ pentru că o diagrama în C
Înainte de a introduce categoria Eilenberg-Moore trebuie definite algebrele pentru monade, acestea diferă față de algebrele pentru un endofunctori prin faptul că pe lângă definiția generală a acestora au o structură suplimentară compatibilă
cu unitatea și multiplicarea monadei.
Fie (T,η,μ) o monadă, o algebră pentru acestă monadă este o algebră (a,α) cu structura suplimentară că următoarele diagrame comută:
Fie C o categorie și cu o monadă (T,η,μ). Categoria Eilenberg-Moore a acestei monade este CT cu:
Ob(CT)={(x,νx)∣x∈Ob(C),α:Tx→x∈Hom(C)}, adică mulțimea de obiecte este mulțimea algebrelor pentru T
Hom(CT)={fT∣fT=f:x→y∈Hom(C,νy∘Tf=f∘νx),α:Tx→x∈Hom(C)} este mulțimea homomorfismelor între algebrele pentru T
Putem crea o pereche de functori adjuncți L⊢R în felul următor:
L mapează ∀x∈Ob(C) la Lx=(Tx,μx) și ∀f:x→y∈Hom(C) la Lf=(Tf)T:(Tx,μx)→(Ty,μy)
R mapează ∀(x,νx)∈Ob(CT) la R(x,νx)=x și ∀fT:(x,νx)→(y,νy) la R(fT)=f:x→y, cu alte cuvinte R este un functor de uitare (forgetful functor), uită de anumite elemente din categorie,
și pentru că este adjunctul la dreapta numim adjunctul la stânga al unui functor de uitate functor liber (free functor)
Se poate vedea ușor că acești doi functori sunt adjuncți și produc monada:
∀x∈Ob(C), (R∘L)(x)=R(Tx,μx)=Tx
∀f:x→y∈Hom(C), (R∘L)(f)=R((Tf)T)=Tf
unitatea adjuncției există deja și este unitatea monadei
counitatea adjuncției este dată de multiplicarea și unitatea monadei pentru că ∀fT:(x,νx)→(y,νy)∈Hom(CT) ține condiția νx∘μx=νx∘Tνx pentru algebra monadei și atunci
ϵ(x,νx)=νx:(Tx,μx)→(x,νx) este un homomorfism care face să comute:
Care ține pentru că indiferent de ce compunere de homomorfism aplicăm obținem următoarele diagrame comutative care sunt echivalente:
Putem verifica imediat compunerile:
νy∘Tf∘Tνx=νy∘Tνy∘TTf=f∘νx∘μx=νy∘Tf∘μx
comultiplicarea acestei comonade este dată de deltax=Tηx:(Tx,μx)→(TTx,μTx) este un homomorfism care face să comute:
Care ține pentru că indiferent de ce compunere de homomorfism aplicăm obținem următoarele diagrame comutative care sunt echivalente:
Putem verifica imediat compunerile pentru că ηy∘f=Tf∘etax: