Sunteți pe pagina 1din 35

Cursul

11 12 MONADE
Monade
Introducere
Tipul de data Maybe
Constructori de =p
Monada n Haskell
Exemplul 1: monada Maybe
Exemplul 2: monada []
Clasa Monad
Notaia do
Monada IO
Exemple

G. Grigoras - FII Programare Funcional 1


Side Eects n Haskell
Programele de pn acum nu au side-eects: programele
sunt executate pentru a aa nite valori care se aeaz.
Cum construim programe interac=ve? (ci=rea unor valori,
aare, lucru cu iere, desenarea unei guri etc.)

n Haskell, valorile pure sunt separate de aciuni, n dou


moduri:
Tipuri: O expresie de =p IO a are asociate execuiei sale eventuale
aciuni , ul=ma dintre ele ind returnare unei valori de =p a.

Sintax: Construcia sintac=c do realizeaz o aciune care, n general,


este secven de aciuni elementare.

G. Grigoras - FII Programare Funcional 2


Introducere
Monada o cale de a structura calculele ca valori i secvene
de calcule ce u=lizeaz aceste valori
Valorile monadice sunt numite calcule

Permit construirea de calcule folosind blocuri secveniale care,


la rndul lor pot secvene de calcul

Monada determin modul n care o combinaie de calcule


formeaz un nou calcul, scu=ndu-l pe programator de a scrie
cod pentru as^el de combinaii

O monad este vzut ca o strategie pentru combinarea


calculelor ntr-un calcul complex

G. Grigoras - FII Programare Funcional 3


Tipul de data Maybe
Tipul calcul care poate eua sau poate returna o valoare:
data Maybe a = Nothing | Just a

mydiv:: Float -> Float -> Maybe Float


mydiv x 0 = Nothing
mydiv x y = Just (x/y)

Tipul Maybe sugereaz o strategie de combinare a


calculelor: daca un calcul compus const dintr-un calcul B
care depinde de rezultatul altui calcul A, atunci calculul
combinat va produce Nothing daca A sau B produce
Nothing; al^el, dac ambele se produc cu succes, calculul
combinat va produce rezultatul calculului B, calcul care este
aplicat rezultatului calculului A

G. Grigoras - FII Programare Funcional 4


Proprieti fundamentale
Modularitate
calcule (complexe) compuse din calcule simple
separarea strategiilor de combinare a calculelor de calculele n
sine
Flexibilitate
Programele scrise cu monade sunt mai adaptabile dect cele
fr monade
Izolare
monadele permit crearea de structuri de calcul n s=l impera=v
(sistemul I/O de ex.) care rmn izolate de corpul principal al
programului funcional

G. Grigoras - FII Programare Funcional 5


Constructori de =p
Un constructor de =p este o deniie de =p parametrizat ce
folosete =puri polimorfe
In deniia =pului Maybe
data Maybe a = Nothing | Just a
Maybe este un constructor de =p iar Nothing, Just
sunt constructori de date (de =pul Maybe)
Se poate construi o data aplicnd constructorul de data
Just unei valori:
tara = Just Romania
Se poate construi un tip aplicnd constructorul
de tip Maybe unui tip:
Maybe Int, Maybe String

G. Grigoras - FII Programare Funcional 6


Tipurile polimorfe pot privite ca nite containere ce sunt
capabile s conin valori de diverse =puri:
Maybe String ar putea privit ca un container Maybe ce
poate conine o valoare de =p String (sau Nothing)

Se poate ca =pul containerului sa e polimorf: m a


reprezint un container de un anumit =p m ce poate conine
o valoare de un anumit =p a

Se folosesc adesea variabile =p cu constructorii de =p pentru


a descrie trsturile abstracte ale unui calcul: Maybe a este
=pul tuturor calculelor care pot returna o valoare de =p a sau
Nothing

G. Grigoras - FII Programare Funcional 7


Monada n Haskell
O monad n Haskell este dat prin:
un constructor de =p (notat m) care este numit
constructorul de =p monad
O funcie constructor de =p, a -> m a,
care construiete instane ale monadei, numit
return
O funcie m a -> (a -> m b) -> m b
care combin o instan m a cu un calcul
(funcie) ce produce dintr-o valoare de =p a o
instan m b a monadei i n nal produce o nou
instan m b. Aceast funcie este denumit
bind i se scrie >>=
G. Grigoras - FII Programare Funcional 8
Monada in Haskell
constructorul de =p monad denete un =p de calcul
funcia return creeaz valori primi=ve (instane) ale acestui =p
operatorul (funcia) >>= combin calcule de acest =p pentru a
obine calcule mai complexe de =pul respec=v
Analogia cu containerul:
constructorul de =p m este un container ce poate conine diferite
valori
m a este un container ce conine o valoare de =p a
funcia return pune o valoare n containerul monad
funcia >>= :
ia valoarea din container
o transmite unei funcii
produce un nou container ce conine o noua valoare, posibil de alt =p

funcia >>= se cheam bind pentru c ea leag valoarea din


containerul monad cu primul argument al funciei

G. Grigoras - FII Programare Funcional 9


Acces la componentele unei date
Tipul de dat Point:
data Point = Pt Float Float
let p = Pt 3.2 5.4

Accesul la componente poate fcut prin funcii:


pointx :: Point -> Float
pointx (Pt x _) = x
pointx p

Mai comod declaraia echivalent:


data Point = Pt {pointx, pointy :: Float}

pointx :: Point -> Float


pointy :: Point -> Float

G. Grigoras - FII Programare Funcional 10


Acces la componentele unei date
data T = C1 {f1,f2 :: Int}
| C2 {f1 :: Int,
f3,f4 :: Char}

Expresie Traducere
C1 {f1 = 3} C1 3 undefined

C2 {f1 = 1, f4 = 'A', f3 = 'B} C2 1 'B' 'A'

x {f1 = 1} case x of C1 _ f2 -> C1 1 f2


C2 _ f3 f4 -> C2 1 f3 f4

G. Grigoras - FII Programare Funcional 11


Exemplu: Monada Maybe
Studiu de caz: determinarea strmoilor: bunici,
strbunici etc. din arborele genealogic
Tipul de data Person:
data Person= Person {name::String,
mother::Maybe Person, father::Maybe Person}

Pentru a determina bunicul unei persoane am putea


deni o funcie:
maternalGrandfather :: Person -> Maybe Person
maternalGrandfather s = case (mother s) of
Nothing -> Nothing
Just m -> father m

G. Grigoras - FII Programare Funcional 12


Exemplu: Monada Maybe
Un alt strmo:
mothersPaternalGrandfather :: Person -> Maybe Person
mothersPaternalGrandfather s = case (mother s) of
Nothing -> Nothing
Just m -> case (father m) of
Nothing -> Nothing
Just gf -> father gf

Soluia nu este ecient


Odat gsit valoarea Nothing ntr-un punct al
calculului, aceasta rmne Nothing ca rezultat
nal
Sa ncercm s implementm acest lucru o
singur dat

G. Grigoras - FII Programare Funcional 13


Exemplu: Monada Maybe
Crearea unui combinator ce nglobeaz acest aspect:
comb :: Maybe a -> (a -> Maybe b) -> Maybe b
comb Nothing _ = Nothing
comb (Just x) f = f x

U=lizarea acestui combinator pentru a construi


secvene mai complicate:
maternalGrandfather :: Person -> Maybe Person
maternalGrandfather s = (Just s) `comb` mother `comb` father

fathersMaternalGrandmother :: Person -> Maybe Person


fathersMaternalGrandmother s = (Just s) `comb` father `comb`
mother `comb` mother

mothersPaternalGrandfather :: Person -> Maybe Person


mothersPaternalGrandfather s = (Just s) `comb` mother `comb`
father `comb` father

G. Grigoras - FII Programare Funcional 14


Exemplu: Monada Maybe
Combinatorul comb
Este o funcie polimorf, nu este specializat pentru =pul
Person
Captureaz strategia general de combinare a calculelor
care pot, n par=cular, s eueze
Poate folosit i n alte aplicaii: interogare baze de date,
cutare n dicionare, etc.

Tipul Maybe mpreun cu funcia Just (care


acioneaz ca i return) i combinatorul comb (ce
acioneaz ca i >>=) formeaz o monad folosit
pentru construirea de calcule ce pot eua
G. Grigoras - FII Programare Funcional 15
Monada []
Constructorul de =p [] (pentru construirea listelor) este
de asemenea o monad:

Funcia return creaz lista singleton


return x = [x]

Operaia de legare pentru liste construete o noua list


coninnd rezultatul aplicrii funciei tuturor valorilor listei
originale
l >>= f = concatMap f l
concatMap :: (a -> [b]) -> [a] -> [b]

G. Grigoras - FII Programare Funcional 16


Clasa Monad
n Haskell exist o clas standard Monad care
denete numele si signatura funciilor
return i >>=

class Monad m where


(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a

Se recomand ca monadele u=lizator s e


instane ale clasei Monad

G. Grigoras - FII Programare Funcional 17


Exemplul 1 revizuit
Monada Maybe este instan a clasei Monad:
instance Monad Maybe where
Nothing >>= f = Nothing
(Just x) >>= f = f x
return = Just
Se pot u=liza operatorii standard din Monad:
maternalGrandfather s = (return s) >>= mother >>= father

fathersMaternalGrandmother s = (return s) >>= father >>=


mother >>= mother

G. Grigoras - FII Programare Funcional 18


Notaia do
Alterna=va n a u=liza funciile monadice
Similar cu u=lizarea list comprehensions pentru
liste
Scrierea calculelor monadice n s=l pseudo-impera=v
folosind variabile:
Rezultatul unui calcul monadic poate asignat unei
variabile folosind operatorul <-
U=lizarea variabilei ntr-o subsecven de calcul monadic,
se face automat legarea
Tipul expresiei din dreapta semnului <- este =p monadic
m a

G. Grigoras - FII Programare Funcional 19


Nota=a do
mothersPaternalGrandfather s = do m <- mother s
gf <- father m
father gf

sau:
mothersPaternalGrandfather s =
do {m <- mother s; gf <- father m; father gf}

Monadele ofer prin notaia do posibilitatea de a crea calcule


n s=l impera=v n cadrul programelor funcionale

Notaia do este doar syntac=c sugar

G. Grigoras - FII Programare Funcional 20


Transformarea do n >>=
Comanda x <- expr1 devine:
expr1 >>= \x ->
Comanda expr2 devine:
expr2 >>= \_ ->
De exemplu:
mothersPaternalGrandfather s = do m <- mother s
gf <- father m
father gf
devine
mothersPaternalGrandfather s = mother s >>= \m ->
father m >>= \gf ->
father gf
Operatorul >>= (bind) leag valoarea din monad cu
argumentul din urmtoarea lambda expresie

G. Grigoras - FII Programare Funcional 21


Monada IO

Operaiile de intrare/ieire sunt incompa=bile


cu s=lul funcional pur: efect secundar,
modicarea valorilor
Soluia: monada IO a
Calcule ce realizeaz I/O n cadrul monadei IO
Calculele n monada IO se numesc aciuni de
intrare /ieire (I/O ac+ons)

G. Grigoras - FII Programare Funcional 22


Side Eects n Haskell
Un program interac=v este conceput ca o funcie care are ca argument
state of the world (o valoare de =p World) i produce o valoare de =p
World care este o alt stare ce reect efectul colateral produs de
program:

type IO = World -> World

n general, un program interac=v poate returna i o anume valoare pe


lng efectul ce-l are asupra strii (de exemplu, se citete un caracter de la
tastatur (aciune) i se returneaz acel caracter). Aadar, mai corect:
type IO a = World -> (a, World)
Exemplu: IO Char, IO Int; IO ()

O expresie de =p IO a are asociate execuiei sale aciuni, ul=ma dintre


ele ind returnarea unei valori de =p a.
G. Grigoras - FII Programare Funcional 23
Construcia sintac=c do
Dac act este o aciune de =p IO a atunci putem descrie
realizarea aciunii act, returna valoarea corespunztoare i
eventual adugarea altor aciuni, as^el:

do val <- act


... -- actiunea urmatoare
... -- actiunea urmatoare
return x -- actiunea final

Toate aciunile de dup val <- act pot folosi val.


Funcia return are un parametru de =p a, devine o
aciune de =p IO a, care nu face altceva dect s returneze
valoarea respec=v.

G. Grigoras - FII Programare Funcional 24


do Exemplu
:: Integer :: IO Integer

:: IO Char
do x <- f 5 :: IO ()
c <- getChar (aciune fr
v <- )
putChar c
:: Char return (x + 4)

:: IO Integer
(tipul ultimei aciuni determin
tipul expresiei do )

G. Grigoras - FII Programare Funcional 25


Aciuni IO
O valoare de =p IO a este o aciune;
aceast valoare va avea efect atunci cnd va
executat.

n Haskell, o valoare program este valoarea


variabilei main din modulul Main. Dac
aceast valoare este de =p IO a, atunci va
executat ca o aciune. Dac este de alt =p
atunci valoarea sa va aat.

G. Grigoras - FII Programare Funcional 26


Aciuni IO predenite
Introducerea unui caracter de la tastatur
getChar :: IO Char
Scrie un caracter la terminal
putChar :: Char -> IO ()
Introducerea unei linii de la tastatur
getLine :: IO String
Ci=rea unui ier ca i String
readFile :: FilePath -> IO String
Scriere String n ier
writeFile :: FilePath -> String -> IO ()

G. Grigoras - FII Programare Funcional 27


Aciuni recursive
Aciunea getLine poate denit recursiv din aciuni mai
simple:

getLine :: IO String
getLine = do c <- getChar -- citeste un caracter
if c == '\n' -- daca este newline
then return [] -- return sirul vid
else do l <- getLine - recursiv restul
return (c:l) -- return intreaga linie

G. Grigoras - FII Programare Funcional 28


Aciuni recursive
Aciunea putStr poate denit recursiv din aciuni mai
simple:

putStr:: String -> IO ()

putStr[] = return ()
putStr (x:xs) = do putChar x
putStr xs

putStrLn:: String -> IO ()

putStrLn xs = do putStr xs
putChar \n

G. Grigoras - FII Programare Funcional 29


Exemplul 1: Lungimea unui ir ci=t
Aciunea de ci=re a unui ir de la tastatur i aarea lungimii
sale:

strlen:: IO()
strlen = do putStr Introdu un sir:
xs <- getLine
putStr Sirul are
putStr(show(length(xs))
putStrLn caractere

Main> strlen
Introdu un sir: abracadabra
Sirul are 11 caractere

G. Grigoras - FII Programare Funcional 30


Exemplul 2: Suma unor intregi
getInt :: IO Int
getInt = do line <- getLine
return (read line :: Int)

sumInts :: IO Int
sumInts = do n <- getInt
if n==0
then return 0
else
do m <- sumInts
return (n+m)
suma::IO ()
suma = do x <- sumInts
putStrLn("Suma este: " ++ show x )

G. Grigoras - FII Programare Funcional 31


Main> suma
0
Suma este: 0

Main> suma
354
24
100
0
Suma este: 478

G. Grigoras - FII Programare Funcional 32


Exemplul 3: Comanda Unix wc
Programul Unix wc (word count) citete un
ier i determin numrul de caractere,
cuvinte i linii pe care le aeaz
Ci=rea ierului este o aciune, restul este un
calcul.
Strategia:
Se denete o funcie care determin numrul de
caractere, cuvinte i linii ntr-un string.
Numrul de linii = numrul de \n
Numrul de cuvinte ~= numrul de plus numrul de \t
Se denete o aciune care citete un ier ntr-un string,
aplic funcia, apoi aeaz rezultatul.
G. Grigoras - FII Programare Funcional 33
Implementarea
wcf :: (Int,Int,Int) -> String -> (Int,Int,Int)
wcf (cc,w,lc) [] = (cc,w,lc)
wcf (cc,w,lc) (' ' : xs) = wcf (cc+1,w+1,lc) xs
wcf (cc,w,lc) ('\t' : xs) = wcf (cc+1,w+1,lc) xs
wcf (cc,w,lc) ('\n' : xs) = wcf (cc+1,w+1,lc+1) xs
wcf (cc,w,lc) (x : xs) = wcf (cc+1,w,lc) xs

wc :: IO ()
wc = do putStr Dati numele fisierului:
name <- getLine
contents <- readFile name
let (cc,w,lc) = wcf (0,0,0) contents
putStrLn (Fisierul: ++ name ++ are )
putStrLn (show cc ++ caractere )
putStrLn (show w ++ cuvinte )
putStrLn (show lc ++ linii )

G. Grigoras - FII Programare Funcional 34


Exemplu execuie
*Main> wc
Dati numele fisierului: wcount.hs
Fisierul: wcount.hs are
615 caractere
173 cuvinte
17 linii

G. Grigoras - FII Programare Funcional 35