Mònada (programació funcional)
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.
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]
- la biblioteca coThreads[30] proporciona la mònada STM a OCaml
- Guia sobre Mònades per a OCaml (anglès)
- 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]
Relacionat [modifica]
Referències [modifica]
- ↑ 1,0 1,1 Haskellwiki - La mònada(anglès)
- ↑ 2,0 2,1 Biblioteca OCaml Light weight threads per a computacions IO no blocants (asíncrones)
- ↑ Composició de Kleisli
- ↑ Notació especial "do" a la mònada (anglès)
- ↑ El mòdul Control.Monad
- ↑ Haskell98 - Condicionals Traducció de if-then-else(anglès)
- ↑ Do i l'if-then-else(anglès)
- ↑ Haskell2010 (anglès) DoAndIfThenElse hi és comprès.
- ↑ 9,0 9,1 Bloc Do recursiu(anglès)
- ↑ Do recursiu i MonadFix(anglès)
- ↑ L'abstracció Applicative - Programació aplicativa amb efectes col·laterals(anglès)
- ↑ 12,0 12,1 Applicative i Alternative(anglès)
- ↑ La classe Functor(anglès)
- ↑ WrappedMonad(anglès)
- ↑ Formlets - functors aplicatius als formularis
- ↑ Why is Alternative concatenating lists instead of choosing the first non-empty one?
- ↑ HaskellWiki - La classe MonadPlus(anglès)
- ↑ Extensions GHC - Llista per comprensió monàdica(anglès)
- ↑ GHC - Notes de la versió 7.2.1(anglès)
- ↑ Codi font de la mònada Maybe
- ↑ Viquillibre Haskell - Monad Transformers (anglès)
- ↑ la classe MonadTrans(anglès)
- ↑ la classe MonadIO (anglès)
- ↑ La classe MonadError(anglès)
- ↑ La mònada Writer(anglès)
- ↑ La mònada State(anglès)
- ↑ Diferències entre State i ST
- ↑ mòdul Control.Monad.State.Lazy
- ↑ La mònada Reader(anglès)
- ↑ coThreads (anglès) Mònada STM per a OCaml (anglès)
- ↑ Syntax extension for Monads in Ocaml(anglès)
- ↑ Viquillibre anglès de F# - Computation expressions(anglès)
- ↑ 33,0 33,1 Microsoft - F# Computation expressions(anglès)
- ↑ Asynchronous Workflows
- ↑ F# survival guide (anglès) Vegeu Chapter 16 Workflows, Asynchronous & Parallel Programming
- ↑ viquillibre - Computation_Expressions#Syntax_Sugar
Enllaços externs [modifica]
- Haskell.org - Tot sobre les mònades (anglès)
- Mònades al viquillibre anglès sobre Haskell (anglès)
- Difference between State, ST, IORef, and MVar Diferències entre les mònades State i ST, i les referències (punters) IORef i MVar