En programació funcional una mònada és un TAD sense tipus concrets, corresponent a una estructura algebraica d'un sol element (d'aquí el nom de mònada), on la finalitat de les operacions és modelar la composició i la seqüencialitat de les computacions (accions amb efectes) mitjançant l'encadenament, separant la composició temporal, de l'execució, així com incorporar el resultat de cada operació sobre l'entorn.[1]
La mònada modela una computació i pot ser vista com una acció amb efecte (un canvi), que produeix un resultat.
Les mònades es poden compondre, mitjançant l'encadenament, amb operacions sobre el resultat de la precedent (que poden incloure efectes col·laterals), estalviant, respecte de la programació imperativa, les variables locals emprades per aquesta per desar resultats.
(return v) representa una computació elemental que ofereix v com a resultat. return és el generador de la mònada.
(>>=), anomenada bind, és la composició de computacions, encadenant una acció amb la següent en forma de funció sobre el resultat de la precedent.
En notació Haskell:
-- generador d'una computació simple, que retorna una expressió de tipus `a` com a resultatreturn::a->efectea-- composició de computacions per encadenament amb el resultat(>>=)::efectea->(a->efecteb)->efecteb-- compon amb una altra, com a funció del resultat de la precedent
.
-- tipus de l'accióacció::efectetipusDelResultat-- exemple d'ús amb els operadors canònics:acció=return5-- acció simple que ofereix el valor com a resultatacció=acció1>>=funcAcció2-- retorna el resultat de la darrera acció-funció-- que s'aplica al resultat de la primera -- exemple amb niuament progressiu per retornar una expressió de resultats precedentsacció=acció1>>=\resultat1->(acció2>>=\resultat2->returnexpressió_de_resultats_precedents)-- el mateix amb notació monàdica (sintaxi especial) denotant la precedència temporalacció=doresultat1<-acció1-- sintaxi: resultat "<-" acció_d'efectesresultat2<-acció2...letresultatNou=expressió_de_resultats_precedentsreturnresultatNou-- acció simple que retorna el valor
La mònada (signatura) s'implementa per a un tipus (estructura) que pot ser vist com un contenidor d'un sol element que pot ser el resultat, però no sempre és el resultat tot sol. #La mònada Writer aparella com a valor el resultat i un element combinable (un Monoide). #La mònada Reader conté com a valor una funció de tipus (entorn -> resultat). #La mònada State conté com a valor una altra tipus de funció (estat -> (resultat, nouEstat)).
Té aplicació en llenguatges de programació no-estrictes, on l'ordre de les operacions és indeterminat, principalment al Haskell i també en altres àmbits, en entorns on l'ordre de les operacions no està garantit, com ara encadenament de transaccions en memòria (mònada STM a Haskell i a OCaml), encadenament d'operacions asíncrones (mònada Par a Haskell, Asynchronous Workflows a F#), etc.
Això facilita als lleng. funcionals complementar la part funcional pura, amb efectes com ara operacions d'entrada/sortida, ops. sobre l'entorn exterior, canvis d'estat, i també el preprocés i optimització de les operacions abans de la seva execució.[1]
Al llenguatge Haskell la classe de tipus Monad descriu la signatura de les operacions descrites, a banda d'altres mètodes amb motius pràctics:
classMonadefectewhere-- generadorreturn::a->efectea-- generador d'una "computació" a partir-- d'un valor de tipus ''a'' interpretable com a resultat{- encadenament (ang.: ''bind'') encadena un efecte amb una funció efecte sobre el seu resultat -}(>>=)::efectea->(a->efecteb)->efecteb-- exemple: getline >>= putStrLn -- imprimeix la línia introduïda (resultat de l'efecte precedent){- la classe Monad també incorpora els següents mètodes, per qüestions pràctiques però que no fan falta per la descripció teòrica: -}{-| (>>) modela un encadenament que ignora el resultat de la computació precedent. -}(>>)::efectea->efecteb->efectebm_x>>m_y=m_x>>=(\_->m_y)-- implementació per defecte -- el mètode `fail` per disparar error cas de fallar l'encaix a la variable del resultat-- A partir de GHC 8.8 desapareix de Monad i obliga que els encaixos siguin irrefutables-- Per a l'ús de patrons refutables als encaixos caldrà que la mònada implementi MonadFailfail::String->IOafailmissatge
La funció `fail` s'utilitza en la sintaxi específica de blocs d'encadenaments monàdics, els blocs do, per disparar una excepció en cas que falli l'encaix del patró del paràmetre de la funció d'efectes, però serà expulsada de la classe Mònada a GHC 8.8, cap a una classe específica MonadFail de Control.Monad.Fail.[3] L'ús de patrons en l'encadenament requerirà que la mònada implementi MonadFail evitant les petades per l'ús de patrons en mònades que no els suporten.[4] Fase final de la proposta implementada a GHC 8.8.[5]
En Haskell podem expressar (>>=), en termes de fmap i join, doncs la classe Monad actualment deriva de la classe Applicative que deriva al seu torn de Functor. El nom "flatMap" que (>>=) pren en altres llenguatges (Scala, Java8) ve de l'ús d'aquests conceptes:
-- ''join'' aplana el tipus.join::Monadefecte=>efecte(efectea)->efectea-- Si l'efecte implementa l'estructura Functorfmap::Functorefecte=>(a->b)->efectea->efecteb-- (>>=) :: efecte a -> (a -> efecte b) -> efecte bm_a>>=f=(join.fmapf)m_a-- d'aquí el nom de flatMap en altres llenguatges
Al llenguatge OCaml: Mònada per a les computacions IO no blocants.[6]
(* De la biblioteca Light Weight Threads. A l'OCaml els tipus s'apliquen en sentit invers (de dreta a esq.) *)moduleLWT=sigtype+'at(* The type of threads returning a result of type 'a. *)valreturn:'a->'atval(>>=):'at->('a->'bt)->'btvalfail:exn->'at...end;;
traitFilterMonadic[+A,+Repr]extendsAny{// flatMap: encadenament (equivalent del Haskell (>>=))abstractdefflatMap[B,That](f:(A)⇒GenTraversableOnce[B])(implicitbf:CanBuildFrom[Repr,B,That]):That// la transformació final en una llista per comprensió requereix que implementi Functor// l'aplicació del mètode (map f) converteix la coŀleció Repr en una del tipus de sortida Thatabstractdefmap[B,That](f:(A)⇒B)(implicitbf:CanBuildFrom[Repr,B,That]):ThatabstractdefwithFilter(p:(A)⇒Boolean):FilterMonadic[A,Repr]abstractdefforeach[U](f:(A)⇒U):Unit}
A Java8 la classe d'objectes Optional implementa operacions similars a les de Scala:[8]
ClassOptional<T>publicstatic<T>Optional<T>of(Tvalue)// equivalent del Haskell (return)public<U>Optional<U>flatMap(Function<?superT,Optional<U>>mapper)// equivalent del Haskell (>>=)public<U>Optional<U>map(Function<?superT,?extendsU>mapper)// equivalent del Haskell (fmap)Optional<T>filter(Predicate<?superT>predicate)...
Mònades segons la quantificació de resultats
Seqüenciació d'accions amb
resultat únic:
IO: entrada/sortida, excepcions I/O i canvis d'estat de variables globals IORef.
ST: encapsulament de canvis d'estat en un àmbit local (variables STRef).
resultat opcional (de funcions o bé d'accions que poden fallar):
la mònada Maybe: èxit o fallada de funcions definides parcialment.
la mònada (Either tipusError): èxit o fallada de la validació amb try d'accions que poden llançar excepcions.
Les mònades no impliquen estrictesa (avaluació del primer argument de (>>=) malgrat que el segon no en demani el resultat com a paràmetre: cas del comodí '_') sinó que depèn de la implementació.[9]
La mònada IO és d'avaluació estricta, la mònada llista [a] no,[9] i per exemple la mònada State té versions estricta[10] i d'avaluació tardana (lazy)[11]
L'element absorbent per l'esquerra fa inútil l'avaluació de les computacions subseqüents, ja que el resultat és el mateix valor, i pot indicar una situació d'error que invalidi la continuació de les computacions.
L'encadenament de computacions amb mònades que compleixin la regla de l'element absorbent en (>>=) per algun valor del tipus, evita haver de comprovar resultats d'error a cada pas nou en la seqüència.
-- de la definiciódataMaybea=Nothing|JustainstanceMonadMaybewherereturnx=Justxfail_=NothingNothing>>=_=Nothing-- element absorbent; no avalua la computació subseqüent (Justx)>>=f=fx-- f :: a -> Maybe b
Exemple:
headMaybe::[a]→MaybeaheadMaybe(x:_)=returnx-- resultat exitós, equival a (Just x)headMaybe[]=Nothing-- element absorbent de la mònada Maybedoblar::Numa=>a→Maybeadoblarx=return$2*xdoblarElCap::Numa=>[a]→MaybeadoblarElCapllista=headMaybellista>>=doblar-- equivalent amb notació de blocs ''do''doblarElCapllista=dox<-headMaybellistadoblarx-- cas de llista buida, ''doblar'' no s'avalua-- doblarElCap [] == Nothing-- doblarElCap [1,2,3] == Just 2
La mònada Either (resultats duals)
Amplia el valor Nothing de la mònada Maybe amb un segon conjunt de valors, emprat sovint per designar els errors en computacions que poden fallar disparant excepcions.
Els constructors Left i Right, a més de l'accepció d'Esquerre i Dreta, també tenen el significat de Tort i Dret, explicitant així la correcció del resultat.
dataEitherab=Lefta|RightbinstanceMonad(Eithere)wherereturnx=RightxLeftl>>=_=Leftl-- element absorbent, l'encadenament no s'avaluaRightr>>=f=fr
Exemple:
-- `try` de Control.Exception avalúa una acció que pot disparar excepcions retornant l'excepció o el resultattry::Exceptione=>IOa->IO(Eitherea)-- `evaluate` de Control.Exception avalúa una expressió funcional per fer-ne aflorar les excepcionsevaluate::a->IOa-- força l'avaluació (a WHNF)-- exemple:importData.Fixed(Centi)-- coma fixa centessimal per a valors monetarisprovaDivisió::Centi->Centi->EitherArithExceptionCentiprovaDivisióxy=try(evaluate(x/y))
Haskell modela l'entrada/sortida com una seqüència encadenada d'operacions (per forçar-ne la serialització degut a l'avaluació no-estricta), encapsulant els canvis d'estat d'accés a fitxers. Aquest model encaixa amb el concepte de mònada, donant lloc a la mònada IO.
La funció inicial main de qualsevol programa en Haskell ha de ser una expressió d'operacions d'ent./sortida i per tant del tipus de la mònada IO.
-- expressions de la ''mònada'' IO (el tipus porta com a paràmetre el tipus del resultat de les operacions)getLine-- captura una línia introduïda -- tipus ''IO String''getLine>>=putStr-- imprimeix la línia introduïda -- tipus ''IO ()''putStr"polseu Intro">>hFlushstdout>>getLine>>putStrLn"Fet">>returnTrue-- tipus ''IO Bool''(>>=)-- encadena aplicant la funció següent al resultat de la computació precedent(>>)-- encadena ignorant el resultat de la computació precedent.returnx-- op. generadora de la computació mònada amb x com a resultat-- quedant, per ex., del tipus IO X si la mònada és IO-- no pressuposa ''retorn'' d'enlloc; per ex. (return 5) :: IO Intfailmissatge-- per al cas que el paràmetre de la funció d'efectes no sigui irrefutable i falli l'encaix-- fail s = IO.failIO s-- a partir de GHC 8.0 la funció 'fail' es trasllada a una classe diferent: la MonadFail
Blocs do: notació especial de les expressions monàdiques
La clàusula do exposa l'encadenament de manera seqüencial, amb una expressió monàdica per línia, introduint l'estil imperatiu.
bloc=dox<-getLine-- resultat "<-" efecteputStrLnxputStrLn"Fet"returnx-- equival a l'expressió següent, encadenant cada línia amb (>>=) o bé (>>) -- * les corresponents al patró (resultat "<-" efecte (";"|"\n") resta_del_bloc)-- es tradueixen per (efecte >>= \ resultat -> resta_del_bloc)bloc=getLine>>=\x->(-- la resta_del_bloc té accés a l'argument de la lambda putStrLnx>>putStrLn"Fet">>returnx)-- actualment s'incorpora una opció per al cas de fallada de l'encaix, -- cridant al mètode 'fail' de la classe Monad, que -- des de GHC 8.x passa a la classe MonadFail-- de manera que caldrà que l'acció implementi MonadFail (de Control.Monad.Fail) -- per a emprar patrons no irrefutablesbloc=letsegonaAcciópatróDelResultatPrecedent=resta_del_blocsegonaAcció_=fail"encaix fallit a la posició tal"-- generat sempre a GHC <= 8.0inprimeraAcció>>=segonaAcció
el bloc do és un artifici sintàctic[15] que es tradueix a una única expressió, relligant les línies amb operacions de mònada com el que segueix:
les del tipus x <- expr_monàdica es concatenen amb les següents per encadenament (>>=) amb funció anònima de param. x i la continuació com a cos.
les altres es concatenen amb encadenament (>>) sense passar resultats
Clàusula let dins el bloc do
let a nivell de do (NO és el mateix que let..in) permet establir definicions basant-se en resultats d'efectes precedents, cosa que no és possible amb clàusules where.
"let" var1 "=" expr1 {";" var2 "=" expr2}
obté el valor de les expressions. let introdueix un subbloc, que es podrà delimitar o bé pel sagnat, o bé amb claus {} i separadors ';', i quines variables definides queden visibles a la resta del bloc do.
main=doprint"Entreu un mot: "x<-getLineprint"Entreu-ne un altre: "y<-getLine-- subbloc ''let'' de definicions basades en resultats d'efectes precedentslet{z=x++y;w=z++"."}-- equivalent amb sagnatletz=x++yw=z++"."-- cal alinear el bloc a la variable definida putStrLn("La concatenació és: "++showw)
Control imperatiu
Operacions comparables al control dels llenguatges imperatius.
El mòdul Control.Monad[16] de Haskell aporta entre d'altres les següents funcions:
-- sequence: encadena una llista d'operacions monàdiquessequence::Monadm=>[ma]->m[a]-- oferint la llista de resultatssequence_::Monadm=>[ma]->m()-- igual descartant resultats-- forever: encadena una acció monàdica amb ella mateixa de manera indefinida (bucle infinit)-- finalitzant només en cas d'excepcióforever::Monadm=>ma->mb-- replicateM: encadena una acció monàdica amb ella mateixa, un nombre finit de vegades.replicateM::Monadm=>Int->ma->m[a]-- oferint la lista de resultatsreplicateM_::Monadm=>Int->ma->m()-- descartant resultats-- iteracions amb efectes col·laterals:-- forM i mapM: encadena les aplicacions d'una "funció d'efectes col·laterals (tipus mònada en el retorn)" -- a cadascun dels elements d'una llista, preservant l'ordreforM::Monadm=>[a]->(a->mb)->m[b]-- oferint la llista de resultatsforM_::Monadm=>[a]->(a->mb)->m()-- descartant resultats-- mapM === forM amb els paràmetres canviats (oferint llista de resultats)-- mapM_ === forM_ amb els paràmetres canviats (descartant resultats)-- mapM i també ''sequence'', tenen una versió genèrica en el tipus de la col·lecció, -- definides a la classe Traversable de Data.Traversable-- exemple: encadena la impressió dels elements d'una llista per oferir-los ordenadament.forM_[1..10]$\x->printxmapM_(\x->printx)[1..10]-- equivalent de l'anterior (estil funcional)-- amb llista que conté generador i filtre:forM_[i^3|i<-[1..10],i`mod`2==0]$\x->printx-- amb un bloc ''do'' (estil imperatiu)forM_[i^3|i<-[1..10],i`mod`2==0]$\x->doputStr"següent valor: "printx-- exec. condicional when::Monadm=>Bool->m()->m()-- exec. condicional negadaunless::Monadm=>Bool->m()->m()
Per posar més d'una instrucció en una branca d'un if o d'un case cal fer-ho en un subbloc do.
{-# LANGUAGE ScopedTypeVariables #-}importControl.MonadasMonadimportControl.Exceptionmain=doprint"Farem l'eco fins que l'entrada sigui buida."catch(Monad.forever$do-- bucle infinit, cal una excepció per trencar-lox<-getLinecasexof-- alternativa []->ioError(userError"Línia buida")-- llança excepció per sortir del "forever"_->do-- subbloc do per encabir més d'una instrucció a la brancaputStrLnxputStrLn"tornem-hi")(\(excep::SomeException)->printexcep)
Recursivitat en els blocs do
Parlem de Recursivitat mútua en definicions i accions amb efectes.
Als subblocs let de definicions amb encaix
Els blocs let permeten, per l'avaluació tardana, la recursivitat mútua de les variables definides.
f::Int->[Int]->[Int]frllista=llista++[r]-- per exemple, afegeix al finallet_és_let_rec=doletllista@(x:xs)=fr[1..9]-- utilitza el valor ''r'' de la definició que segueixr=x*2-- utilitza la ''x'' de l'encaix de la definició precedentreturn(r,llista)main=do(r,ll)<-let_és_let_recputStrLn$"resultat: "++showr++"; llista: "++showll
dona:
resultat: 2; llista: [1,2,3,4,5,6,7,8,9,2]
Als subblocs rec d'efectes amb recursivitat mútua
A GHC, permeten recursivitat mútua a les accions (efectes col·laterals). Cal esmentar la pragma {-# LANGUAGE RecursiveDo #-} Vegeu ref.[21][20]
{-# LANGUAGE RecursiveDo #-}importData.IORef-- crea nodes amb referències mútuesdataNode=NodeInt(IORefNode)mk2nodes=dorecrefS<-newIORef(Node0refR)refR<-newIORef(Node1refS)returnrefSmain=dorefP<-mk2nodesputStrLn"nodes creats"NodexrefQ<-readIORefrefP-- obté els components del contingut de la ref.printxNodey_<-readIORefrefQprinty
Seqüències de computacions sense lligar resultats - Functors aplicatius
De vegades interessa només obtenir la llista de resultats d'una seqüència de computacions amb efectes col·laterals però sense encadenar resultats.
L'operació sequence esmentada prèviament fa aquesta funció.
sequence::[IOa]->IO[a]sequence[]=return[]sequence(c:cs)=dox<-c-- primera computacióxs<-sequencecs-- crida recursiva per a les següents computacionsreturn$(x:)xs-- aplicació parcial que afegeix x al capdavant dels resultats amb (:)
Aquest patró correspon a un combinador de la biblioteca Control.Monad que es diu ap (de aplicatiu)
ap::Monadm=>m(a->b)->ma->mbapm_fm_x=dof<-m_f-- computació de resultat funcióx<-m_x-- computació posteriorreturn(fx)-- combina el resultat de la segona amb la funció resultat de la primera
Elevant una funció a efecte, la podrem utilitzar com a combinador:
computació::Monadm=>mrcomputació=ap(returnf)m_x-- per a f :: a -> r
Una funció de n paràmetres ens permet combinar els resultats de n efectes
comp2::Monadm=>mrcomp2=(returnf2)`ap`m_x`ap`m_y-- per a f2 :: a -> b -> rcompN::Monadm=>mrcompN=(returnfn)`ap`m_x`ap`m_y`ap`...`ap`m_N-- per a fn :: a1 -> a2 -> ... -> aN -> r
Generalitzant la manera de combinar una seqüència de computacions, en una mònada, sense encadenar resultats:
importControl.Monad-- definits liftM, liftM2, .. liftM5 a Control.Monad com segueixliftM::(Monadm)=>(a->b)->ma->mbliftMfx1=(returnf)`ap`x1-- per a ''f'' d'un sol argumentliftM2f2x1x2=(returnf2)`ap`x1`ap`x2-- on f2 :: a1 -> a2 -> bliftMnfnx1...xn=(returnfn)`ap`x1`ap`...`ap`xn-- on fn :: a1 -> ... -> an -> b
Això dona lloc a l'abstracció Applicative més simple que la Mònada però que està en els fonaments dels llenguatges funcionals.[22]
La classe Applicative
Expressa la combinació de computacions (amb efectes col·laterals) sense l'encadenament del resultat que permet la mònada. Signatura.[23]
classFunctorefecte=>Applicativeefectewhere-- genera una dada efecte a partir d'un valor interpretable com a resultatpure::a->efectea-- combina resultats avaluant les computacions seqüencialment-- la primera computació retorna una funció que és aplicada al resultat de la segona-- vegeu més amunt la definició de ''ap''(<*>)::efecte(a->b)->efectea->efecteb-- encadena seqüencialment dos efectes, oferint com a resultat el del segon(*>)::efectea->efecteb->efecteb-- encadena seqüencialment dos efectes, oferint com a resultat el del primer(<*)::efectea->efecteb->efectea
classFunctorcontenidorwherefmap::(a->b)->contenidora->contenidorb-- les instàncies de ''functor'' han de complir-- fmap id == id-- fmap (f. g) == fmap f. fmap g
Per aplicar una funció de n arguments als resultats de n computacions en seqüència definides Applicative tenim les funcions liftAn:
importControl.Applicative-- definits liftA, liftA2, i liftA3 a Control.Applicative(<$>)=fmap-- definició per a l'ús com operador en pos. ''infix'' (entremig)liftA::(Applicativem)=>(a->b)->ma->mbliftAfx1=fmapfx1-- f té un sol argument=f<$>x1-- equivalentliftA2f2x1x2=f2<$>x1<*>x2-- f2 :: a1 -> a2 -> bliftAnfnx1...xn=fn<$>x1<*>x2<*>...<*>xn-- fn :: a1 -> ... -> an -> b
Un exemple d'ús el trobem en els formularis web en haskell anomenats Formlets.[26]
Aquí una versió de collita pròpia, com a producte de parells (entrada, resultat) per mostrar entrades i errors a validar per l'usuari:
typeTEntrada=StringdataTError=ErrValorInvàlid|ErrLlargExcessiva|...-- camps amb (entrada, resultat)dataTFormPersona=FormPersona{campNom::(TEntrada,EitherTErrorString),campEdat::(TEntrada,EitherTErrorInt),campAdrElec::(TEntrada,EitherTErrorTAdrElec)}llegirFormulariPersona::Applicativef=>fTFormPersonallegirFormulariPersona=FormPersona<$>llegeixCampNom<*>llegeixCampEdat<*>llegeixCampAdreçaElec-- o tambéllegirFormulariPersona=liftA3FormPersonallegeixCampNomllegeixCampEdatllegeixCampAdreçaElec-- on els tipusllegeixCampNom::Applicativef=>f(TEntrada,EitherTErrorString)llegeixCampEdat::Applicativef=>f(TEntrada,EitherTErrorInt)llegeixCampAdreçaElec::Applicativef=>f(TEntrada,EitherTErrorTAdrElec)
La classe Alternative (computacions alternatives)
Defineix un monoide sobre els functors aplicatius.[23]
classApplicativeefecte=>Alternativeefectewhere-- | L'element neutre de '<|>'empty::efectea-- | Una operació binària associativa(<|>)::efectea->efectea->efectea...instanceAlternativeMaybewhereempty=NothingNothing<|>p=pJustx<|>_=Justx
La interpretació de l'op. binària (<|>) no és sempre la intuïtiva. Està previst que Applicative esdevingui superclasse de Monad a GHC 7.10.[27] De cara a una eliminació de duplicitats entre Mònada i Applicative[28] es pretén que la relació d'Alternative amb Applicative sigui similar a la de MonadPlus amb Monad i que la implementació dels monoides d'Alternative i de MonadPlus donin el mateix resultat per als tipus que implementin ambdues classes. És el cas de les llistes.[29]
La classe MonadPlus (computacions amb solucions múltiples)
Defineix un Monoide sobre les computacions mònada.[30] Permet expressar computacions amb solucions múltiples (zero o més) i l'opció de fallada (no avaluació de les computacions posteriors) i obtenir-ne la combinació de solucions.
classMonadm=>MonadPlusmwheremzero::ma-- element absorbent (fallada de la mònada)mplus::ma->ma->ma-- combina solucions-- "mzero" ha de satisfer:mzero>>=f=mzerom_x>>mzero=mzero
exemple: la mònada Llista
-- de la definició a Control.Monad.InstancesinstanceMonad[]wherereturnx=[x]fail_=[]-- encadenant amb una funció (f :: a -> [b]), aplana la llista de llistes resultant concatenant amb (++)xs>>=f=foldr((++).f)[]xs-- encadenant amb una llista ys, mapeja amb (const ys), concatenant ys (length xs) vegadesxs>>ys=foldr((++).(\_->ys))[]xsinstanceMonadPlus[]wheremzero=[]mplus=(++)
Exemple d'ús:
-- computació de solucions a l'equació de segon grau sobre tres fonts d'entradaimportControl.Monadarrels::(Floatingt,Ordt)=>[]t->[]t->[]t->[]tarrelsm_am_bm_c=doa<-m_ab<-m_bc<-m_cif(a==0)-- evita div-per-zerothenmzero-- retorna [] (fail de la mònada llista)elsedoletdiscriminant=b*b-4*a*cdenom=2*asol_única=(-b)/denombiaix=sqrtdiscriminant/denomcase()of-- http://www.haskell.org/haskellwiki/Case#Using_syntactic_sugar_|discriminant<0->mzero|discriminant==0->returnsol_única|otherwise->return(sol_única+biaix)`mplus`return(sol_única-biaix)main=doputStrLn$"discriminant negatiu: "++show(arrels[1][1][1]::[Float])putStrLn$"discriminant zero: "++show(arrels[1][2][1]::[Float])putStrLn$"discriminant positiu: "++show(arrels[1][4][1]::[Float])putStrLn$"conjunt solucions: "++show(arrels[0,1][1,2,4][1]::[Float])-- inclou 0 en posició a
{-# LANGUAGE PackageImports #-}importControl.Monad(guard)import"HUnit"Test.HUnit-- del paquet HUnit -- cabal install HUnitllista_esperada=[(x,y)|x<-[1..10],y<-[1..x],x+y<10]-- traducció monàdicallista_actual::(Numa,Orda,Enuma)=>[](a,a)llista_actual=dox<-[1..10]y<-[1..x]guard(x+y<10)return(x,y)-- de la definició de ''guard'' :: MonadPlus m => Bool -> m ()-- guard False = mzero -- element absorbent, invalida l'encadenament posterior-- guard True = return () -- no fa res, permet continuarprova=TestCase$assertEqual"comprensió monàdica: "llista_esperadallista_actualmain=docomptaTests<-runTestTTprovaprintcomptaTests
L'extensió MonadComprehensions permet emprar qualsevol mònada que sigui instància de MonadPlus, en la construcció llista per comprensió. Cal GHC >= 7.2.1[32]
En el següent cas ja no és una llista per comprensió sinó una Maybe per comprensió
{-# LANGUAGE MonadComprehensions #-}val_mònada::(Numa)=>Maybeaval_mònada=[x+y|x<-Just1,y<-Just2]main=printval_mònada
dona
Just 3
Transformadors de mònades
Aplicació sobre el tipus d'una mònada que en combina l'operativa (permet l'ús dels mètodes de diverses mònades en una), encapsulant una mònada dins la mònada transformada resultant. Vegeu[33]
classMonadTranstwhere-- 'lift' eleva un efecte del tipus de la "mònada m" al tipus de la "mònada transformada (t m)"-- el tipus resultant té un nivell més que el d'origen.lift::(Monadm)=>ma->tma
Les transformacions són apilables: se'n poden aplicar diverses. Si t i t' són transformadors de mònades, llavors el tipus (t' t m a) constitueix la mònada (t' t m) transformada de la (t m)
La classe MonadIO[35] és per definir una versió optimitzada de lift de MonadTrans per elevar el tipus dels efectes des de la mònada IO quan hi ha transformacions interposades.
classMonadm=>MonadIOmwhereliftIO::IOa->ma-- eleva el tipus d'un efecte des de la mònada IO
Implementació del transformador MaybeT
El transformador MaybeT permetrà ampliar una mònada qualsevol amb la característica afegida de la mònada Maybe, d'evitar l'execució dels efectes posteriors a una fallada (op. fail).
-- el constructor MaybeT (a la dreta de la def.) incorporarà -- un sol component de tipus ''m (Maybe a)'' amb accessor ''runMaybeT''newtypeMaybeTma=MaybeT{runMaybeT::m(Maybea)}-- MaybeT :: m (Maybe a) -> MaybeT m a -- constructor-- runMaybeT :: MaybeT m a -> m (Maybe a) -- inversa del constructor instance(Monadm)=>Monad(MaybeTm)wherereturnx=MaybeT$return$Justx-- genera valor mònada exitosafail_=MaybeT$returnNothing-- assenyala falladatm_v>>=f=MaybeT(do-- el tipus del bloc do és :: m (Maybe a)-- (el del component del constructor MaybeT)maybeV<-runMaybeTtm_vcasemaybeVofNothing->returnNothing-- no avalúa l'efecte subseqüentJustv->runMaybeT$fv-- sí que l'avalua (f :: a -> MaybeT m a))
La classe MonadTrans
La classe MonadTrans aporta lift que eleva un efecte des d'una mònada m a la mònada transformada (t m). Continuant el codi anterior:
-- liftM (de Control.Monad) eleva el tipus d'una funció de valors-- a una funció d'efectesliftM::(Monadm)=>(a->b)->(ma->mb)liftMf=\m_x->dox<-m_xreturn(fx)-- ''lift'' de MonadTrans eleva un efecte de la "mònada m" a la "mònada (t m)"classMonadTranstwherelift::(Monadm)=>ma->tma-- implementació per a MaybeTinstanceMonadTransMaybeTwhere-- elevem efectes de ''m'' amb categoria d'exitosesliftm_x=(MaybeT.liftMJust)m_x
Exemple d'ús
Farem servir el transformador MaybeT que combina les computacions que poden fallar (vegeu més amunt) sobre la mònada IO resultant la mònada (MaybeT IO)
Caldrà elevar el tipus de les operacions sobre la mònada IO al de la mònada (MaybeT IO) amb lift.
runMaybeT permet "rebaixar" (contrari de lift: elevar) el tipus d'un efecte, de la mònada transformada (MaybeT m) al tipus de la mònada subjacent:
{-# LANGUAGE PackageImports #-}importNumeric(readSigned,readFloat)importText.Printf(printf)importSystem.IO(hFlush,stdout)-- les importacions següents del MaybeT oficial -- poden ser substituïdes per la implementació de l'apartat precedentimport"transformers"Control.Monad.Trans.Maybe(MaybeT(..))import"transformers"Control.Monad.Trans.Class(lift)llegirReal::String->MaybeDoublellegirRealstr=case(readSignedreadFloat)strof[(r,"")]->Justr-- només val si no hi ha altres caràcters_->NothingdeMaybe_a_MaybeTIO::Maybea->MaybeTIOadeMaybe_a_MaybeTIO=MaybeT.returnobtenirRealVàlid::MaybeTIODouble-- mònada (MaybeT IO)obtenirRealVàlid=dolift$putStr"entreu nombre real: "lift$hFlushstdoutstrLínia<-liftgetLinedeMaybe_a_MaybeTIO$llegirRealstrLíniaobtenirLArrelQuadrada::Double->MaybeTIODoubleobtenirLArrelQuadradax|x<0=dolift$putStrLn"Entrada negativa"fail""|x==0=return0|otherwise=return$sqrtx-- en cas que ''obtenirRealVàlid'' falli, ''obtenirLArrelQuadrada'' no s'avalua.main=dov<-runMaybeT$obtenirRealVàlid>>=obtenirLArrelQuadradacasevofJustv->printf"el valor és: %6.2f\n"vNothing->putStrLn"lectura fallida, o bé, valor iŀlegal"
Excepcions en una mònada - La classe MonadError
És un mecanisme de simulació d'excepcions només amb el tractament monàdic.
Implementa l'estratègia de combinar computacions que poden llançar excepcions saltant-se les computacions encadenades que segueixen la que llança l'error, fins a la sortida del gestor d'excepcions on recupera l'encadenament habitual.[36]
classMonadm=>MonadErrorexcepm|m->excepwherethrowError::excep->ma-- llança l'excepciócatchError::ma->(excep->ma)->ma-- caça l'excepció i la gestiona-- do { acció1; throwError err; acció3 } `catchError` gestorDExcepcions-- el bloc i el gestor d'excepcions han de donar resultat del mateix tipus-- segons es desprèn de la definició -- i es mostra a la implementació de l'exemple que segueix
Exemple: la mònada (Either tipusExcepció)
El tipus (Either tipusExcepció tipusResultat) millora la info. del tipus Maybe afegint-hi informació de l'error. El constructor Right introdueix el resultat Correcte i el constructor Left l'Error. Igual que amb el tipus Maybe, el podem fer servir de tipus de computació com a mònada ((Either tipusExcepció) tipusResultat).
dataEithertipusExcepciótipusResultat=LefttipusExcepció|RighttipusResultat-- prenem errors de tipus String per fer-ho senzillinstanceMonad(EitherString)wherereturnx=Rightxfailerr=Lefterr(Lefterr)>>=_=Lefterr-- element absorbent en (>>=), no avalua la computació subseqüent(Rightx)>>=f=fxinstanceMonadErrorString(EitherString)wherethrowErrorerr=Lefterr-- assenyala excepciócatchError(Lefterr)gestorDExcepcions=gestorDExcepcionserr-- gestiona l'excepciócatchError(Rightx)_=Rightx-- no hi ha hagut excepció
Més mònades rellevants en Haskell
La mònada Writer
Facilita la generació de traces i enregistraments (ang:logs) sense barrejar-los amb els resultats.[37]
El domini de la mònada és un parell amb el resultat i l'enregistrament. Aquest darrer ha d'implementar un Monoide.
newtypeWriterloga=Writer{runWriter::(a,log)}-- ''log'' és l'enregistramentinstance(Monoidlog)=>Monad(Writerlog)wherereturnx=Writer(x,mempty)-- genera una dada efecte amb el ''log'' buit(Writer(x,log))>>=f=let(x',log')=runWriter$fx-- l'aplicació de l'efecte funció (segon operand) genera un log'inWriter(x',log`mappend`log')-- que s'afegeix al log preexistent--class(Monoidlog,Monadm)=>MonadWriterlogm|m->logwherepass::m(a,log->log)->malisten::ma->m(a,log)tell::log->m()instance(Monoidlog)=>MonadWriterlog(Writerlog)wherepass(Writer((x,f),logval))=Writer(x,flogval)-- sobre una computació de resultat parell (valor, f: log -> log)-- aplica la funció sobre el log i retorna el valor com a resultat listen(Writer(x,logval))=Writer((x,logval),logval)-- ofereix el parell com a resultattells=Writer((),s)-- estableix ''s'' al ''log''-- censor: aporta la funció per aplicar al ''log'' mitjançant ''pass''censor::(MonadWriterlogm)=>(log->log)->ma->macensorfm_x=pass$dox<-m_x;return(x,f)-- listens: obté el parell retornat per listen i hi aplica un selector al segon elementlistens::(MonadWriterlogm)=>(log->b)->ma->m(a,b)listensselm_x=listenm_x>>=(return.fmapsel)
pass
passa una funció per aplicar sobre l'enregistrament, aparellant-la amb el resultat
listen
obté el parell (resultat, enregistrament)
tell
estableix l'enregistrament al valor especificat
censor
aporta la funció per aplicar a l'enregistrament i l'aplica amb pass
listens
obté el parell retornat per listen i el retorna aplicant un selector a l'enregistrament
Exemple d'ús sense transformador. la biblioteca de GHC a Control.Monad.Writer no inclou el tipus, i cal definir-lo, instanciant les classes requerides:
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}importData.MonoidimportqualifiedControl.Monad.WriterasWnewtypeWriterloga=Writer{runWriter::(a,log)}-- instanciem la classe Functor, base per a la classe ApplicativeinstanceFunctor(Writerlog)wherefmapf(Writer(x,w))=Writer(fx,w)-- instanciem la classe Applicative, base per a la Monadinstance(Monoidlog)=>Applicative(Writerlog)wherepurex=Writer(x,mempty)Writer(f,w)<*>Writer(x,w')=Writer(fx,w`mappend`w')-- finalment instanciem Monadinstance(Monoidlog)=>Monad(Writerlog)whereWriter(x,w)>>=f=let(x',w')=runWriter$fxinWriter(x',w`mappend`w')-- i també MonadWriterinstance(Monoidlog)=>W.MonadWriterlog(Writerlog)wherepass(Writer((x,f),w))=Writer(x,fw)listen(Writer(x,w))=Writer((x,w),w)tells=Writer((),s)exemple::WriterStringIntexemple=W.tell"a">>W.tell"b">>return1main=print$runWriterexemple
dona el resultat
$runhaskellprova.hs
(1,"ab")
La mònada State
Facilita l'enfilada d'un estat a través de computacions que el modifiquen, encapsulant-ne els canvis en codi funcional pur.[38][39][40]
Els valors de la mònada State són funcions de transició entre estats del tipus (estat -> (resultat, nouEstat)). Per avaluar-lo caldrà aplicar-lo sobre un estat inicial, obtenint un parell amb el resultat en primera posició: (runState computacióMonadState) estatInicial
newtypeStatesa=State{runState::(s->(a,s))}instanceMonad(States)wherereturnx=State$\s->(x,s)-- estableix el resultat(Statetf)>>=f=State$\s->let(x,s')=tfs-- obté el (resultat, nouEstat) del primer efecte inrunState(fx)s'-- aplica (runState (f resultat)) sobre el nouEstat del primer efecte---classMonadStatems|m->swhereget::msput::s->m()instanceMonadState(States)swhereget=State$\s->(s,s)-- obté l'estatputs=State$\_->((),s)-- estableix l'estat
La mònada Reader
Facilita l'arrossegament d'un entorn d'associacions variable, per exemple la substitució de textos definits en plantilles, que poden augmentar o ésser tapades per noves definicions.[41]
El valor de la mònada Reader és una funció sobre l'entorn: (entorn -> resultat). Per avaluar-lo caldrà aplicar-lo sobre un entorn inicial: (runReader computacióMonadReader) entornInicial
newtypeReaderenva=Reader{runReader::(env->a)}-- env per environment instanceMonad(Readerenv)wherereturnx=Reader$\_->x-- genera computació amb resultat fix(Readerr)>>=f=Reader$\env->runReader(f(renv))env-- r :: (env -> a)--classMonadReaderenvm|m->envwhereask::menvlocal::(env->env)->ma->mainstanceMonadReader(Readerenv)whereask=Readeridlocalfcomputació=Reader$\env->runReadercomputació(fenv)asks::(MonadReaderenvm)=>(env->a)->maaskssel=ask>>=return.sel
ask
obté l'entorn
local
avalua un efecte havent modificat l'entorn amb una funció (env -> env)
asks
obté el resultat d'aplicar un selector (env -> a) sobre l'entorn.
STM (Software Transactional Memory) permet l'actualització consistent de variables en diferents fils d'execució mitjançant transaccions en memòria que admeten tot o res d'una seqüència de lectures i modificacions de les variables transaccionals.
A la biblioteca async,[42] facilita l'execució simultània d'operacions I/O no blocants on el tipus Concurrently implementa un functor aplicatiu que permet combinar els resultats d'accions engegades asíncronament, esperant-ne la finalització conjunta, i també implementa Alternative per obtenir el resultat de la primera que acaba, anul·lant els fils d'execució de la resta d'operacions engegades.
la biblioteca LWT (light-weight cooperative threads) modela la seqüenciació de computacions d'entrada/sortida no-blocants (asíncrones) en una mònada.[6]
la biblioteca coThreads[43] proporciona la mònada STM a OCaml
Si en comptes de llistes hi subministrem dades de tipus Option[A] el resultat és també del tipus de la mònada Option (equivalent de la Maybe en Haskell).
Vegeu presentació "Introduction to Monads in Scala".[51]
scala>valopt3=Some(3)scala>valopt4=Some(4)scala>for{x<-opt3;y<-opt4}yield(x+y)res4:Option[Int]=Some(7)// desplegament equivalent en termes de ''flatMap'' i ''map'' (ops. de FilterMonadic)scala>opt3.flatMap{x=>opt4.map{y=>x+y}}res5:Option[Int]=Some(7)