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 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 contenidor d'un sol valor o estat, resultat de la darrera operació, el qual es pot transformar, mitjançant l'encadenament, amb operacions sobre el seu contingut (amb possibles efectes col·laterals).

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 operacions d'entrada/sortida, sobre l'entorn exterior, canvis d'estat, i també el preprocés i optimització de les operacions abans de la seva execució.[1]

En Haskell:

 class Monad m where
    -- generador
    return :: a -> m a   -- genera un valor de tipus mònada a partir
                         -- d'un valor de tipus ''a'' interpretable com a resultat
 
    -- encadenament (ang.: ''bind'') 
    -- encadena una acció amb una funció sobre el seu resultat
    (>>=)  :: m a -> (a -> m b) -> m b 
 
          -- exemple: getline >>= putStrLn  -- imprimeix la línia introduïda
 
    -- encadena dues accions, ignorant el resultat de la primera
    (>>)   :: m a -> m b -> m b  
    m_x >> m_y  =  m_x >>= (\_ -> m_y) 
 
    -- equival a encadenar amb (>>=) amb la funció constant (const m_y)
 
        -- exemple: putStr "polseu Intro" >> hFlush stdout >> getLine >> putStrLn "fet"
 
    -- assenyalament d'errors dins la seqüència
    fail :: String -> m a
    -- la implementació per defecte crida a ''error'', volent dir operació "no definida"

La fallada (op. fail) es pot indicar

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

(* 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 ;;

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

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 amb element absorbent per l'esquerra per indicar 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 i pot indicar una situació d'error que invalidi la continuació de les computacions.

L'encadenament de computacions amb mònades que implementin l'element absorbent, evita haver de comprovar l'error a cada pas intermedi.

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

-- de la definició
 
data Maybe a = Nothing | Just a
 
instance Monad Maybe where
  return x = Just x
  fail _ = Nothing
 
  Nothing  >>= _ = Nothing       -- no avalúa la computació subseqüent 
                                 --      (passa del 2n. operand: _)
  (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'avalúa
 
    Right r >>= f = f r

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

Composició de Kleisli.[6]

(>=>) :: 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 que contindrà x 
                            -- 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

Vegeu el tipus de (failIO s)[2]

$ ghci
Prelude> fail "pifiada"
*** Exception: user error (pifiada)
Prelude> :t fail "pifiada"
fail "pifiada" :: 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
    putStrLn x
    putStrLn "Fet"
    return x
 
-- equival a l'expressió, relligant cada línia
--   o bé amb (>>= \patró ->) les que incorporen (patró <-)
--   o bé amb (>>) les que no
 
bloc = getLine >>=  \ x ->     
                  putStrLn x >> 
                  putStrLn "Fet" >> 
                  return x

el bloc do és un artifici sintàctic[7] 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 en base a resultats de computacions precedents, cosa que no és possible amb clàusules where.

"let" variable "=" expr {";" variable "=" expr}
obté el valor de les expressions. let introdueix un subbloc que es podrà delimitar pel sagnat o claus {} i separadors ';'
 -- equival a ''return expr >>= \ variable ->''
main = do
          print "Entreu un mot: "
          x <- getLine
          print "Entreu-ne un altre: "
          y <- getLine
 
          -- subbloc ''let'' d'expressions sobre resultats 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[8] 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)
 
  -- 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[9]

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

-- segons el Haskell 2010[10][11] 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

Exemple del forever[modifica | modifica el codi]

{-# 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                      -- repetir indefinidament
               x <- getLine
               case x of           -- alternativa (una línia per branca, si més d'una, subbloc do)
 
                  [] -> ioError (userError "Línia buida") -- llança excepció per sortir del "forever"
                  _ -> do
                          putStrLn x
                          putStrLn "tornem-hi"                        
            )
            (\(excep :: SomeException) -> print excep
            )

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

Als subblocs let d'expressions[modifica | modifica el codi]

Els blocs let permeten definicions recursives a GHC.

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

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 resultat de la instrucció que segueix
        r = x * 2
    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 de computacions[modifica | modifica el codi]

A GHC, permeten recursivitat a les computacions. Cal esmentar la pragma {-# LANGUAGE DoRec #-} Vegeu ref.[13][12]

{-# LANGUAGE DoRec #-}
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ó en primer lloc
    x <- m_x    -- computació posterior
    return (f x)

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

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

class Functor f => Applicative f where
        -- eleva el valor de tipus ''a'' al 
        --   tipus ''(f a)'' de computacions amb resultat de tipus 'a'
        pure :: a -> f a
 
        -- combina 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''
        (<*>) :: f (a -> b) -> f a -> f b
 
        -- aplica seqüencialment dues computacions retornant el resultat de la segona
        (*>) :: f a -> f b -> f b
 
        -- aplica seqüencialment dues computacions retornant el resultat de la primera
        (<*) :: f a -> f b -> f a

La classe Functor:[16]

class Functor f where
  fmap :: (a -> b) -> f a -> f 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.[17] 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.[18]

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

class Applicative f => Alternative f where
    -- | L'element neutre de  '<|>'
    empty :: f a
 
    -- | Una operació binària associativa
    (<|>) :: f a -> f a -> f 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.[19] De cara a una eliminació de duplicitats entre Mònada i Applicative[20] 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.[21]

  • De la proposta Functor-Applicative-Monad:[20]
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.[22] 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
   v >> 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]), concatena els resultats de la seva aplicació
    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.[23]

{-# 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[24]

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 la operativa de dues mònades en una, encapsulant una mònada dins la mònada transformada resultant. Vegeu[25]

Implementen la classe MonadTrans[26]

class MonadTrans t where
  -- lift eleva una acció del tipus de la "mònada m" al tipus de la "mònada transformada (t m)"
  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[27] és per definir una versió optimitzada de lift de MonadTrans per elevar el tipus de les computacions des de la mònada IO quan hi ha tranformacions interposades.

class Monad m => MonadIO m where
  liftIO :: IO a -> m a  -- eleva el tipus d'una computació 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ó de computacions 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 la computació 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 una computació 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 (d'un argument) 
--                                         a una funció de computacions
 
liftM :: (Monad m) => (a -> b) -> (m a -> m b)
liftM f = \m_x -> do 
                 x <- m_x
                 return (f x)
 
-- ''lift'' de MonadTrans eleva una computació 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 computacions 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'una computació 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]

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

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   -- no avalúa la computació subseqüent (2n. operand)
  (Right x)  >>= f    = f x          
 
instance MonadError String (Either String) where
 
  throwError err = Left err
 
  catchError (Left err) gestorDExcepcions = gestorDExcepcions err
  catchError (Right x) _ = Right x    -- computació sense 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 (logs) sense barrejar-los amb els resultats.[29]

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

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

Facilita l'arrossegament d'un entorn d'associacions variable, per exemple la substitució de textos definits amb plantilles, que poden augmentar o ésser tapades per noves definicions.[33]

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,[34] facilita l'execució simultània d'operacions I/O no blocants on el tipus Concurrently implementa un functor aplicatiu que permet seqüenciar l'engegada de diverses computacions IO simultànies i esperar-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.

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

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

Vegeu Computation expressions[37][38] i també Async. Workflows[39][40]

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

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

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

Si en comptes de llistes hi subministrem dades de tipus Option[A] el resultat és també del tipus de la mònada Option.

Vegeu presentació "Introduction to Monads in Scala".[43]

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

Enllaços externs[modifica | modifica el codi]