Mònada (programació funcional)

De Viquipèdia
Dreceres ràpides: navegació, cerca

En programació funcional una mònada és un TAD on la finalitat de les operacions és modelar la seqüencialitat dels 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 pot ser vista com un efecte (un canvi), que produeix resultats, els quals es poden transformar, mitjançant l'encadenament, amb operacions sobre els resultats precedents (amb possibles efectes col·laterals), estalviant, respecte de la programació imperativa, les variables locals emprades per aquesta per desar resultats.

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]

El tipus de la mònada pot ser vist com un contenidor d'un únic element, que no sempre és el resultat tot sol. #La mònada Writer aparella com a valor el resultat i un element combinable (Monoide). #La mònada Reader conté com a valor una funció (entorn -> resultat). #La mònada State conté com a valor una funció (estat -> (resultat, nouEstat)).

Al Haskell:

 class Monad efecte where
    -- generador
    return :: a -> efecte a   -- genera una dada efecte 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
    (>>=)  :: efecte a -> (a -> efecte b) -> efecte b 
              
          -- exemple: getline >>= putStrLn  -- imprimeix la línia introduïda (resultat de l'efecte precedent)
    -- encadena dos efectes, ignorant el resultat del primer
    (>>)   :: efecte a -> efecte b -> efecte b  

    (>>) es pot expressar en termes de (>>=) 
    m_x >> m_y  =  m_x >>= (\_ -> m_y)    -- implementació per defecte

        -- exemple: putStr "polseu Intro" >> hFlush stdout >> getLine >> putStrLn "fet"

   -- 'fail' indica el comportament en cas que falli l'encaix (patróResultat <- efecte) en un bloc 'do'
   -- per a la mònada Maybe, (fail _) avalua a Nothing, element absorbent de (>>=) en el domini (Maybe a)
   -- per a la mònada llista, (fail _) avalua a [] (Nil), element absorbent de (>>=) en el domini [a] 
   -- per a la mònada IO, (fail = failIO) de GHC.IO, que llança una excepció 'userError' indicant la fallada i la posició
   -- per defecte crida a 'error'
   fail :: String -> efecte a    -- aquesta funció es retirarà pròximament (vegeu nota)

A partir de GHC 8.0.x està previst donar a 'fail' per obsoleta, i incloure-la en una classe específica MonadFail definida al mòdul Control.Monad.Fail.[2][3][4]

   -- La classe 'MonadFail' incorporarà el mètode 'fail' de la classe 'Monad'

   class (Monad efecte) => MonadFail efecte where            -- Des de GHC 8.0 al mòdul Control.Monad.Fail
      fail :: String -> efecte a
  • a la mònada IO s'implementa llançant una excepció (vegeu failIO).[5]
  • a la mònada ST no està definida (la implementació per defecte crida a error)

A Haskell, en demanar, de manera tardana, el resultat d'un encadenament cada lligada (>>=) o bé (>>) requereix l'avaluació prèvia de l'operació precedent en la cadena, establint l'ordre seqüencial.

A OCaml: Mònada per a les computacions IO no blocants.[6]

(* de la biblioteca Light Weight Threads *)
module LWT = sig
  type +'a t    (* The type of threads returning a result of type 'a. *)
  val return : 'a -> 'a t
  val bind : 'a t -> ('a -> 'b t) -> 'b t
  val (>>=) : 'a t -> ('a -> 'b t) -> 'b t  (* t >>= f is an alternative notation for bind t f *)
  val fail : exn -> 'a t
  ...
end ;;

Actualment la classe Mònada és derivada de Applicative que, al seu torn, ho és de Functor.

Podem expressar (>>=), que en altres llenguatges anomenen flatMap, en termes de fmap i join:

-- ''join'' aplana el tipus.
join :: Monad efecte => efecte (efecte a) -> efecte a

fmap :: Functor contenidor => (a -> b) -> contenidor a -> contenidor b

-- (>>=)  :: efecte a -> (a -> efecte b) -> efecte b
  m_a >>= f = join $ fmap f m_a                 -- implementació basada en fmap i join

-- A ghci podem comprovar la coincidència dels tipus:
Prelude Control.Monad Control.Applicative> let mx :: IO Int; mx = return 5
Prelude Control.Monad Control.Applicative> :t fmap print mx
fmap print mx :: IO (IO ())
Prelude Control.Monad Control.Applicative> :t (join $ fmap print mx)
(join $ fmap print mx) :: IO ()

A Haskell és join qui està definida en termes de (>>=) i no al revés, per motius històrics.

join     :: (Monad m) => m (m a) -> m a
join x   =  x >>= id   -- proporciona el resultat de l'efecte del primer paràmetre.

En llenguatges imperatius, la mònada evita haver d'emmagatzemar el resultat de l'efecte com un estat, reduint el nombre d'estats.

A Scala la mònada és el substrat de les llistes per comprensió:[7] Vegeu #Mònades a Scala més avall.

 
trait FilterMonadic[+A, +Repr] extends Any {
  // flatMap: encadenament, aplanant el contenidor de contenidors resultant (equivalent de (>>=))
  abstract def flatMap[B, That](f: (A)  GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That 

  // transformació final en una llista per comprensió
  abstract def map[B, That](f: (A)  B)(implicit bf: CanBuildFrom[Repr, B, That]): That 

  abstract def withFilter(p: (A)  Boolean): FilterMonadic[A, Repr] 
  abstract def foreach[U](f: (A)  U): Unit 
}

A Java8 la classe d'objectes Optional implementa operacions similars a les de Scala:[8]

Class Optional<T>
  public static <T> Optional<T> of(T value)   // genera una dada efecte partint d'un valor
  public <U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) // equivalent de (>>=)
  public <U> Optional<U> map(Function<? super T, ? extends U> mapper) // equivalent de (fmap)
  Optional<T> filter(Predicate<? super T> predicate)
  ...

La resta de l'article se centra en abstraccions monàdiques al llenguatge Haskell que és el que més en facilita.

Les lleis de la mònada[modifica | modifica el codi]

Vegeu ref.[9]

return a >>= f f a -- identitat per l'esquerra
m >>= return m -- identitat per la dreta
m >>= (\x -> f x >>= g) (m >>= f) >>= g -- associativitat

Mònades segons la quantificació de resultats[modifica | modifica el codi]

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 de variables locals STRef)
  • resultat opcional:
    • la mònada Maybe,
    • la mònada (Either tipusError)
  • resultat múltiple: en col·leccions d'elements, la mònada llista.

Mònades amb efecte fallada[modifica | modifica el codi]

Afegint la llei de l'element absorbent per l'esquerra

mzero >>= fmzero

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.

La mònada Maybe (computacions que poden fallar)[modifica | modifica el codi]

Nothing n'és l'element absorbent per l'esquerra. Optimitza la seqüència evitant l'execució de computacions subseqüents a la que falla.[10]

-- de la definició

data Maybe a = Nothing | Just a

instance Monad Maybe where
  return x = Just x
  fail _ = Nothing

  Nothing  >>= _ = Nothing       -- element absorbent; no avalua la computació subseqüent 
                                 
  (Just x) >>= f = f x           -- f :: a -> Maybe b

Exemple:

headMaybe :: [a]  Maybe a
headMaybe (x : _) = return x  -- resultat exitós, equival a (Just x)
headMaybe [] = Nothing        -- element absorbent de la mònada Maybe
 
doblar :: Num a => a  Maybe a
doblar x = return $ 2 * x
 
doblarElCap :: Num a => [a]  Maybe a
doblarElCap llista = headMaybe llista >>= doblar

-- equivalent amb notació de blocs ''do''
doblarElCap llista = do
            x <- headMaybe llista
            doblar x   -- cas de llista buida, ''doblar'' no s'avalua
 
-- doblarElCap [] == Nothing
-- doblarElCap [1,2,3] == Just 2

La mònada Either (resultats duals)[modifica | modifica el codi]

Amplia el valor Nothing de la mònada Maybe amb un segon conjunt de valors, emprat sovint per designar els errors en resultats de funcions definides parcialment.

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.

Tots els elements amb constructor Left (Left l) són elements absorbents per l'esquerra.

data  Either a b  =  Left a | Right b

instance Monad (Either e) where
    return x = Right x

    Left l >>= _ = Left l    -- element absorbent, l'encadenament no s'avalua

    Right r >>= f = f r

Composició monàdica[modifica | modifica el codi]

Composició de Kleisli.[11]

(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)

La mònada IO[modifica | modifica el codi]

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" >> hFlush stdout >> getLine >> putStrLn "Fet" >> return True       -- tipus ''IO Bool''
 
 -- >>=  -- encadena aplicant la funció següent al resultat de l'operació precedent
 -- >>   -- encadena sense passar resultats
 -- return x  -- op. generadora d'una 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 Int

 -- fail missatge   -- dispara excepció en cas d'error en encaixar patrons dins una clàusula do
                    -- fail s = IO.failIO s

 -- a partir de GHC 8.0 la funció 'fail' es trasllada a una classe diferent: la MonadFail

Vegeu el tipus de (failIO s)[5]

$ ghci
Prelude> fail "pífia"
*** Exception: user error (pífia)
Prelude> :t fail "pífia"
fail "pífia" :: Monad m => m a

Blocs do: notació especial de les expressions monàdiques[modifica | modifica el codi]

La clàusula do exposa l'encadenament de manera seqüencial, amb una expressió monàdica per línia, introduint l'estil imperatiu.

bloc = do           
    x <- getLine      -- resultat "<-" efecte
    putStrLn x
    putStrLn "Fet"
    return x

-- 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   
                  putStrLn x >> 
                  putStrLn "Fet" >> 
                  return x
                  )

-- actualment s'incorpora una opció per al cas de fallada de l'encaix, cridant al mètode 'fail' de la classe Monad

bloc = let f patró = resta_del_bloc
           f _ = fail "encaix fallit a la posició tal"   -- generat sempre a GHC <= 8.0
       in efectePrimer >>= f

-- pròximament, amb el previst trasllat del mètode 'fail' a la classe Control.Monad.Fail.MonadFail
-- el codi amb la crida a 'fail' es generarà només si existeix una instància de MonadFail per a la mònada, visible en l'àmbit
-- sense instància de MonadFail caldrà que el patró sigui irrefutable, eliminant la possibilitat actual d'error en temps d'execució (implementació per defecte de 'fail' a Monad).

el bloc do és un artifici sintàctic[12] 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[modifica | modifica el codi]

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 = do
          print "Entreu un mot: "
          x <- getLine
          print "Entreu-ne un altre: "
          y <- getLine

          -- subbloc ''let'' de definicions basades en resultats d'efectes precedents
          let { z = x ++ y; w = z ++ "." }
 
          -- equivalent amb sagnat
          let z = x ++ y        
              w = z ++ "."  -- cal alinear el bloc a la variable definida  
 
           putStrLn ("La concatenació és: " ++ show w)

Control imperatiu[modifica | modifica el codi]

Operacions comparables al control dels llenguatges imperatius.

El mòdul Control.Monad[13] de Haskell aporta entre d'altres les següents funcions:

-- sequence: encadena una llista d'operacions monàdiques

sequence :: Monad m => [m a] -> m [a]   -- oferint la llista de resultats

sequence_ :: Monad m => [m a] -> 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 :: Monad m => m a -> m b  

-- replicateM: encadena una acció monàdica amb ella mateixa, un nombre finit de vegades.

replicateM :: Monad m => Int -> m a -> m [a]   -- oferint la lista de resultats

replicateM_ :: Monad m => Int -> m a -> 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'ordre

forM :: Monad m => [a] -> (a -> m b) -> m [b]   -- oferint la llista de resultats

forM_ :: Monad m => [a] -> (a -> m b) -> 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 -> print x 

      mapM_ (\x -> print x) [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 -> print x    

      -- amb un bloc ''do'' (estil imperatiu)

      forM_ [i^3 | i <-[1..10], i `mod` 2 == 0] $ \x -> do
                                                           putStr "següent valor: "
                                                           print x    

-- exec. condicional 
when :: Monad m => Bool -> m () -> m ()

-- exec. condicional negada
unless :: Monad m => Bool -> m () -> m ()

if-then-else dins la clàusula do[modifica | modifica el codi]

-- segons el Haskell 98[14]

if e1 then e2 else e3   ===   case e1 of { True -> e2; False -> e3 } 

-- segons el Haskell 2010[15][16] varia la sintaxi com a

if exp1 [;] then exp2 [;] else exp3       -- separable en línies pels ';'

acceptant el següent format:

f_exemple :: Monad m => Bool -> m Int
f_exemple cond = do
    if cond 
         then return 1
         else return 2

Subblocs do[modifica | modifica el codi]

Per posar més d'una línia en una branca d'un if o d'un case cal fer-ho en un subbloc do.

{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad as Monad
import Control.Exception

main = do
         print "Farem l'eco fins que l'entrada sigui buida."
         catch(
            Monad.forever $ do     -- bucle infinit, cal una excepció per trencar-lo
               x <- getLine
               case x of           -- alternativa 
                  [] -> ioError (userError "Línia buida") -- llança excepció per sortir del "forever"
                  _ -> do     -- subbloc do per encabir més d'una línia a la branca
                          putStrLn x
                          putStrLn "tornem-hi"                        
            )
            (\(excep :: SomeException) -> print excep
            )

Recursivitat en els blocs do[modifica | modifica el codi]

Als subblocs let de definicions amb encaix[modifica | modifica el codi]

Els blocs let permeten, per l'avaluació tardana, l'ús de les variables definides dins les expressions a GHC de manera mútuament recursiva.

(anglès) >> In Haskell let is really let_rec[17]

f :: Int -> [Int] -> [Int]
f r llista = llista ++ [r]  -- per exemple, afegeix al final

let_és_let_rec = do
    let llista @ (x:xs) = f r [1..9]  -- utilitza el valor ''r'' de la definició que segueix
        r = x * 2                -- utilitza la ''x'' de l'encaix de la definició precedent
    return (r, llista)

main = do
  (r, ll) <- let_és_let_rec
  putStrLn $ "resultat: " ++ show r ++ "; llista: " ++ show ll

dóna:

resultat: 2; llista: [1,2,3,4,5,6,7,8,9,2]

Als subblocs rec d'efectes recursius[modifica | modifica el codi]

A GHC, permeten recursivitat a les computacions. Cal esmentar la pragma {-# LANGUAGE RecursiveDo #-} Vegeu ref.[18][17]

{-# LANGUAGE RecursiveDo #-}
import Data.IORef

-- crea nodes amb referències mútues

data Node = Node Int (IORef Node)

mk2nodes = do
    rec p <- newIORef (Node 0 r)
        r <- newIORef (Node 1 p)
    putStrLn "nodes creats"
    return p

main = do
  p <- mk2nodes
  Node x q <- readIORef p
  print x
  Node y _ <- readIORef q
  print y

Seqüències de computacions sense lligar resultats - Functors aplicatius[modifica | modifica el codi]

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 :: [IO a] -> IO [a]
sequence [] = return []
sequence (c : cs) = do
    x <- c              -- primera computació
    xs <- sequence cs   -- crida recursiva per a les següents computacions
    return $ (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 :: Monad m => m (a -> b) -> m a -> m b
ap m_f m_x = do
    f <- m_f    -- computació de resultat funció
    x <- m_x    -- computació posterior
    return (f x)  -- 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ó :: Monad m => m r
computació = ap (return f) m_x  -- per a f :: a -> r

Una funció de n paràmetres ens permet combinar els resultats de n efectes

comp2 :: Monad m => m r
comp2 = (return f2) `ap` m_x `ap` m_y    -- per a f2 :: a -> b -> r

compN :: Monad m => m r
compN = (return fn) `ap` m_x `ap` m_y `ap` ...  `ap` m_N   -- per a fn :: a1 -> a2 -> ... -> aN -> r

Llavors la funció sequence es pot escriure

sequence :: [IO a] -> IO [a]
sequence [] = return []
sequence (c : cs) = (return (:)) `ap` c `ap` (sequence cs)

Generalitzant la manera de combinar una seqüència de computacions, en una mònada, sense encadenar resultats:

import Control.Monad
-- definits liftM, liftM2, .. liftM5 a Control.Monad com segueix

liftM :: (Monad m) => (a -> b) -> m a -> m b
liftM f x1 = (return f) `ap` x1            -- per a ''f'' d'un sol argument

liftM2 f2 x1 x2 = (return f2) `ap` x1 `ap` x2    -- on f2 :: a1 -> a2 -> b

liftMn fn x1 ... xn = (return fn) `ap` x1 `ap` ... `ap` xn    -- on fn :: a1 -> ... -> an -> b

Això dóna lloc a l'abstracció Applicative més simple que la Mònada però que està en els fonaments dels llenguatges funcionals.[19]

La classe Applicative[modifica | modifica el codi]

Expressa la combinació de computacions (amb efectes col·laterals) sense l'encadenament del resultat que permet la mònada. Signatura.[20]

class Functor efecte => Applicative efecte where
        -- genera una dada efecte a partir d'un valor interpretable com a resultat
        pure :: a -> efecte a

        -- 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) -> efecte a -> efecte b

        -- encadena seqüencialment dos efectes, oferint com a resultat el del segon
        (*>) :: efecte a -> efecte b -> efecte b

        -- encadena seqüencialment dos efectes, oferint com a resultat el del primer
        (<*) :: efecte a -> efecte b -> efecte a

La classe Functor:[21]

class Functor contenidor where
  fmap :: (a -> b) -> contenidor a -> contenidor b

-- 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:

import Control.Applicative
-- definits liftA, liftA2, i liftA3 a Control.Applicative

(<$>) = fmap   -- definició per a l'ús com operador en pos. ''infix'' (entremig)

liftA :: (Applicative m) => (a -> b) -> m a -> m b
liftA f x1 = fmap f x1            -- f té un sol argument
           = f <$> x1           -- equivalent

liftA2 f2 x1 x2 = f2 <$> x1 <*> x2    -- f2 :: a1 -> a2 -> b

liftAn fn x1 ... xn = fn <$> x1 <*> x2 <*> ... <*> xn    -- fn :: a1 -> ... -> an -> b

A la mònada tenim les següents equivalències

  pure = return
  (<*>) = ap  
  (*>) = (>>)
  f_a <* f_b = do
                 x <- f_a
                 f_b
                 return x

però GHC defineix la instanciació a partir d'un tipus derivat (amb newtype) de nom WrappedMonad.[22] per establir la relació entre els tipus.

newtype WrappedMonad m a = WrapMonad { unwrapMonad :: m a }

instance Monad m => Applicative (WrappedMonad m) where
        pure = WrapMonad. return
        WrapMonad f <*> WrapMonad v = WrapMonad (f `ap` v)

Un exemple d'ús el trobem en els formularis web en haskell anomenats Formlets.[23]

Aquí una versió de collita pròpia, com a producte de parells (entrada, resultat) per mostrar entrades i errors a validar per l'usuari:

type TEntrada = String
data TError = ErrValorInvàlid | ErrLlargExcessiva | ...
-- camps amb (entrada, resultat)
data TFormPersona = FormPersona {campNom :: (TEntrada, Either TError String)
, campEdat :: (TEntrada, Either TError Int)
, campAdrElec :: (TEntrada, Either TError TAdrElec)}

llegirFormulariPersona :: Applicative f => f TFormPersona
llegirFormulariPersona = 
    FormPersona <$> llegeixCampNom <*> llegeixCampEdat <*> llegeixCampAdreçaElec

-- o també
llegirFormulariPersona = 
    liftA3 FormPersona llegeixCampNom llegeixCampEdat llegeixCampAdreçaElec

-- on els tipus
llegeixCampNom :: Applicative f => f (TEntrada, Either TError String)
llegeixCampEdat :: Applicative f => f (TEntrada, Either TError Int)
llegeixCampAdreçaElec :: Applicative f => f (TEntrada, Either TError TAdrElec)

La classe Alternative (computacions alternatives)[modifica | modifica el codi]

Defineix un monoide sobre els functors aplicatius.[20]

class Applicative efecte => Alternative efecte where
    -- | L'element neutre de  '<|>'
    empty :: efecte a

    -- | Una operació binària associativa
    (<|>) :: efecte a -> efecte a -> efecte a

    ...

instance Alternative Maybe where
    empty = Nothing

    Nothing <|> p = p
    Just x <|> _ = Just x

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.[24] De cara a una eliminació de duplicitats entre Mònada i Applicative[25] 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.[26]

  • De la proposta Functor-Applicative-Monad:[25]
import Control.Applicative (Alternative(..))
import Control.Monad       (mzero, mplus)
 
-- MonadPlus m
 
instance Alternative m where
    (<|>) = mplus
    empty = mzero

La classe MonadPlus (computacions amb solucions múltiples)[modifica | modifica el codi]

Defineix un Monoide sobre les computacions mònada.[27] 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.

class Monad m => MonadPlus m where
  mzero :: m a                     -- element absorbent (fallada de la mònada)
  mplus :: m a -> m a -> m a       -- combina solucions

-- "mzero" ha de satisfer:
   mzero >>= f  =  mzero
   m_x >> mzero   =  mzero

exemple: la mònada Llista[modifica | modifica el codi]

-- de la definició a Control.Monad.Instances
instance Monad [] where
    return x            = [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) vegades
    xs >> ys            = foldr ((++). (\ _ -> ys)) [] xs

instance MonadPlus [] where
  mzero = []
  mplus = (++)

Exemple d'ús:

-- computació de solucions a l'equació de segon grau sobre tres fonts d'entrada
import Control.Monad

arrels :: (Floating t, Ord t) => [] t -> [] t -> [] t -> [] t
arrels m_a m_b m_c = do
  a <- m_a
  b <- m_b
  c <- m_c
  if (a == 0)      -- evita div-per-zero
    then mzero   -- retorna [] (fail de la mònada llista)
    else do
      let discriminant = b * b - 4 * a * c
          denom = 2 * a
          sol_única = (-b) / denom
          biaix = sqrt discriminant / denom

      case () of          -- http://www.haskell.org/haskellwiki/Case#Using_syntactic_sugar

        _ | discriminant < 0  -> mzero
          | discriminant == 0 -> return sol_única
          | otherwise         -> return (sol_única + biaix) `mplus` return (sol_única - biaix)

main = do
  putStrLn $ "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

dóna

discriminant negatiu: []
discriminant zero: [-1.0]
discriminant positiu: [-0.26794922,-3.732051]
conjunt solucions: [-1.0,-0.26794922,-3.732051]

Llista per comprensió monàdica[modifica | modifica el codi]

Generalitzen la notació de Llistes per comprensió. L'ús de filtres requereix que la mònada implementi la classe MonadPlus

Vegeu ref.[28]

{-# LANGUAGE PackageImports #-}
import Control.Monad (guard)
import "HUnit" Test.HUnit      -- del paquet HUnit    -- cabal install HUnit

llista_esperada = [ (x, y) | x <- [1..10], y <- [1..x], x+y < 10]

-- traducció monàdica

llista_actual :: (Num a, Ord a, Enum a) => [] (a, a)
llista_actual = do
            x <- [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 continuar

prova = TestCase $ assertEqual "comprensió monàdica: " llista_esperada llista_actual

main = do
         comptaTests <- runTestTT prova
         print comptaTests

Ús de la construcció llista per comprensió amb qualsevol instància de MonadPlus[modifica | modifica el codi]

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[29]

En el següent cas ja no és una llista per comprensió sinó una Maybe per comprensió

{-# LANGUAGE MonadComprehensions #-}

val_mònada :: (Num a) => Maybe a
val_mònada = [ x + y | x <- Just 1, y <- Just 2 ]

main = print val_mònada

dóna

Just 3

Transformadors de mònades[modifica | modifica el codi]

Permeten combinar l'operativa de dues mònades en una, encapsulant una mònada dins la mònada transformada resultant. Vegeu[30]

Implementen la classe MonadTrans[31]

class MonadTrans t where
  -- '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 :: (Monad m) => m a -> t m a

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[32] é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.

class Monad m => MonadIO m where
  liftIO :: IO a -> m a  -- eleva el tipus d'un efecte des de la mònada IO

Implementació del transformador MaybeT[modifica | modifica el codi]

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''

newtype MaybeT m a = MaybeT {runMaybeT :: m (Maybe a)} 

  -- MaybeT    :: m (Maybe a) -> MaybeT m a    -- constructor
  -- runMaybeT :: MaybeT m a -> m (Maybe a)    -- inversa del constructor 

instance (Monad m) => Monad (MaybeT m) where

  return x = MaybeT $ return $ Just x          -- genera valor mònada exitosa
  fail _ = MaybeT $ return Nothing             -- assenyala fallada

  
  tm_v >>= f = MaybeT (do                     -- el tipus del bloc do és :: m (Maybe a)
                                              --   (el del component del constructor MaybeT)
                 maybeV <- runMaybeT tm_v
                 case maybeV of
                   Nothing -> return Nothing  -- no avalúa l'efecte subseqüent
                   Just v -> runMaybeT $ f v  -- sí que l'avalua (f :: a -> MaybeT m a)
                 )

La classe MonadTrans[modifica | modifica el codi]

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'efectes

liftM :: (Monad m) => (a -> b) -> (m a -> m b)
liftM f = \m_x -> do 
                 x <- m_x
                 return (f x)

-- ''lift'' de MonadTrans eleva un efecte de la "mònada m" a la "mònada (t m)"

class MonadTrans t where
  lift :: (Monad m) => m a -> t m a    

-- implementació per a MaybeT
  
instance MonadTrans MaybeT where
  -- elevem efectes de ''m'' amb categoria d'exitoses

  lift m_x = (MaybeT. liftM Just) m_x

Exemple d'ús[modifica | modifica el codi]

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 #-}
import Numeric (readSigned, readFloat)
import Text.Printf (printf)
import System.IO (hFlush, stdout)
-- les importacions següents del MaybeT oficial 
--     poden ser substituïdes per la implementació de l'apartat precedent
import "transformers" Control.Monad.Trans.Maybe (MaybeT(..))
import "transformers" Control.Monad.Trans.Class (lift)


llegirReal :: String -> Maybe Double
llegirReal str = case (readSigned readFloat) str of
                   [(r, "")] -> Just r   -- només val si no hi ha altres caràcters
                   _ -> Nothing
                 
deMaybe_a_MaybeTIO :: Maybe a -> MaybeT IO a
deMaybe_a_MaybeTIO = MaybeT. return

obtenirRealVàlid :: MaybeT IO Double   -- mònada (MaybeT IO)
obtenirRealVàlid = do
        lift $ putStr "entreu nombre real: "
        lift $ hFlush stdout
        strLínia <- lift getLine
        deMaybe_a_MaybeTIO $ llegirReal strLínia
 
obtenirLArrelQuadrada :: Double -> MaybeT IO Double
obtenirLArrelQuadrada x
  | x < 0  = do
                lift $ putStrLn "Entrada negativa"
                fail ""
  | x == 0 = return 0
  | otherwise = return $ sqrt x
 
-- en cas que ''obtenirRealVàlid'' falli, ''obtenirLArrelQuadrada'' no s'avalua.
 
main = do
 
         v <- runMaybeT $ obtenirRealVàlid >>= obtenirLArrelQuadrada
         case v of
              Just v -> printf "el valor és: %6.2f\n" v
              Nothing -> putStrLn "lectura fallida, o bé, valor iŀlegal"

Excepcions en una mònada - La classe MonadError[modifica | modifica el codi]

É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.[33]

class Monad m => MonadError excep m | m -> excep   where
  throwError :: excep -> m a                    -- llança l'excepció
  catchError :: m a -> (excep -> m a) -> m a    -- 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ó)[modifica | modifica el codi]

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

data Either tipusExcepció tipusResultat = Left tipusExcepció | Right tipusResultat

-- prenem errors de tipus String per fer-ho senzill

instance Monad (Either String) where
  return x = Right x
  fail err = Left err

  (Left err) >>= _    = Left err   -- element absorbent en (>>=), no avalua la computació subseqüent
  (Right x)  >>= f    = f x          

instance MonadError String (Either String) where

  throwError err = Left err  -- assenyala excepció

  catchError (Left err) gestorDExcepcions = gestorDExcepcions err  -- gestiona l'excepció
  catchError (Right x) _ = Right x                                 -- no hi ha hagut excepció

Més mònades rellevants en Haskell[modifica | modifica el codi]

La mònada Writer[modifica | modifica el codi]

Facilita la generació de traces i enregistraments (ang:logs) sense barrejar-los amb els resultats.[34]

El tipus de la mònada conté com a valor un parell amb el resultat i l'enregistrament. Aquest darrer ha d'implementar un Monoide.

newtype Writer log a = Writer { runWriter :: (a, log) } -- ''log'' és l'enregistrament

instance (Monoid log) => Monad (Writer log) where
    return x             = Writer (x, mempty)      -- genera una dada efecte amb el ''log'' buit
    (Writer (x, log)) >>= f = let (x', log') = runWriter $ f x    -- l'aplicació de l'efecte funció (segon operand) genera un log'
                              in Writer (x', log `mappend` log')  -- que s'afegeix al log preexistent

--
class (Monoid log, Monad m) => MonadWriter log m | m -> log where
    pass   :: m (a, log -> log) -> m a  
    listen :: m a -> m (a, log)
    tell   :: log -> m ()

instance (Monoid log) => MonadWriter log (Writer log) where
    pass   (Writer ((x, f), logval)) = Writer (x, f logval)    -- 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 resultat
    tell   s                  = Writer ((), s)              -- estableix ''s'' al ''log''

-- censor: aporta la funció per aplicar al ''log'' mitjançant ''pass''
censor :: (MonadWriter log m) => (log -> log) -> m a -> m a 
censor f m_x = pass $ do x <- m_x ;  
                         return (x, f) 

-- listens: obté el parell retornat per listen i hi aplica un selector al segon element
listens :: (MonadWriter log m) => (log -> b) -> m a -> m (a, b)
listens sel m_x = listen m_x >>= (return. fmap sel)
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 amb el transformador WriterT.
import Control.Monad.Writer 

exemple :: Monad m => WriterT String m Int
exemple = tell "a" >> tell "b" >> return 1

main = runWriterT exemple >>= print

dóna el resultat

$ runhaskell prova.hs
(1,"ab")
  • 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 #-}

import Data.Monoid
import qualified Control.Monad.Writer as W

newtype Writer log a = Writer { runWriter :: (a, log) }

-- instanciem la classe Functor, base per a la classe Applicative
instance Functor (Writer log) where
  fmap f (Writer (x, w)) = Writer (f x, w)

-- instanciem la classe Applicative, base per a la Monad
instance (Monoid log) => Applicative (Writer log) where
  pure x = Writer (x, mempty)
  Writer (f, w) <*> Writer (x, w') = Writer (f x,  w `mappend` w')

-- finalment instanciem Monad
instance (Monoid log) => Monad (Writer log) where
  Writer (x, w) >>= f = let (x', w') = runWriter $ f x
                        in Writer (x', w `mappend` w')

-- i també MonadWriter
instance (Monoid log) => W.MonadWriter log (Writer log) where
  pass   (Writer ((x, f), w)) = Writer (x, f w)
  listen (Writer (x, w))     = Writer ((x, w), w)
  tell s = Writer ((), s)

exemple :: Writer String Int
exemple = W.tell "a" >> W.tell "b" >> return 1

main = print $ runWriter exemple

dóna el resultat

$ runhaskell prova.hs
(1,"ab")

La mònada State[modifica | modifica el codi]

Facilita l'enfilada d'un estat a través de computacions que el modifiquen, encapsulant-ne els canvis en codi funcional pur.[35][36][37]

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

newtype State s a = State { runState :: (s -> (a, s)) } 
 
instance Monad (State s) where 
    return x        = State $ \s -> (x, s)        -- estableix el resultat
    (State tf) >>= f = State $ \s -> let (x, s') = tf s   -- obté el (resultat, nouEstat) del primer efecte 
                                     in runState (f x) s' -- aplica (runState (f resultat)) sobre el nouEstat del primer efecte

---
class MonadState m s | m -> s where 
    get :: m s
    put :: s -> m ()
 
instance MonadState (State s) s where 
    get   = State $ \s -> (s, s)     -- obté l'estat
    put s = State $ \_ -> ((), s)    -- estableix l'estat

La mònada Reader[modifica | modifica el codi]

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.[38]

El valor de la mònada Reader és una funció sobre l'entorn: (env -> resultat). Per avaluar-lo caldrà aplicar-lo sobre un entorn inicial: (runReader computacióMonadReader) entornInicial

newtype Reader env a = Reader { runReader :: (env -> a) }  

instance Monad (Reader env) where
    return x         = Reader $ \_ -> x  -- genera computació amb resultat fix
    (Reader r) >>= f = Reader $ \env -> runReader (f (r env)) env       -- r :: (env -> a)

--
class MonadReader env m | m -> env where
    ask   :: m env
    local :: (env -> env) -> m a -> m a

instance MonadReader (Reader env) where
    ask       = Reader id
    local f computació = Reader $ \env -> runReader computació (f env)

asks :: (MonadReader env m) => (env -> a) -> m a
asks sel = 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.

Vegeu l'exemple.[38]

La mònada STM[modifica | modifica el codi]

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.

Vegeu Haskell concurrent - STM.

La mònada Par[modifica | modifica el codi]

Encadenament de càlculs simultanis

El Functor aplicatiu Concurrently[modifica | modifica el codi]

A la biblioteca async,[39] 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.

Mònades a l'OCaml[modifica | modifica el codi]

  • 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]
  • Extensió sintàctica per a les mònades a OCaml.[41]

Mònades a l'F#[modifica | modifica el codi]

Vegeu Computation expressions[42][43] i també Async. Workflows[44][45]

  • Extensió sintàctica per a les Mònades a F#.[46][43]

Mònades a Scala[modifica | modifica el codi]

La clàusula for corresponent a les llistes per comprensió respon a un encadenament monàdic.[47]

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".[48]

scala> val opt3 = Some(3)
scala> val opt4 = Some(4)

scala> for { x <- opt3; y <- opt4} yield (x+y)
res4: Option[Int] = Some(7)

// desplegament equivalent
scala> opt3 flatMap { x => opt4 map { y => x + y}}
res5: Option[Int] = Some(7)

Relacionat[modifica | modifica el codi]

Referències[modifica | modifica el codi]

  1. 1,0 1,1 Haskellwiki - La mònada(anglès)
  2. MonadFail proposal (MFP)(anglès)
  3. MonadFail proposal update 1(anglès)
  4. mòdul Control.Monad.Fail
  5. 5,0 5,1 mòdul GHC.IO funció failIO
  6. 6,0 6,1 Biblioteca OCaml Light weight threads per a computacions IO no blocants (asíncrones)
  7. FilterMonadic(anglès)
  8. La classe d'objectes Optional<T>(anglès)
  9. Haskell wiki - Monad laws(anglès)
  10. Codi font de la mònada Maybe
  11. Composició de Kleisli
  12. Notació especial "do" a la mònada (anglès)
  13. El mòdul Control.Monad
  14. Haskell98 - Condicionals Traducció de if-then-else(anglès)
  15. Do i l'if-then-else(anglès)
  16. Haskell2010 (anglès) DoAndIfThenElse hi és comprès.
  17. 17,0 17,1 Bloc Do recursiu(anglès)
  18. Do recursiu i MonadFix(anglès)
  19. L'abstracció Applicative - Programació aplicativa amb efectes col·laterals(anglès)
  20. 20,0 20,1 Applicative i Alternative(anglès)
  21. La classe Functor(anglès)
  22. WrappedMonad(anglès)
  23. Formlets - functors aplicatius als formularis
  24. Notes de la versió 7.8.1(anglès)
  25. 25,0 25,1 Functor-Applicative-Monad Proposal(anglès)
  26. Why is Alternative concatenating lists instead of choosing the first non-empty one?
  27. HaskellWiki - La classe MonadPlus(anglès)
  28. Extensions GHC - Llista per comprensió monàdica(anglès)
  29. GHC - Notes de la versió 7.2.1(anglès)
  30. Viquillibre Haskell - Monad Transformers (anglès)
  31. la classe MonadTrans(anglès)
  32. la classe MonadIO (anglès)
  33. La classe MonadError(anglès)
  34. La mònada Writer(anglès)
  35. La mònada State(anglès)
  36. Diferències entre State i ST
  37. mòdul Control.Monad.State.Lazy
  38. 38,0 38,1 La mònada Reader(anglès)
  39. Biblioteca Async (anglès)
  40. coThreads (anglès) Mònada STM per a OCaml (anglès)
  41. Syntax extension for Monads in Ocaml(anglès)
  42. Viquillibre anglès de F# - Computation expressions(anglès)
  43. 43,0 43,1 Microsoft - F# Computation expressions(anglès)
  44. Asynchronous Workflows
  45. F# survival guide (anglès) Vegeu Chapter 16 Workflows, Asynchronous & Parallel Programming
  46. viquillibre - Computation_Expressions#Syntax_Sugar
  47. Monads in Scala(anglès)
  48. Introduction to Monads in Scala(anglès)

Enllaços externs[modifica | modifica el codi]