Skip to main content

Monade și comonade

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ă.

Monade

Fie C\mathcal{C} o categorie. O monadă este un triplet (T,η,μ)(T, \eta, \mu) unde T:CCT : \mathcal{C} \rightarrow \mathcal{C} este un endofunctor, η:IdCT\eta : Id_{\mathcal{C}} \Rightarrow T și μ:T2=TTT\mu : T^2 = T \circ T \Rightarrow 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\mu \circ \eta T = \mu \circ T \eta = id_T
  • μTμ=μμT\mu \circ T \mu = \mu \circ \mu 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.

Comonade

Fie C\mathcal{C} o categorie. O comonadă este un triplet (G,ϵ,δ)(G, \epsilon, \delta) unde G:CCG : \mathcal{C} \rightarrow \mathcal{C} este un endofunctor, ϵ:GIdC\epsilon : G \Rightarrow Id_{\mathcal{C}} și G:GG=G2G \Rightarrow : G \circ G = G^2 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\epsilon G \circ \delta = G \epsilon \circ \delta = id_G
  • Gδδ=δGδG \delta \circ \delta = \delta G \circ \delta

Counitatea și comultiplicarea acționează în sensul invers ca la unitatea și multiplicara 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 extract/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.