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, 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 les transaccions en memòria (mònada STM a Haskell i a OCaml), 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
    return :: a -> m a   -- genera un valor de tipus mònada a partir
                         -- d'un valor de tipus ''a'' interpretable com a resultat
 
    -- (ang.: ''bind'') encadena una acció mònada 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, sense tenir en compte el resultat de la primera
    (>>)   :: m a -> m b -> m b  
                 -- exemple: putStr "polseu Intro" >> getLine >> putStrLn "fet"
 
    -- assenyalament d'errors dins la seqüència, retorna l'element neutre de les operacions
    fail :: String -> m a

A Haskell, en demanar, de manera tardana, el resultat d'un encadenament cada lligada (>>=) o (>>) 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 asíncrones.[2]

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.

Taula de continguts

La mònada IO [modifica]

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" >> 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
                    -- i retorna l'element neutre de les operacions, ex. IO ()
                    -- el tipus de l'excepció depèn de la instanciació de la mònada (IOError cas d'IO),<ref name='rwh_fail'/>

Composició monàdica [modifica]

Composició de Kleisli.[3]

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

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

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

 do           
    x <- getLine
    putStrLn x
    putStrLn "Fet"
    return x

el bloc do és un artifici sintàctic[4] 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
 -- \ x -> ... : expressió lambda (func. anònima) que s'aplica, per (>>=), al resultat de l'oper. precedent
 
 getLine >>=  \ x ->     
                  putStrLn x >> 
                  putStrLn "Fet" >> 
                  return x
 
 -- "return" no pressuposa la sortida del bloc, només és un operador generador del tipus mònada
 --            el valor final de l'expressió mònada serà el de la darrera operació

Clàusula let dins el bloc do [modifica]

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 ->''
variable "<-" expr_monàdica
obté el valor contingut dins la mònada de l'expressió
 -- equival a ''expr_monàdica >>= \ variable ->''
main = do
          print "Entreu un mot: "
          x <- getLine
          print "Entreu-ne un altre: "
          y <- getLine
 
          let z = x ++ y
              w = z ++ "."      -- no cal repetir ''let'', però si aliniar el bloc
 
          putStrLn ("La concatenació és: " ++ show w)

Control imperatiu [modifica]

Operacions comparables al control dels llenguatges imperatius.

El mòdul Control.Monad[5] 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]

-- segons el Haskell 98[6]

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

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

import Control.Monad as Monad
 
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 -> print excep
            )

Recursivitat en els blocs do [modifica]

Als subblocs let d'expressions [modifica]

Els blocs let permeten definicions recursives a GHC.

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

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]

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

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

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 a la llista amb (:)

Aquest patró correspon a un combinador a 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    -- f2 :: a1 -> a2 -> b
 
liftMn fn x1 ... xn = (return fn) `ap` x1 `ap` ... `ap` xn    -- 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.[11]

La classe Applicative [modifica]

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

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 dues computacions retornant el resultat de la segona
        (*>) :: f a -> f b -> f b
 
        -- aplica dues computacions retornant el resultat de la primera
        (<*) :: f a -> f b -> f a

La classe Functor:[13]

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

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]

Defineix un monoide sobre els functors aplicatius. [12]

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. Es pretén que la relació d'Alternative amb Applicative sigui similar a la de MonadPlus amb Monad i que la implementació d'Alternative i de MonadPlus donin el mateix resultat per als tipus que implementin ambdues classes. És el cas de les llistes.[16]

La classe MonadPlus (computacions amb sol·lucions múltiples) [modifica]

Defineix un Monoide sobre les computacions mònada.[17] Permet expressar computacions amb sol·lucions múltiples (zero o més) i l'opció de fallada (no avaluació de les computacions posteriors) i obtenir-ne la combinació de sol·lucions.

class Monad m => MonadPlus m where
  mzero :: m a                         -- fallada de la mònada 
  mplus :: m a -> m a -> m a           -- combina sol·lucions
 
-- "mzero" ha de satisfer:
   mzero >>= f  =  mzero
   v >> mzero   =  mzero

exemple: la mònada Llista [modifica]

-- de la definició a Control.Monad.Instances
instance Monad [] where
    return x            = [x]
    fail _              = []
    xs >>= f            = foldr ((++) . f) [] xs             -- f :: a -> [b]
    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 fail ""   -- 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]

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

Vegeu ref.[18]

{-# 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)
 
prova = TestCase $ assertEqual "comprensió monàdica: " llista_esperada llista_actual
 
main = do
         comptaTests <- runTestTT prova
         print comptaTests
L'extensió MonadComprehensions
permet emprar qualsevol mònada en la construcció llista per comprensió. Cal GHC >= 7.2.1[19]

En el següent cas ja no és una llista per comprensió sino 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

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

Optimitza la seqüència evitant l'execució de computacions subseqüents a la que falla.[20]

-- de la definició
instance Monad Maybe where
  return x = Just x
  fail _ = Nothing
 
  Nothing  >>= _ = Nothing       -- no avalúa la computació subseqüent 
                                 --      (passa del 2on. param.: _)
  (Just x) >>= f = f x           -- f :: a -> Maybe b
 
  Nothing  >>  _      = Nothing  -- no avalúa la computació subseqüent
  (Just _) >>  k      = k

Exemple d'ús

import Numeric
import System.IO (hFlush, stdout)
import Control.Applicative ((<|>))
import Data.Maybe (fromJust)
 
llegirReal :: String -> Maybe Double
llegirReal str = case (readSigned readFloat) str of
                   -- llista d'interpretacions sintàctiques
                   [(r, _resta_del_text)] -> return r   -- (una sola interpretació) 
                   [] -> fail ""    -- cap interpretació => nombre inexistent 
                   _ -> fail ""  -- múltiples interpretacions => entrada ambigua
 
obtenirLArrelQuadrada :: Double -> Maybe Double
obtenirLArrelQuadrada x
  | x < 0  = fail ""
  | (x == 0 || x == 1) = return x
  | otherwise = return $ sqrt x
 
main = do
        putStr "entreu nombre real: "
        hFlush stdout
        linia <- getLine
        calc2 linia
 
calc1 linia = do    -- si llegirReal falla, la resta de l'expressió no és avaluada
 
        let maybeResultat = llegirReal linia >>= obtenirLArrelQuadrada 
        case maybeResultat of
          Nothing -> putStrLn "o bé no s'ha llegit bé, o bé valor negatiu"
          Just v -> putStrLn $ show v
 
-- incorporant l'op. <|> de la classe Alternative
calc2 linia = do
        let maybeTextResultat = llegirReal linia >>= (fmap show) . obtenirLArrelQuadrada
            maybeText = maybeTextResultat <|> (Just "o bé no s'ha llegit bé, o bé valor negatiu")  
 
        putStrLn $ fromJust maybeText

Transformadors de mònades [modifica]

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

Implementen la classe MonadTrans[22]

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[23] é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]

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

import Control.Monad (liftM)
 
-- 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 >> k  = 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 _ -> runMaybeT k       -- sí que l'avalua
                 )  
 
  tm_v >>= f = MaybeT (do                     -- el tipus del bloc do és :: m (Maybe a)
                 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)
                 )       
 
-- ''lift'' 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    
 
instance MonadTrans MaybeT where
  -- elevem computacions de ''m'' amb categoria d'exitoses
 
  lift = MaybeT . liftM Just   -- versió tàcita o ''pointFree'' 
                               --   (sense paràm. formals reduïbles)
  -- lift m_a = MaybeT $ liftM Just m_a   -- versió no ''pointFree''

Exemple d'ús [modifica]

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
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, _resta_del_text)] -> Just r
                   _ -> Nothing
 
obtenirRealVàlid :: Int -> MaybeT IO Double   -- mònada (MaybeT IO)
obtenirRealVàlid intents = do
        lift $ putStr "entreu nombre real: "
        lift $ hFlush stdout
        linia <- lift getLine
        case llegirReal linia of
             Just v -> return v
             Nothing -> let intents_restants = intents -1
                        in if intents_restants > 0
                           then do
                             lift $ putStrLn "torna-hi que no anem bé!"
                             obtenirRealVàlid intents_restants
                           else fail ""
 
 
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 3 >>= obtenirLArrelQuadrada
         case v of
              Just v -> putStrLn $ "el valor és: " ++ showFloat v ""
              Nothing -> putStrLn "No hi ha manera!!"

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

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 la sortida del gestor d'excepcions on recupera l'encadenament habitual.[24]

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]

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' Incorrecte. 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 (2on. param.)
  (Right x)  >>= f    = f x          
 
  (Left err) >>  _    = Left err  -- no avalúa la computació subseqüent
  (Right _)  >>  k    = k
 
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]

La mònada Writer [modifica]

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

La mònada State [modifica]

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

La mònada Reader [modifica]

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

La mònada STM [modifica]

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]

Encadenament de càlculs simultanis

El Functor aplicatiu Concurrently [modifica]

La biblioteca async facilita l'execució simultània d'operacions I/O no blocants on el tipus Concurrently implementa un functor aplicatiu que permet engegar diverses computacions IO simultànies i obtenir-ne els resultats quan han acabat totes, i també implementa Alternative per obtenir el resultat de la primera que acaba, anuŀlant els fils d'execució de la resta d'operacions.

Mònades a l'OCaml [modifica]

  • la biblioteca LWT (light-weight cooperative threads) modela la seqüenciació de computacions d'entrada/sortida no-blocants (asíncrones) en una mònada.[2]
  • Extensió sintàctica per a les mònades a OCaml.[31]

Mònades a l' F# [modifica]

Vegeu Computation expressions[32][33]i també Async. Workflows[34] [35]

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

Relacionat [modifica]

Referències [modifica]

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

Enllaços externs [modifica]