Sunteți pe pagina 1din 44

Specificarea algebrică a tipurilor de date

Cursul prezintă o perspectivă algebrică a specificării tipurilor de date. Tipurile


astfel specifivcate sunt denumite "tipuri de date abstracte" sau, pe scurt, TDA [Turner
1994].

1. Un model algebric al TDA


Fie E o mulţime de simboluri. Notăm E+ mulţimea E+= {y∈E; w∈E+ • yw}; E+
este mulţimea tuturor şirurilor nevide ce se pot forma cu simboluri din E. Notăm E*=
{λ} ∪ E+, unde λ desemnează şirul vid, mulţimea tuturor şirurilor ce se pot forma cu
simboluri din E, inclusiv şirul vid.

Fie w ∈ E*, w = e1 e2...en un cuvânt (şir) din E*, unde ei, i∈1..n,
reprezintă o mulţime generică. Notăm ×w produsul cartezian ×w = e1 × e2 ×...× en.
Dacă Ai este o mulţime particulară (cu elemente precizate) asociată simbolului ei din
E, i=1,n, iar w∈ E+, notăm Aw = A1 × A2 ×...× An. Şirul λ are o semnificaţie
specială, fiind folosit în reprezentarea simbolurilor funcţionale nulare.

Definiţia 1 Fie S o mulţime de simboluri, numite sorturi, ce desemnează mulţimi


de valori neprecizate şi Σw,s = { σ:×w → s | w∈S* ∧ s∈S} o mulţime de simboluri
funcţionale care desemnează funcţii neprecizate din mulţimea aplicaţiilor ×w→ s.
Mulţimea ΣS ⊂ {w∈S*; s∈S • Σw,s} se numeşte signatură S-sortată.

Exemplul 1 Fie sorturile S= {stack, elm, bool}. Signatura ΣS = { Σelm


stack,stack, Σstack,bool, Σλ,stack, Σstack,elm, Σstack,stack} este S-sortată,
unde:
Σλ,stack = {newstack}, newstack∈ stack
Σelm stack,stack = {push}, push: elm × stack → stack
Σstack,stack = {pop}, pop: stack \ {newstack} → stack
Σstack,bool = {empty}, empty: stack → bool
Σstack,elm = {top}, top: stack \ {newstack} → elm

Definiţia 2 Fie ΣS o signatură S-sortată şi A= {s∈S • As} mulţimi asociate


sorturilor din S, iar δ = {δw,s: Σw,s→ (Aw → As) | w∈ S* ∧ s∈ S ∧ Σw,s∈ ΣS} o
mulţime de funcţii ce asociază simbolurilor funcţionale din ΣS funcţii Aw → As. Dacă
σ ∈ Σw,s atunci δw,s(σ) este o funcţie fσ: Aw → As. Dubletul (A, δ) poartă
numele de algebră de signatură ΣS (sau algebră ΣS-sortată), iar mulţimea A este
suportul algebrei.

Exemplul 2 Pentru signatura ΣS din exemplul 1 alegem:


Astack = {i∈int, v∈char vector • 〈i,v〉}
Aelm = char (mulţimea caracterelor)
Abool = {1, 0}

Fie variabilele c:char; i:int; v, v': char vector şi v0 o constantă de


tip vector de caractere. Putem construi funcţiile δ după cum urmează:

δλ,stack (newstack) = 〈0,v0〉


(δelm stack,stack (push)) (c,〈i,v〉) = 〈i+1,v'〉,
cu v'[j] = v[j], 1 ≤ j ≤ i şi v'[i+1] = c
(δstack,stack (pop)) 〈i,v〉 = 〈i-1,v〉, i ≥1
(δstack,elm(top)) 〈i,v〉 = v[i], i ≥1
(δstack,bool(empty)) 〈i,v〉 = (i = 0)
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 2

Definiţia 3 Fie (A, δ) şi (A', δ') algebre ΣS-sortate şi h= {hs: As → A's |


s∈ S} o mulţime de funcţii, astfel încât diagramele de mai jos comută. Spunem că h
este un morfism de algebre ΣS-sortate.

x ∈ As
hs w hw w
> x'∈ A's y∈ A > y'∈ A'
^ ^

δ ,s δ′λ ,s δ (σ) δ′ (σ)


λ w,s w,s

v v
σ ∈ Σ λ ,s x ∈ As > x'∈ A's
hs

s∈S s ∈ S, σ ∈ Σ w,s w = s1 s2...sn ∈ S+


hw (y) = hs1 (y1 ),hs2 (y ),...,hsn (yn ), yi ∈ Asi
2

Exemplul 3 Fie stk= (A,δ) algebra de signatură ΣS din exemplul 2, iar stk'=
(A',δ') algebra de signatură ΣS, definită după cum se arată mai jos, unde l∈A'stack
şi e∈ A'elm.

A'elm = int
A'stack = int list (liste cu elemente din A'elm, deci liste de întregi)
A'bool = {true, false}

δ'λ,stack(newstack) = [] (lista vidă)


(δ'elm stack,stack(push))(e,l) = e::l
(δ'stack,elm(top)) l = hd l, unde l ≠ []
(δ'stack,stack(pop)) l = tl l, unde l ≠ []
(δ'stack,bool(empty)) l = (l = [])

Notăm [e1; e2; ... ;en] lista cu elementele e1,e2,...,en şi construim


morfismul h în felul următor:

hstack 〈0,v0〉 = []
hstack 〈n,v〉 = [ord v[n]; ord v[n-1]; ... ; ord v[1]]
helm (c) = ord c
hbool(1) = true, iar hbool(0) = false

unde v, v0∈ char vector, n∈int, c∈char, iar ord: char → int este o funcţie de
conversie a caracterelor în întregi. Se poate verifica uşor că h este un morfism de la
(A, δ) la (A', δ'). De exemplu, pentru operatorii newstack şi push diagramele de
mai jos comută.

elm stack
hstack h
<0,v0> >[] c,<n,v> > e, [ e ;...;e n ]
1
^ ^

δ δ′λ,stack δ (push) δ′elm stack,stack (push)


λ ,stack elm stack,stack

v v
newstack <n+1,v'> > [e; e 1 ;...;en ]
hstack
v[k] = v'[k], k=1,n e = ord(c)
v'[n+1] = c e k = ord v[n-k+1], k=1,n
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 3

Definiţia 4 Fie A = {s∈S • As} o mulţime suport corespunzătoare sorturilor S


şi ΣS o signatură S-sortată. Se numeşte algebră liber generată ΣS-sortată de suport A,
algebra (A, δ) construită ca mai jos, unde Σw,s∈ ΣS şi Σ λ,s ∈ ΣS.

• Suportul A
1) As ⊆ As, s∈S
2) σ ∈ As, pentru σ∈Σ λ,s, s∈S
3) σ(x1, x2,..., xn) ∈ As,
pentru σ ∈ Σw,s, s∈S, w∈S+, x1, x2,..., xn ∈ Aw

• Funcţiile δ
1) δ λ,s(σ) = σ, pentru σ∈Σ λ,s
2) δw,s(σ) (x1, x2,..., xn) = σ(x1,x2,...,xn),
pentru σ ∈ Σw,s, s∈S, w∈S+, x1, x2,..., xn ∈ Aw

Prin urmare, δ construieşte reprezentări simbolice ale valorilor caracteristice


diverselor sorturi din S.

Exemplul 4 Fie A mulţimea definită în exemplul 2 pentru stivă. Suportul


algebrei liber generate corespunzătoare stivei este:

Astack= {i∈int; v∈ char vector • 〈i,v〉} ∪ A'stack, unde în A'stack


se află valori simbolice de forma pop(push(c,push(d,newstack))), newstack, cu
c,d ∈ char.

Aelm= char ∪ A'elm, unde în A'elm se află valori simbolice cu structura


top(push(c,newstack)), top(pop(push(c,push(d,newstack)))) etc.

Abool= {1,0} ∪ A'bool, unde în A'bool sunt formule de tipul


empty(newstack), empty(push(c,newstack)) etc.

După cum se remarcă, algebra liber generată (A, δ) a stivei reprezintă o bază
pentru descrierea abstractă a valorilor de tip stivă sau a valorilor derivate din
prelucrarea unei stive.

Teorema 1 Fie (A, δ) algebra liber generată de signatură ΣS şi suport A.


Pentru orice algebră (Q, δ) ΣS-sortată şi orice aplicaţii gs: As → Qs, s∈S există şi
este unic un morfism hs: As → Qs, s∈S, astfel încât diagrama de mai jos, unde id
este funcţia identitate, comută pentru orice s∈S.

id
As > As

gs v v hs

Qs

Teorema subliniază universalitatea algebrelor liber generate. Demonstraţia este


similară cu cea din [Stănăşilă 1978].
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 4

1. Se demonstrează existenţa morfismului h prin construcţie.


1.1. Pentru x ∈ As avem, cf. definiţiei 4, x ∈ As şi impunem hs(x)= gs(x)
1.2. Pentru x∈Σ λ,s avem, cf. definiţiei 4, x∈As şi rezultă hs(x)= δλ,s(x)
1.3. Pentru x ∈ As, cu x = σ(x1, x2,..., xn), σ ∈ Σw,s,
w = s1 s2 ... sn şi x1, x2,..., xn ∈ Aw, impunem
hs(x) = δw,s( σ)(hs1(x1),hs2(x2),...,hsn(xn))

Cu această construcţie a lui h se poate verifica uşor comutativitatea


diagramelor din definiţia 3, diagrame redesenate mai jos. De asemenea, este
asigurată şi comutativitatea diagramei din teoremă.

h (σ)=δ λ ,s (σ)
σ ∈ As s δλ,s(σ) ∈ Qs diagrama (a)
>
^ ^

s∈S δ λ,s δ λ,s

σ ∈ Σ λ ,s

s ∈ S, σ ∈ Σ w,s w = s1 s2...sn ∈ S,+ x ∈ A diagrama (b)


i si

w hw w
(x , x ,..., x n) ∈ A > hs1 (x1 ),hs2 (x2 ),..., hsn (xn ) ∈ Q
1 2

δ (σ) δw,s(σ)
w,s
v v
σ (x ,x ,..., x n) ∈ A s > δ w,s(σ)( hs1 (x1 ),hs2 (x2 ),..., hsn (xn )) ∈ Qs
1 2 hs

2. Se demonstrează unicitatea morfismului h. Să presupunem ca există h'


diferit de h. Atunci, pentru orice valoare σ∈Σ λ,s, trebuie să avem h'(σ) = δλ,s( σ)
pentru ca diagrama (a) să comute. Pentru o valoare v∈As, s∈S, trebuie să avem
h's(v) = g(v) pentru ca diagrama din teoremă să comute. În sfârşit, pentru v ∈ As,
v = σ(x1, x2, ..., xn), cu σ∈Σw,s, w∈S*, trebuie să avem h's(v) =
δλ,s( σ)(h'(x1),h'(x2),...h'(xn)) pentru ca diagrama (b) să comute. Deci h's
este chiar h.  

Exemplul 5 Fie algebra (Q,δ) = stk' = (A',δ') definită în exemplul 3 şi


(A, δ) algebra liber generată cu signatura S din exemplul 4. Funcţiile h sunt construite
conform teoremei 1, cu g având forma:

gstack 〈i,v〉 = [ord(v[i]; ... ; ord(v[1]] ∈ int list


gchar(c) = ord(c) ∈ int
gbool(0) = false gbool(1) = true

h este un morfism de la (A, δ) la (Q, δ), iar implementarea stivei sub formă
de listă este un caz particular de reprezentare a tipului stivă bazată pe utilizarea
dubleţilor 〈 int, char vector〉.

Definiţia 5 Fie Atr o mulţime de sorturi, reprezentând mulţimile de valori ale


atributelor (proprietăţilor) caracteristice obiectelor Ob ale unui tip t. Fie Op signatura
corespunzătoare sorturilor S = Atr ∪ {t}, conform semnificaţiei lui t, t fiind numit
sort principal în S. De asemenea, fie A = {a∈Atr • Aa} ∪ {At}, cu At = ∅, un
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 5

suport al lui S. Algebra liber generată de signatură Op şi suport A, notată (A, δ)t, se
numeşte algebra iniţială a tipului t.

De observat că mulţimea At, care precizează valorile tipului t, nu este


particularizată. Obiectele Ob au exclusiv reprezentare simbolică în At, ca formule
formate cu elemente din Opw,t (constructori ai lui t) şi valori din As, cu w∈S+, s∈S.

Deoarece At reprezintă mulţimea tuturor valorilor tipului abstract t, vom


prefera notaţia t în loc de At. Astfel, notaţiile x∈t şi x:t sunt echivalente cu x∈At.

Corolar 1 Algebra iniţială (A, δ)t este cea mai generală algebră din clasa
algebrelor de signatură ΣS şi suport A = {a∈Atr • Aa} ∪ {At}, pentru orice
particularizare a mulţimii At. Orice asemenea algebră poate fi privită ca un caz
particular al algebrei iniţiale (A, δ)t.

Demonstraţia rezultă din teorema (1) cu particularizările gs=id, pentru s∈Atr,


ht(σ) = δλ,t(σ), δλ,t: Σλ,t→ At, pentru σ∈Σλ,t. Există un morfism de la (A, δ)t
la orice algebră particulară de signatură ΣS şi suport A = {a∈Atr • Aa} ∪ {At}.  

Definiţia 6 Fie (A, δ)t algebra iniţială a unui tip t de obiecte şi E o mulţime de
relaţii de echivalenţă (reflexive, simetrice şi tranzitive) peste A, relaţii numite axiome
ale tipului t, astfel încât valorile simbolice ale (A, δ)t ce fac parte dintr-o clasă de
echivalenţă sub E au aceeaşi semnificaţie din punctul de vedere al tipului t. Algebra
cât (A, δ)t/E este tipul de dată abstractă t.

Valorile din As/E sunt clase de echivalenţă cu valori din As ce au aceeaşi


semnificaţie. Operatorii din Op au ca domeniu si codomeniu mulţimi formate cu
asemenea valori.

1.1 Prezentarea unui TDA

Definiţia 7 Prin prezentare a unui TDA t se înţelege tuplul t = 〈Atr, Op, E,


R〉, unde Atr, Op, E au semnificaţiile stabilite în definiţiile 5 şi 6, iar R este o mulţime de
restricţii respectate de operatorii tipului, restricţii care reflectă parţialitatea operatorilor.
Convenim să descriem prezentarea unui TDA t printr-o schemă cu structura:

t::= <forme canonice ale valorilor lui t >

<signatura Op>

<relaţiile E şi restricţiile R>

Formele canonice ale valorilor tipului precizează constructorii folosiţi pentru a


reprezenta în formă simbolică valorile lui t. De exemplu pentru tipul stack, formele
canonice ale valorilor sunt: newstack (care simbolizează stiva vidă) şi push(e,s), cu
e:elm şi s:stack, care arată cum se construieşte şi, implicit, cum se reprezintă
simbolic o stivă nevidă. Declaraţia formelor canonice ale valorilor este oarecum
asemănătoare celei din Caml sau Haskell: stack::= newstack | push 〈〈elm
× stack〉〉. Operatorii prezentării, diferiţi de constructorii formelor canonice, sunt
specificaţi în <signatura Op>, iar sorturile prezentării derivă implicit din această
signatură şi din specificarea formelor canonice.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 6

Relaţiile E (axiomele tipului t) sunt precizate printr-o mulţime de ecuaţii cu


structura (a) x = v sau (b) x = if vp then v1 else v2, cu v1, v2 de forma
membrului drept din (a) sau (b), unde x, v şi vp sunt:

1) valori din As, s∈Atr, aparţinând tipurilor externe lui t, valori care nu conţin
operatori ai lui t. Aceste valori sunt denumite valori externe lui t şi pot fi constante şi
variabile din As, precum şi valori simbolice formate cu constante, variabile şi operatori
caracteristici tipurilor externe desemnate de sorturile Atr.

2) valori simbolice din As, s∈ Atr ∪ {t}, formate cu operatori din Op, valori
externe tipului t, în care pot să apară operatori ai lui t şi variabile cu valori în As.

3) expresii logice construite cu valori de forma (1) şi (2). Operatorii expresiilor


logice precum şi if...then...else sunt consideraţi operatori ai unui tip asociat
implicit prezentării tipului t. Rolul acestui tip este de a ajuta descrierea lui t.

Notaţia x = y are semnificaţia: valoarea simbolică desemnată de x este


echivalentă structural cu valoarea simbolică a lui y. Operatorul = nu trebuie
confundat cu operatorul de comparaţie propriu unui anumit tip de date, el aparţinând
notaţiei de specificare a tipurilor. De asemenea, notaţia x = if vp then v1 else v2
este o alternativă mai expresivă pentru vp ⇒ (x = v1) ∧ ¬vp ⇒ (x = v2).

Prin convenţie, restricţiile R sunt specificate prin implicaţii x ⇒ y, unde x şi y


sunt expresii logice formate cu valori din As, s ∈ Atr ∪ {t}. Uneori, o restricţie poate
încorpora o axiomă, limitând domeniul operatorilor din axiomă.

Exemplul 6 Fie prezentarea tipului stack, stivă cu elemente de tip elm,


descrisă astfel:

stack::= newstack | push 〈〈elm × stack〉〉

pop: stack \ {newstack} → stack --- eliminare element vârf stivă


top: stack \ {newstack} → elm --- element vârf stivă
empty: stack → bool --- test stivă vidă

pop(push(e,stk)) = stk
top(push(e,stk)) = e
empty(newstack) ∧ ¬empty(push(e,stk))

Pentru o scriere mai comprimată şi mai comodă, în prezentare s-au eliminat


cuantificatorii universali, considerând că toate variabilele sunt cuantificate universal.
Astfel, axioma ∀stk: stack; e:elm • pop(push(e,stk)) = stk a fost scrisă
prescurtat pop(push(e,stk)) = stk. În acest caz, tipul variabilelor din axiome
derivă, implicit, din signaturile operatorilor.

Se observă că orice valoare de tipul stack poate fi descrisă folosind doar doi
operatori: newstack şi push. Orice stivă obţinută folosind pop poate fi reconvertită
conform axiomelor la o stivă formată cu newstack şi push. De exemplu, push(1,
pop(push(2, newstack))) = push(1, newstack). Din acest motiv, newstack şi
push sunt denumiţi constructori de bază, în timp ce pop este un constructor auxiliar.
Valorile construite doar cu constructori de bază sunt numite valori canonice.
Operatorul top este de selecţie, deoarece reîntoarce un element component al unei
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 7

stive, iar empty este un operator de caracterizare, servind la precizarea unei


proprietăţi a stivei.

Stilul de prezentare de mai sus nu este unicul posibil, uneori preferându-se o


notaţie mai apropiată de forma unui program [Turner 1994]. Aici, s-a dorit o cât mai
mare apropiere de stilul folosit pentru specificarea tipurilor libere (free types) din Z
[Diller 1994] şi, totodată, faţă de modul de definire a tipurilor în limbajele de
programare funcţională.

Relativ la stilul de prezentare a unui TDA, trebuie spus că declaraţia formelor


canonice ale valorilor unui tip t, anume t::= <forme canonice ale valorilor
lui t >, este o simplă convenţie sintactică pentru a arăta că unicele valori esenţiale
ale mulţimii t sunt cele canonice. O alternativă interesantă de specificare a formelor
canonice, care ar elimina artificiul sintactic, este indicată în [Diller 1994]. De exemplu,
pentru stack, se consideră specificaţia:

newstack: stack
push: elm × stack → stack

∀U:Ρ stack; s:stack; e:elm • {newstack, push(e,s)}⊆ U ⇒ stack ⊆ U

unde Ρ S desemnează mulţimea putere a mulţimii S (mulţimea tuturor mulţimilor ce se


pot forma cu elemente din S). Specificaţia arată că mulţimea stack este compusă
exclusiv din valori de forma newstack şi push(e,s), cu e∈elm şi s∈stack. Într-
adevăr, să definim mulţimea stk = {newstack} ∪ {s:stack; e:elm • push(e,s)},
stk∈ Ρ stack, şi să presupunem că există o valoare v∈stack astfel încât v∉stk.
Atunci putem alege U=stk, iar restricţia stack⊆U nu este îndeplinită, ceea ce este
imposibil. Deci, v nu poate aparţine mulţimii stack.

1.2 Tipuri parametrizate

Prezentarea tipului stack s-a bazat pe ipoteza că elm este o constantă


reprezentând un tip fixat. Dacă elm desemnează un tip generic, deci este similar unei
variabile de tip, atunci prezentarea tipului stack trebuie să expliciteze parametrizarea
respectivă. În caz contrar, notaţia devine inadecvată specificării unor tipuri compuse
din tipuri parametrizate. De exemplu, pentru un tip Struct ale cărui valori sunt stive şi
liste, ar fi incomod de specificat restricţia ca elementele stivelor şi listelor să fie de
acelaşi tip. Convenim ca forma generală a unei prezentări parametrizate de tip să fie
ca mai jos, cu menţiunea că notaţia nu este standard Z:

t(<sort1 Atr>, ... , <sortn Atr>)::= <forme canonice ale valorilor lui t>

<signatura Op>

<relaţiile E şi restricţiile R>

unde sorturile <sorti Atr>, i = 1,n, desemnează tipuri neprecizate. În schimb, se


consideră că orice operator de prelucrare a valorilor de tip <sorti Atr>, folosit în
<relaţiile E şi restricţiile R>, trebuie să fie definit pentru orice particularizare
posibilă a sortului respectiv.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 8

Distanţa de la prezentarea unui TDA t la implementarea simbolică a tipului t


într-un limbaj de programare funcţională, de exemplu în Caml, este neînsemnată.
Constructorii de bază sunt folosiţi în declaraţia de tip corespunzătoare tipului t, iar
axiomele sunt rescrise ca funcţii ce reprezintă operatorii tipului. Dacă, strict formal, un
operator nu va fi niciodată folosit într-un punct al domeniului său, punct în care nu
este definit, transformarea prezentării TDA într-un tip practic şi robust trebuie să ţină
seama că în afară de leme, teoreme şi axiome mai există şi utilizatori supuşi
greşelilor. Din această perspectivă, restricţiile prezentării vor completa definiţiile
operatorilor. Ca exemplu, prezentarea TDA stack(elm) corespunde următorului
program Caml:

type 'elm stack = newstack | push of 'elm * 'elm stack;;


Type stack defined.

exception emptystack;;
Exception emptystack defined.

let pop = fun newstack -> raise emptystack | (push(e,stk)) -> stk
and top = fun newstack -> raise emptystack | (push(e,stk)) -> e
and empty = fun newstack -> true | _ -> false
;;
pop: 'elm stack -> 'elm stack = <fun>
top: 'elm stack -> 'elm = <fun>
empty: 'elm stack -> bool = <fun>

Ca alternativă a implementării deschise a tipului stack, putem încapsula


implementarea într-un modul obiect, păstrând vizibilă doar signatura tipului. În această
variantă, programele care folosesc tipul stack devin total independente în raport cu
modul de operaţionalizare a axiomelor şi cu reprezentarea particulară a valorilor
tipului. Utilizatorul lucrează cu un tip într-adevăr abstract. În Caml se poate scrie:

stack.mli (signatura stack)

type 'elm stack;;


exception emptystack;;

value newstack: unit -> 'elm stack


and push: 'elm * 'elm stack -> 'elm stack
and pop: 'elm stack -> 'elm stack
and top: 'elm stack -> 'elm
and empty:'elm stack -> bool;;

stack.ml (operaţionalizarea axiomelor)

type 'elm stack = newstackk


| pushh of 'elm * 'elm stack;;

let pop = fun newstackk -> raise emptystack | (pushh(e,stk)) -> stk
and top = fun newstackk -> raise emptystack | (pushh(e,stk)) -> e
and empty = fun newstackk -> true | _ -> false

and push(e,stk) = pushh(e,stk)


and newstack() = newstackk;;

Constructorii de bază ai unui tip descriu valorile tipului fie prin istoria devenirii
acestora, fie prin forma lor instantanee. Axiomele prezentării tipului vor reflecta
punctul de vedere adoptat. Notăm:
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 9

• Conb mulţimea constructorilor de bază, din componenţa formelor canonice


ale valorilor tipului;

• Cona mulţimea constructorilor auxiliari, care nu apar în formele canonice ale


valorilor tipului, fiind convertibili la constructorii de bază;

• Ext mulţimea operatorilor externi tipului, care produc valori externe tipului, cu
Ext = Sel ∪ Car, unde Sel este mulţimea selectorilor, iar Car mulţimea operatorilor
de caracterizare.

Exemplul 7 Se consideră prezentarea tipului parametrizat set (mulţime) în


două variante: (a) cu constructori de bază reflectând istoria generării unui set, (b) cu
constructori care precizează starea instantanee a unui set. În prezentarea de mai jos
Conb= {newset, insset}, Cona= {delset}, Ext = {emptyset, cardset, inset},
Sel = ∅, Car = Ext.

set(elm)::= newset | insset 〈〈elm × set〉〉 (varianta a)

delset: elm × set → set --- eliminare element din mulţime


inset: elm × set → bool --- test apartenenţă element la mulţime
cardset: set → int --- numărul elementelor din mulţime
emptyset: set → bool --- test mulţime vidă

(1) emptyset(newset) = true


(2) emptyset(insset(e,s)) = false
(3) cardset(newset)= 0
(4) cardset(insset(e,s)) = if inset(e,s) then cardset(s)
else 1+cardset(s)
(5) inset(e,newset) = false
(6) inset(e,insset(x,s)) = if (e = x) then true else inset(e,s)
(7) delset(e,newset) = newset
(8) delset(e,insset(x,s)) = if (e = x) then delset(e,s)
else insset(x,delset(e,s))

De notat că insset descrie istoria formării unei mulţimi prin inserări succesive
de elemente şi nu asigură unicitatea apariţiei elementelor în reprezentarea simbolică a
mulţimii construite. Totuşi, comportarea corectă a operatorilor delset, cardset şi
inset este garantată prin axiome, care ţin seamă de natura lui insset. Într-adevăr, fie
mulţimea s = insset(a,insset(a,newset)). Atunci, delset(a,s) elimină, conform
axiomei (8), toate apariţiile lui a în s şi produce newset, iar cardset(s) este 1,
ambele rezultate fiind corecte.

În varianta (b) constructorii de bază sunt newset şi bodyset, în timp ce insset


şi delset sunt constructori auxiliari. Spre deosebire de varianta (a), aici bodyset
descrie starea instantanee a unui set şi, în consecinţă, asigură unicitatea apariţiei
elementelor în cadrul reprezentării simbolice a mulţimii, conform restricţiei (11), de fapt
o condiţie de formulă 'bine formată' a valorilor de tip set în raport cu semnificaţia
tipului. De exemplu, s = insset(a,insset(a,newset)) este mulţimea bodyset(a,
newset).
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 10

set(elm)::= newset | bodyset 〈〈elm × set〉〉 (varianta b)

insset: elm× set → set --- inserare element în mulţime


delset: elm × set → set --- eliminare element din mulţime
inset: elm × set → bool --- test apartenenţă element la mulţime
cardset: set → int --- numărul elementelor din mulţime
emptyset: set → bool --- test mulţime vidă

(1) emptyset(newset) = true


(2) emptyset(bodyset(e,s)) = false
(3) cardset(newset)= 0
(4) cardset(bodyset(e,s)) = 1+ cardset(s)
(5) inset(e,newset) = false
(6) inset(e,bodyset(x,s)) = if (e = x) then true else inset(e,s)
(7) delset(e,newset) = newset
(8) delset(e,bodyset(x,s)) = if (e = x) then s
else bodyset(x,delset(e,s))
(9) insset(e,newset) = bodyset(e,newset)
(10) insset(e,bodyset(x,s)) = if (e = x) then s
else bodyset(x,insset(e,s))
(11) ∀s,r: set; e:elm • (r = bodyset(e,s)) ⇒ ¬ inset(e,s)

Preferinţa pentru una sau cealaltă categorie de constructori de bază este


discutabilă. Singurul argument în favoarea constructorilor de bază care reflectă starea
instantanee a unei valori este acela al apropierii de implementare şi, posibil, forma
mai simplă a axiomelor. Modulele Caml de mai jos reprezintă operaţionalizarea
variantei (b) de prezentare a TDA set.

set.ml

type 'elm set = newSet | bodyset of 'elm * 'elm set;;

let rec
newset() = newSet
and insset = fun (e,newSet) -> bodyset(e,newSet)
| (e,bodyset(x,s)) -> if e = x then bodyset(e,s)
else bodyset(x,insset(e,s))
and delset = fun (e,newSet) -> newSet
| (e,bodyset(x,s)) -> if e = x then s
else bodyset(x,delset(e,s))
and inset = fun (e,newSet) -> false
| (e,bodyset(x,s)) -> if e = x then true else inset(e,s)
and cardset = fun newSet -> 0
| (bodyset(_,s)) -> 1 + cardset(s)
and emptyset = fun newSet -> true | _ -> false ;;

set.mli

type 'elm set;;


value newset: unit -> 'elm set
and insset: 'elm * 'elm set -> 'elm set
and delset: 'elm * 'elm set -> 'elm set
and inset: 'elm * 'elm set -> bool
and cardset: 'elm set -> int
and emptyset:'elm set -> bool;;
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 11

2 Corectitudinea TDA
Problema corectitudinii se pune atât în momentul construcţiei unui TDA (mai
precis, în faza de sinteză a axiomelor prezentării tipului), cât şi pentru verificarea unui
TDA deja construit.

Definiţia 8 Un TDA t este corect dacă:

a) t este complet. Pentru orice valori x,y∈As, s∈ Atr ∪ {t}, egalitatea x=y
poate fi probată pe baza axiomelor E şi a axiomelor tipurilor externe Atr.

b) t este consistent. Pentru orice valoare simbolică x∈As, s∈Atr şi orice


secvenţe de reducere x ⎯ r1 → v1 ∈ As şi x ⎯ r2 → v2 ∈ As, obţinute
aplicând în ordine diferită axiome din E, precum şi axiomele tipurilor externe, are loc
egalitatea v1 = v2, unde v1 şi v2 sunt valori externe tipului t. Rezultatul reducerii
este unic şi este o valoare externă lui t.

Pentru exemplificare, să considerăm varianta (a) de prezentare a TDA set,


unde modificăm axioma (8) şi, în plus, adăugăm axioma (9), care reprezintă
proprietatea conform căreia un element eliminat dintr-o mulţime nu mai aparţine
mulţimii.

(8') delset(e,insset(x,s)) = if egal(e,x) then s


else insset(x,delset(e,s))
(9) inset(e,delset(x,s)) = if egal(e,x) then false else inset(e,s)

Noul TDA, notat setn, este incorect, fiind inconsistent. Într-adevăr, valoarea
v = inset(e,delset(e,s)) din Abool, cu s = insset(e, insset(x, insset(e,
newset))), se poate reduce în mod diferit:

v ⎯ axioma 9 → false
v ⎯ axioma 8' → inset(e, insset(x, insset(e, newset)))
⎯ axioma 6 → inset(e,insset(e,newset))
⎯ axioma 6 → true

În afară de inconsistenţa lui setn, tipurile set şi stack definite mai sus sunt
incomplete. De exemplu, echivalenţa, aparent evidentă,

insset(e, insset(x, newset)) = insset(x, insset(e, newset))

nu poate fi demonstrată folosind axiomele tipului set. În general, problema


completitudinii nu este decidabilă. Ce cale ce poate fi urmată pentru a controla
corectitudinea unui TDA constă în limitarea noţiunii de corectitudine la corectitudine
parţială. Justificarea acestei opţiuni are la bază perspectiva practică a unui TDA. De
obicei, specificările abstracte definesc date folosite în rezolvarea unor probleme unde
prelucrările sunt limitate ca varietate. Din acest punct de vedere, un TDA este parţial
corect dacă respectă proprietăţile limitate impuse de utilizare. De exemplu,
proprietăţile P1(s) şi P2(s), pentru ∀s∈ set(elm), pot fi considerate esenţiale pentru
TDA set.

P1(s) =def ∀e:elm • ¬ inset(e, delset(e,s))


P2(s) =def (s = newset) ∨ (∃e:elm; r:set • s = bodyset(e,r))
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 12

Proprietatea P1 arată că un element nu mai este conţinut în mulţimea din care


a fost eliminat, iar P2 arată că un set poate fi reprezentat exclusiv folosind
constructorii newset şi bodyset.

Stabilirea proprietăţilor definiţiei unui tip poate fi influenţată nu numai de


utilizarea tipului, dar chiar de modul său de specificare. De exemplu, în cazul variantei
(b) de prezentare a tipului set, una din proprietăţile importante legate direct de
specificare este: orice element membru al unui set are o unică apariţie în formula
care reprezintă mulţimea. Formal, pentru ∀s ∈ set(elm), proprietatea este:
P3(s) =def ∃r:set; e:elm • (s = bodyset(e,r)) ⇒ ¬ inset(e,r)

Deşi P3 este asemănătoare ca formulare cu restricţia (11), din prezentarea


tipului, ea nu trebuie confundată cu restricţia, care se referă doar la aplicarea
operatorului bodyset. Într-adevăr, fără această restricţie, ar putea exista valori
canonice eronate, ca, bunăoară, bodyset(e,bodyset(e,newset)). Proprietatea P3
este, însă, generală şi acoperă orice mulţime obţinută prin orice constructor, de bază
sau auxiliar, al tipului set. Mai mult, restricţia este asertată - considerată ca
adevărată - în timp ce proprietatea trebuie demonstrată. Confirmarea proprietăţii
validează prezentarea tipului set, din punctul de vedere al restricţiei (11). Falsitatea
proprietăţii arată că prezentarea tipului este incorectă în ceea ce priveşte restricţia.
Astfel, dacă axioma (10) ar fi insset(e,bodyset(x,s)) =
bodyset(x,insset(e,s)), atunci este lesne de văzut că proprietatea P3 nu este
îndeplinită şi, deci, prezentarea tipului set nu ar respecta restricţia (11).

Definiţia 9 O prezentare t = 〈Atr, Op, E, R〉 a unui TDA este suficient


completă şi consistentă - prin definiţie parţial corectă - dacă pentru o mulţime Prop de
proprietăţi date este îndeplinită condiţia: ∀p∈Prop; x∈t • p(x). Deci orice
proprietate din Prop este decidabilă folosind axiomele E şi este validă pentru orice
valoare simbolică a tipului t.

2.1 Verificarea corectitudinii prin inducţie structurală

O metodă de verificare a corectitudinii parţiale este inducţia structurală, o


particularizare a inducţiei bine formate. Fie P o proprietate a tipului t cu sorturile S =
Atr ∪ {t}. Proprietatea este validă pentru ∀x ∈ t dacă:

a) orice constructor de bază nular σ ∈ Opλ,t satisface P;

b) pentru orice constructor de bază σ ∈ Opw,t cu w ∈ Atr+, valoarea


σ(v1,...,vn,), unde vi, i = 1,n sunt valori externe tipului, satisface P. Cu alte
cuvinte, P este satisfăcută de orice valoare a lui t, construită exclusiv cu valori
externe tipului;

c) pentru orice constructor de bază σ ∈ Opw,t cu w ∈ S+, astfel încât t apare


în w de n ori, valoarea y = σ(...,xi,...) satisface proprietatea P, unde xi∈t
satisface P pentru i = 1,n. Deci P este satisfăcută de valorile canonice ale lui t,
valori formate recursiv cu valori ale lui t, corecte în raport cu P.

Inducţia structurală verifică dacă mulţimea de adevăr a proprietăţii P este


închisă în raport cu constructorii de bază ai tipului t. Pentru că valorile din At pot fi
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 13

formate doar cu constructorii de bază, constructorii auxiliari fiind eliminaţi pe baza


axiomelor, rezultă că orice valoare simbolică a tipului face parte din mulţimea de
adevăr a lui P, dacă P este validă.

Inducţia structurală poate fi descrisă şi prin intermediul schemei de mai jos,


care pune în evidenţă elementele principale: cazurile de bază, pasul inducţiei şi
ipoteza inductivă.

cazuri de bază ipoteză inductivă


| | pas inducţie
∀σ: Opλ,t • P(σ) [ xi ∈ t • P(xi) ] /
∀σ: Opw,t • P(σ(v1,..,vn)) ___________________ ∀σ∈ Opw,t ∩ Conb,
w ∈ Atr+, (v1,..,vn) ∈Aw P(σ(...,xi,...)) t este în w
____________________________________________________________________
∀x: t • P(x)

Exemplul 8 Pentru varianta (b) a prezentării TDA set să verificăm prin inducţie
structurală proprietatea:
P1(s) =def ∀e: elm • ¬ inset(e, delset(e,s))

Trebuie să arătăm că P1(s) este satisfăcută pentru o mulţime s construită cu


newset şi bodyset. Totodată, o să considerăm că proprietatea P3(s), din secţiunea 2,
este adevărată pentru orice set s nevid.

1) Pentru σ = newset avem inset(e,delset(e,newset))


⎯ axioma 7→ inset(e,newset)
⎯ axioma 5 → false

2) Pentru σ∈bodyset. Să arătăm că dacă P1(s) este verificată, atunci ea


rămâne adevărată şi pentru bodyset(x,s).

2.1) Dacă e = x atunci inset(e,delset(e,bodyset(x,s)))


⎯ axioma 8 → inset(e,s).

Conform P3(bodyset(e,s)) rezultă imediat inset(e,s) = false.

2.2) Dacă e ≠ x atunci inset(e, delset(e, bodyset(x,s)))


⎯ axioma 8 → inset(e, bodyset(x,delset(e,s)))
⎯ axioma 6 → inset(e,delset(e,s)),

şi, deci, P1(bodyset(x,s)) = P1(s) care este satisfăcută prin ipoteză.

Să considerăm aceeaşi prezentare, în varianta (b), corespunzătoare TDA set,


pentru a proba proprietatea:

P2(s) =def (s = newset) ∨ (∃e:elm; r:set • s = bodyset(e,r))

În acest scop, să construim şi să verificăm prin inducţie structurală proprietăţile


de mai jos, potrivit cărora constructorii auxiliari pot fi eliminaţi din valorile simbolice ale
tipului set.
P2'(s) =def ∀e: elm • P2(s) ⇒ P2(delset(e,s))
P2"(s) =def ∀e: elm • P2(s) ⇒ P2(insset(e,s))
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 14

1) Pentru σ = newset, P2'(σ) şi P2"(σ) sunt verificate conform reducerilor


de mai jos.

delset(e, newset) ⎯ axioma 7 → newset, şi P2' devine P2(newset) ⇒


P2(newset)

insset(e, newset) ⎯ axioma 9 → bodyset(e, newset), şi P2" devine


P2(newset) ⇒ P2(bodyset(e,newset))

2) Fie σ = bodyset. Să considerăm că P2'(s) este satisfăcută şi, deci, că


P2(delset(e,s)) este satisfăcută dacă P2(s) este satisfăcută. Trebuie să arătăm că
proprietatea P2' este verificată pentru bodyset(x,s), x: elm. Cu ipoteza P2(s) =
true avem,

P2'(bodyset(x,s))=(P2(bodyset(x,s))⇒
P2(delset(e, bodyset(x,s)))) = P2(delset(e, bodyset(x,s)))

2.1 Dacă e = x atunci


delset(e, bodyset(x,s)) ⎯ axioma 8 → s, şi
P2'(bodyset(x,s)) = P2(s) = true

2.2 Dacă e ≠ x atunci


delset(e, bodyset(x,s))
⎯ axioma 8 → bodyset(x, delset(e,s)).

Rezultă, P2'(bodyset(x,s))=P2(bodyset(x, delset(e,s))). Dar, conform


ipotezei, P2(delset(e,s))=true şi, conform proprietăţii P2, P2(bodyset(x,
delset(e,s))) = true, deci P2'( bodyset(x,s)) = true.

3) Fie σ = bodyset. Să considerăm că P2"(s) este satisfăcută şi, deci, că


P2(insset(e, s)) este satisfăcută dacă P2(s) este satisfăcută. Trebuie să arătăm
că proprietatea P2" este verificată pentru bodyset(x,s). Cu ipoteza P2(s) = true,

P2"( bodyset(x,s)) = (P2(bodyset(x,s)) ⇒


P2(insset(e, bodyset(x,s)))) = P2(insset(x, bodyset(x,s)))

3.1 Dacă e = x atunci


insset(e, bodyset(x,s)) ⎯ axioma 10 → s, şi
P2"( bodyset(x,s)) = P2(s) = true

3.2 Dacă e ≠ x atunci


insset(e, bodyset(x,s)) ⎯ axioma 10 →
bodyset(x, insset(e,s)).

Rezultă, P2"(bodyset(x,s))= P2(bodyset(x, insset(e,s))). Dar, conform


ipotezei, P2(insset(e,s)) = true şi conform proprietăţii P2, P2(bodyset(x,
insset(e,s))) = true, deci P2"( bodyset(x,s)) = true.

Proprietatea P2 pentru TDA set este, ca natură, universală în sensul că pentru


orice prezentare a unui TDA t trebuie dovedit că o valoare de tip t poate fi clădită,
conform prezentării, folosind exclusiv constructorii de bază ai tipului. În virtutea
proprietăţii universale, nu mai este necesară verificarea unei proprietăţi oarecare P din
punctul de vedere al constructorilor auxiliari, ci - conform inducţiei structurale - doar
demonstrarea faptului că pentru o valoare formată cu constructorii de bază ai tipului,
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 15

valoare numită canonică, proprietatea P este validă. Proprietatea universală justifică


inducţia structurală.

2.2 Inducţia structurală versus inducţia bine formată

Inducţia structurală a folosit, indirect, o proprietate remarcabilă a valorilor


canonice ale unui tip t: lungimea valorilor văzută ca numărul apariţiilor constructorilor
de bază în cadrul formulei ce reprezintă valoarea. De exemplu lungimea valorii
bodyset(1,newset) are lungime 2. Dacă definim lg: t → Ν, cu Ν mulţimea
numerelor naturale, funcţia care calculează lungimea valorilor canonice ale lui t,
atunci se observă că pasul de inducţie:

[ xi ∈ t • P(xi) ]
________________________________ ∀σ ∈ Opw,t ∩ Conb,
P(σ(...,x1, ...,xi,...,xn,...)) w ∈ S+, t este în w

al inducţiei structurale foloseşte în ipoteza inductivă valori xi ∈ t cu lg(xi)


< lg(σ(...,x1, ...,xn,...)). Într-un fel, inducţia structurală este în raport cu
numerele naturale, care reprezintă o măsură a taliei valorilor canonice ale tipului t.
Această observaţie sugerează că inducţia structurală este o particularizare a unei
scheme de inducţie generale, numită inducţie bine formată.

Fie p o relaţie de ordine definită peste o mulţime X, astfel încât orice şir ...p xn
p...p x2 p x1 este finit. Se spune că relaţia p este bine formată. De exemplu,
relaţia < definită peste mulţimea numerelor naturale este bine formată. Pentru orice
număr natural x1 şirul ...< xn < ...< x1 este evident finit. În schimb, relaţia < pentru
mulţimea int a numerelor întregi nu este bine formată; şirul ...< -10 < ...< -1 < 1
nu este finit.

În general, dacă există f: X → Ν, putem interpreta f ca o funcţie de măsurare


a elementelor din X şi, de asemenea, putem defini relaţia bine formată pf peste X în
modul următor:
∀a, b: X • (a pf b ⇔ f(a) < f(b))

De exemplu, relaţia plist_length, definită pe mulţimea 'a list, este bine


formată. La fel, pentru TDA set, relaţia pcardset este bine formată.

Folosind conceptul de relaţie bine formată, principiul inducţiei poate fi formulat


într-o variantă cunoscută sub numele de inducţie bine formată.

ipoteză inductivă
|
[ ∀y: X | y pf x • P(y)]
_______________________ pas inducţie
P(x)
_______________
∀x: X • P(x)
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 16

Pasul de inducţie porneşte de la ipoteza inductivă ∀y: X | y pf x • P(y)


pentru a demonstra proprietatea P(x). Cu alte cuvinte, în ipoteza că P este verificată
de toate elementele mulţimii X care au măsură inferioară lui x, trebuie demonstrat că
şi x satisface proprietatea P.

Argumentul corectitudinii inducţiei bine formate se obţine prin contradicţie. Dacă


presupunem că există o valoare x astfel încât ¬P(x) atunci, conform pasului de
inducţie, trebuie să existe x1 pf x astfel încât ¬P(x1). Repetând raţionamentul
ajungem la concluzia că există un şir ... p xn p ... p x2 p x1 infinit, ceea ce
contrazice ipoteza relativă la relaţia pf.

Pentru un TDA t să particularizăm relaţia pf la xi pf x ⇔ x = σ( ...,xi,


...), cu xi∈t, pentru σ un constructor de bază nenular al lui t. Ca şi la inducţia
matematică, pasul de inducţie se despică în două părţi: una pentru valori x care pot fi
formate din valori xi pf x, iar cealaltă pentru valori x care corespund constructorilor
nulari ai tipului. Această ultimă parte formează cazurile de bază ale inducţiei. Se
obţine schema inducţiei structurale.

3 Axiome strict monotone


În general, metoda de construcţie a unui TDA poartă responsabilitatea
decidabilităţii proprietăţilor Prop, care definesc corectitudinea parţială a tipului. În
acest sens se va urmări ca axiomele tipului, care pot fi privite ca reguli de reducere a
valorilor simbolice, să garanteze terminarea procesului de reducere. De exemplu,
demonstrarea proprietăţilor TDA set, din secţiunea 2.1, a fost facilitată de forma
specială a axiomelor, asigurând reducerea treptată a complexităţii valorilor simbolice
din Aset şi, implicit, decidabilitatea echivalenţelor folosite în demonstraţie.

Definiţia 10 (Arbore simbolic, nivelul unui operator) Fie v o valoare conformă


prezentării unui TDA t. Definim arborele simbolic asociat lui v, arb(v), arborele
format astfel:
nodul frunză v, dacă v ∈ As, s ≠ t, (v este o valoare externă lui t.
arb(v) =
arborele σ, dacă v=σ(x1,...,xn), σ∈Opw,s, w∈S*, s∈S, S= Atr∪{t}
/ \
/ ... \
arb(x1) arb(xn)

Definiţia 11 (Nivelul unui operator într-o formulă f) Fie f o formulă construită


cu valori conforme prezentării TDA t, inclusiv cu operatori logici şi operatorul if then
else. Definim funcţia:

nivel(σ,f) =
• 0, dacă σ nu apare în f
• max {h(arb(f)σ},
dacă f are forma ρ(... σ...), unde arb(f)σ este un subarbore
din arb(f) cu rădăcina σ, iar h(a) este înălţimea arborelui a
• max {v ∈ {vp,v1,v2} • nivel(σ,v)},
dacă f are forma if vp then v1 else v2
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 17

De exemplu,
nivel(delset, delset(e,bodyset(x,r))) = 3
nivel(delset,bodyset(x,delset(e,r)) = 2
nivel(delset,newset) = 0
nivel(delset, if e=x then s else bodyset(x,delset(e,s))) = 2

Definiţia 12 (Monotonie axiome) Fie (ax =def v1 = v2) o axiomă


corespunzătoare TDA t. Se spune ca axioma ax este strict monotonă relativ la nivelul
lui σ dacă nivel(σ,v1) > nivel(σ,v2). Axiomele tipurilor definite până în prezent
sunt monotone relativ la nivelul primului operator ce apare în membrul stâng al
axiomelor.

Definiţia 13 (Convertibilitatea constructorilor auxiliari) Fie Cona mulţimea


constructorilor auxiliari ai tipului t, considerată ca reuniune de submulţimi disjuncte:

n
Cona = U Cona , Cona ∩ Cona = ∅ , pentru i ≠ j.
i i j
i = 1

Un operator σa∈ Cona este convertibil la constructorii de bază Conb ai lui t


i
dacă:
1. Pentru fiecare σb∈ Conb există cel puţin o axiomă de convertibilitate

ax(σa,σb) =def σa(... σb ...) = v,

strict monotonă relativ la nivelul lui σa, astfel încât singurii constructori admişi în v sunt
din mulţimea
i − 1
Con b ∪ {σ a} ∪ ( U Cona )
j
j = 1

2. Orice constructor σ ∈ Cona , k < i, este convertibil la Conb.


k

Mulţimea Cona este convertibilă la Conb dacă fiecare σa ∈ Cona este convertibil
la Conb. Dacă pentru constructorul σa ∈ Cona, convertibil la Conb, axiomele de
convertibilitate nu conţin alţi constructori auxiliari, în afară de σa, se spune ca σa este
direct convertibil la Conb.

Definiţia 14 (Reductibilitatea operatorilor externi) Fie Ext mulţimea operatorilor


externi ai tipului t, considerată ca reuniune de submulţimi disjuncte.

n
Ext = U Exti, Exti ∩ Extj = ∅ , pentru i ≠ j
i = 1

Un operator extern σ ∈ Exti este reductibil, în raport cu constructorii de bază


Conb ai lui t, la o constantă externă tipului t (o valoare în care nu apar operatori din
Op, deci operatori ai lui t) dacă:

1. Pentru fiecare σb ∈ Conb există cel puţin o axiomă de reductibilitate

ax(σ,σb) =def σ(... σb ...) = v,


Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 18

strict monotonă relativ la nivelul lui σ, astfel încât singurii operatori admişi în v fac
parte din mulţimea
i − 1
Con b ∪ {σ} ∪ ( U Extj)
j = 1

2. Orice constructor σ ∈ Extk, k < i, este reductibil în raport cu Conb.

Mulţimea Ext este reductibilă în raport cu Conb dacă fiecare σ ∈ Ext este
reductibil în raport cu Conb.

Convertibilitatea constructorilor auxiliari şi a operatorilor externi, precum şi


forma axiomelor de convertibilitate, constituie, de fapt, restricţii ce pot fi folosite pentru
a asigura reducerea oricărei valori simbolice a unui tip t, fie la o formă canonică, fie la
o constantă externă tipului t. Restricţionarea deliberată a modului de construcţie a
prezentărilor TDA, pe baza convertibilităţii operatorilor, este utilă, mai ales, din punct
de vedere practic, verificarea TDA simplificâdu-se considerabil. De asemenea, devin
posibile sisteme realiste de asistare a verificării şi implementării TDA.

4 Construcţia a unui TDA sigur


Definiţia 15 Fie t un TDA cu signatura Op şi sorturile S = Atr ∪ {t}. Vom
spune că prezentarea lui t este sigură (sau, prin forţarea limbajului, că t este sigur)
dacă operaţiile tipurilor din Atr sunt decidabile şi consistente, iar setul E al axiomelor
prezentării se construieşte în modul următor:

1) Se împarte mulţimea operatorilor Opw,s, w ∈ S* în

na ne
Conb, Cona = U Cona şi Ext = U Exti .
i
i = 1 i = 1

2) Se specifică restricţiile corespunzătoare aplicării operatorilor din Op sau cele


necesare asigurării corectitudinii valorilor simbolice formate cu operatori.

3) Pentru fiecare σa∈Cona se construiesc axiome stict monotone de


convertibilitate la Conb (conform definiţiei 13), astfel încât restricţiile (2) să fie
satisfăcute. Pentru fiecare σb∈Conb există o singură axiomă de convertibilitate a lui
σa la σb.

4) Pentru fiecare σ∈Ext se construiesc axiome stict monotone de reductibilitate


în raport cu Conb (conform definiţiei 14), astfel încât restricţiile (2) să fie satisfăcute.
Pentru fiecare σb ∈ Conb există o singură axiomă de convertibilitate a lui σ la σb.

Trebuie observat că monotonia strictă a axiomelor este o condiţie suficientă,


dar nu şi necesară, pentru a asigura terminarea procesului de reducere a valorilor
simbolice şi, deci, pentru decidabilitatea proprietăţilor tipului. Toate prezentările
discutate până în prezent au axiome strict monotone şi sunt decidabile în raport cu
proprietăţile asociate lor.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 19

Exemplul 10 Să construim o prezentare sigură pentru TDA vector cu


elemente de tip elm. Modificările elementelor vectorului nu au efecte laterale; atunci
când este necesar se construieşte un nou vector.

vector(elm)::= newvec | bodyvec 〈〈elm × vector〉〉

assign: elm × int × vector → vector --- atribuire valoare vect[i] = a


val: int × vector ⎯|→ elm --- selectare valoare vect[i]
def: int × vector → bool --- test existenţă valoare vect[i]

(1) assign(e,i,newvec) = bodyvec(e,i,newvec)


(2) assign(e,i,(bodyvec(x,j,v)) =
if i = j then bodyvec(e,i,v)
else bodyvec(x,j,assign(e,i,v))
(3) (def(i,v) ∨ i = j) ⇒
(val(i,bodyvec(e,j,v)) = if i = j then e else val(i,v))
(4) def(i,newvec)) = false
(5) def(i,bodyvec(e,j,v)) = if i = j then true else def(i,v)

(6) ∀v,v':vector; e:elm; i:int • (v'=bodyvec(e,i,v)) ⇒ ¬ def(i,v)

Se observă clasificarea operatorilor Op: constructori de bază Conb = {newvec,


bodyvec}, constructori auxiliari Cona= {assign}, selectori Sel = {val} şi operatori
de caracterizare Car = {def}, unde constructorii newvec şi bodyvec descriu starea
instantanee (conţinutul) unui vector. De asemenea, val este o funcţie parţială,
deoarece val(i,v) are sens doar dacă elementul de indice i din v este definit.

Teorema 2 Un TDA t cu prezentare 〈Atr,Op,E,R〉 sigură este decidabil şi


consistent pentru orice set de proprietăţi format din:

a) proprietatea universală (orice valoare simbolică de tip t poate fi construită


folosind exclusiv constructorii de bază ai lui t);
b) proprietăţi de forma v1 = v2, cu v1,v2 ∈ U As (formule simbolice sau
s ∈ Atr
constante ce desemnează valori externe tipului t).

Decidabilitate.
a) Fie v o valoare de tip t, v = σ(... σa(...)...), cu σa∈Cona. Constructorul
σa poate fi eliminat din v. Într-adevăr, termenul σa(...) poate fi substituit în v
conform axiomelor de convertibilitate, obţinând o valoare v', astfel încât
nivel(σa,v) > nivel(σa,v'). Prin inducţie, după nivelul lui σa, se ajunge la o
valoare vm cu nivel(σa,vm)=0, deci, care nu conţine σa. Deoarece această
proprietate este valabilă pentru σa ales arbitrar, rezultă că toţi constructorii auxiliari
pot fi eliminaţi din formule.

b) Fie v o valoare externă tipului, v = σ(...), v∈As, s∈Atr, cu σ ∈Exts.


Valoarea v se poate reduce, conform (a), la o valoare v' fără constructori auxiliari.
Folosind axiomele de reductibilitate la Conb, corespunzătoare lui σ, v' se reduce la
v", astfel încât nivel(σ,v') > nivel(σ,v"). Prin inducţie, după nivelul lui σ, se
ajunge la o valoare vm cu nivel(σ,vm) = 0. Deoarece eliminarea este valabilă
pentru σ ales arbitrar, rezultă că orice egalitate v1 = v2, cu v1 şi v2 formule cu valori
externe TDA t, se reduce la v1p = v2q, v1p şi v2q fiind constante externe tipului t.
Egalitatea poate fi verificată deoarece tipurile externe Atr sunt decidabile.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 20

Consistenţă.
Deoarece pentru orice σ∈Cona ∪ Ext şi σb∈Conb există o singură axiomă de
convertibilitate sau reductibilitate, iar operaţiile tipurilor externe Atr sunt consistente,
ordinea de reducere a unei valori simbolice v∈At corespunzătoare TDA t este unică,
deci valoarea redusă este unică.  

Teorema 2 nu garantează corectitudinea parţială a unui TDA, ci doar asigură


posibilitatea verificării corectitudinii parţiale. De asemenea, prezentarea sigură este
suficientă, dar nu şi necesară, pentru a asigura decidabilitatea verificării corectitudinii
parţiale.

5 Nivelul abstractizării unui TDA


Împărţirea constructorilor unui tip t în Conb şi Cona nu are reguli bine
precizate, cu atât mai mult cu cât constructorii depind de nivelul abstractizării tipului t.
La prima vedere, se pare că toţi constructorii nulari, σ ∈ Op λ,t ar trebui să facă parte
din Conb, dar nici măcar acest fapt nu este întotdeauna adevărat, fiind influenţat, la
rândul său, de nivelul abstractizării tipului. Iată, ca exemplu, o variantă de prezentare -
mai puţin elegantă - a tipului stack, folosind un singur constructor de bază -
bodystack - care nu numai că pune în evidenţă conţinutul instantaneu al unei stive
dar, mai mult, face explicit şi mecanismul de acces la elementele stocate în stivă. În
noua variantă Conb = {bodystack} şi Cona = {newstack, push, pop}. Înaintea
altor comentarii, prezentarea tipului stackv este:

stackv(elm)::= bodystack 〈〈elm × vector(elm)〉〉 --- variantă a tipului stack

newstack: → stackv
push: elm × stackv → stackv
pop: stackv \ {newstack} → stackv
top: stackv \ {newstack} → elm
empty: stackv → bool

(1) newstack = bodystack(0,newvec)


(2) push(e,bodystack(i,v)) = bodystack(i+1,assign(e,i+1,v))

(3) i > 0 ⇒ (pop(bodystack(i,v)) = bodystack(i-1,v))


(4) i > 0 ⇒ (top(bodystack(i,v)) = val(i,v))
(5) empty(bodystack(i,v)) = (i = 0)

Prezentarea TDA stackv descrie mai curând tipul stivă al cărei corp este un
vector, în loc de stivă, în general. Nivelul de abstractizare are de suferit în favoarea
apropierii specificării în raport cu o posibilă implementare a tipului stackv, o stivă
fiind reprezentată ca o înregistrare cu două câmpuri: indexul de vârf al stivei şi
vectorul cu elementele stivei.

Deşi prezentarea este corectă, totuşi ea este mai puţin clară decât cea iniţială -
pentru TDA stack. Proprietăţile elementare pop(push(e,s)) = s şi top(push(e,s))
= e nu mai rezultă explicit din axiome, ci trebuie demonstrate ca proprietăţi ale stivei.
În general, vom evita astfel de prezentări, cu atât mai mult cu cât precizarea
implementării unui TDA se poate realiza foarte bine la nivel abstract.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 21

În consecinţă, chiar dacă se folosesc constructori de bază ce pun în evidenţă


starea instantanee (conţinutul) unei valori a tipului specificat, aceşti operatori nu
trebuie să precizeze mecanismul de acces la componentele valorii, ci doar
componentele ca atare. Din acest punct de vedere există, evident, o mare deosebire
între bodyvec şi bodyset (din exemplele 10, 7), pe de o parte, şi bodystack, pe de
altă parte. În timp ce bodyvec şi bodyset sunt operatori abstracţi, bodystack este,
practic, un operator de reprezentare concretă a unei stive.

6 Reificarea TDA
Fie ti = 〈Atri,Opi,Ei,Ri〉, i=1,n, o mulţime de TDA folosite ca suport al
n
implementării unui TDA t = 〈Atr,Op,E,R〉. Notăm T = × t i mulţimea punctelor
i = 1
〈x1, x2, ...,xn〉, xi ∈ti, formate cu valori ale tipurilor suport.

Definiţia 16 A implementa tipul t, folosind suportul de implementare T,


corespunzător tipurilor ti, i= 1,n, înseamnă a defini:

• o funcţie de abstractizare Abs: T → t sau


• o funcţie de implementare Imp: t → T,

care asociază un punct x ∈ T cu o valoare v ∈ t (reamintim echivalenţa de notaţie


dintre t şi At), x fiind reprezentarea lui v. Ideal, Abs ar trebui să asigure ca fiecare
valoare canonică din t să provină prin abstractizarea unei unice valori din T, deci să
fie injectivă şi surjectivă, iar Imp ar trebui să ducă la implementări distincte pentru
orice valori canonice diferite din t, deci să fie totală şi injectivă.

Cele două funcţii sunt specificate folosind axiome de abstractizare, pentru Abs,
şi axiome de implementare, pentru Imp. Procesul de implementare a unui tip abstract
pe baza altor tipuri, considerate predefinite în contextul implementării, poartă numele
de rafinare sau reificare. În cele ce urmează, ne limităm doar la implementarea prin
abstractizare.

Definiţia 17 O axiomă de abstractizare este o ecuaţie cu structura:

a) σ(...,Abs〈x1,x2,...,xn〉,...) = v, σ ∈Opw,s, w ≠ λ, unde v poate fi:

(1) Abs〈y1, y2,..., yn〉, yi ∈ ti, dacă σ∈Opw,t


n
(2) g(y1 ,y2,..., ym), yi∈ti, g∈ U Opi , dacă σ∈Opw,s, s ≠ t.
i = 1
(3) if vp then v1 else v2, cu vp, v1 şi v2 de forma (1),(2) sau (3).

b) σ = Abs〈x1,x2,...,xn〉, σ∈Opλ,t.

Exemplul 11 Fie tipul abstract stack(elm), din secţiunea 1.2, şi suportul de


implementare T = {i∈int; v∈ vector(elm) • 〈i,v〉}. Funcţia de abstractizare este
introdusă prin următoarele axiome:

(A.1) newstack = Abs〈0,newvec〉


(A.2) push(e, Abs〈i,v〉) = Abs〈i+1, assign(e,i+1,v)〉
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 22

(A.3) pop(Abs〈i,v〉) = Abs〈i-1,v〉


(A.4) top(Abs〈i,v〉) = val(i,v)
(A.5) empty(Abs〈i,v〉) = (i = 0)

Axiomele de abstractizare sunt asemănătoare cu axiomele tipului stackv,


explicitând, de această dată în mod necesar, modul de prelucrare a structurii de date
care reprezintă o valoare de tip stack.

Restricţionarea implementării
Se observă că, în lipsa unor restricţii adecvate, funcţiile Abs şi Imp pun
probleme. Astfel Abs nu este injectivă. Bunăoară, în cazul exemplului 11, conform
axiomelor tipului stack şi axiomelor de abstractizare, pentru orice e: elm avem:

newstack = pop(push(e,Abs〈0,newvec〉)) =
pop(Abs〈1,v〉) = Abs〈0,v〉, cu v = assign(e,1,newvec)

newstack = Abs〈0,newvec〉

şi, în general, pentru orice v: vector(elm), newstack = Abs〈0,v〉. Deci, Abs nu este
injectivă.

Acest exemplu sugerează că funcţiile Abs şi Imp trebuie să lucreze cu clase de


echivalenţă, corespunzătoare unor valori care au aceeaşi semnificaţie din punctul de
vedere al tipului implementat. Din acest motiv, domeniul lui Abs şi, respectiv,
codomeniul lui Imp, se limitează la T'= T/Re, unde Re este o relaţie de echivalenţă
care concentrează în aceeaşi clasă toate elementele lui T ce reprezintă aceeaşi
valoare simbolică a tipului implementat t. De exemplu, pentru implementarea TDA
stack, relaţia Re de echivalenţă a reprezentării este definită de proprietatea:

〈i,v1〉 a 〈j,v2〉 ∈ Re ⇔(i = j) ∧ (∀k: 1..i • val(k,v1) = val(k,v2))

Cu alte cuvinte, doi dubleţi 〈i,v1〉 şi 〈j,v2〉 sunt echivalenţi (reprezintă aceeaşi
stivă) dacă i = j şi conţinutul celor doi vectori este acelaşi pentru indici între 1 şi i. O
clasă de echivalenţă din T/Re desemnează o reprezentare dusă de Abs într-o stivă
abstractă, iar reprezentările distincte din T/Re sunt transformate de Abs în stive
diferite.

Implementarea va fi restricţionată şi din considerente de corectitudine a


reprezentărilor - în sens de formule bine formate - precum şi din motive practice. Nu
orice dublet 〈i,v〉 din mulţimea suport int × vect(elm) reprezintă o stivă. De
exemplu, dubletul 〈100000,assign(e,100000,newvec)〉 este o reprezentare incorectă
pentru o stivă din două motive: (a) elementele stivei pentru i = 1,99999 nu sunt
definite şi (b) capacitatea stivei ar putea fi limitată la un număr de elemente inferior lui
100000. Vom impune ca indicele i, corespunzător vârfului stivei, să fie într-un interval
dat, iar vectorul v să fie definit pentru toţi indicii între 1 şi indicele curent al vârfului
stivei. Rezultă restricţia:

Inv〈i,v〉 =def i ∈ 0..Max ∧ (∀k: 1..i • def(k,v)),

numită invariant al reprezentării, impusă tuturor elementelor 〈i,v〉 din suportul T al


implementării. Evident, invariantul reprezentării limitează implementarea valorilor unui
TDA, aici a stivelor abstracte. De asemenea, invariantul reprezentării conduce indirect
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 23

la restricţionarea domeniului operatorilor tipului implementat. Bunăoară, push nu mai


este un operator total, ci poate fi aplicat doar acelor stive care rezultă prin Abs〈i,v〉,
astfel încât restricţia Inv〈i,v〉 ∧ i < Max este satisfăcută.

În general, cu limitările impuse de relaţia de echivalenţă şi de invariantul


reprezentării, funcţia Abs devine totală şi injectivă, iar Imp poate fi considerată inversa
lui Abs.
Abs: TInv/Re → t şi Imp: t → TInv/Re,

unde, TInv este submulţimea reprezentărilor valorilor lui t, mulţime filtrată prin
invariantul reprezentării (de fapt mulţimea de adevăr a lui Inv). Pentru cazul particular
al stivei:
TInv =def { i∈int; v ∈ vector(elm) | Inv〈i,v〉 • 〈i,v〉}

Definiţia 18 Implementarea prin funcţia de abstractizare Abs (respectiv, funcţia


de implementare Imp) a TDA t = 〈Atr, Op, E, R〉 este corectă dacă:

a) invariantul reprezentării este verificat de implementarea constructorilor de


bază ai lui t şi

b) axiomele din E sunt verificate folosind:

• axiomele de abstractizare (respectiv implementare);


• axiomele tipurilor suport, ti, i = 1,n;
• relaţia de echivalenţă şi invariantul reprezentării valorilor de tip t;
• restricţiile operatorilor tipului t şi cele ale invariantului reprezentării.

Exemplul 12 Se verifică implementarea prin abstractizare a tipului stack,


conform exemplului 11, cu relaţia de echivalenţă şi invariantul reprezentării:

〈i,v1〉 a 〈j,v2〉 ∈ Re ⇔ (i = j) ∧ (∀k: 1..i • val(k,v1) = val(k,v2))

Inv〈i,v〉 =def i ∈ 0..Max ∧ (∀k: 1..i • def(k,v)),

a) Verificarea invariantului reprezentării în raport cu implementarea cons-


tructorilor de bază.

newstack ⎯ axioma A.1 → Abs〈0,newvec〉, care satisface Inv.

Fie 〈i,v〉 reprezentarea unei stive astfel încât 〈i,v〉 satisface Inv〈i,v〉 ∧ i <
Max, unde i < Max este restricţia suplimentară indusă de Inv asupra operatorului
push. Avem:

push(e,Abs〈i,v〉) ⎯ axioma A.2 → Abs〈i+1, assign(e, i+1, v)〉,

unde def(i+1, assign(e, i+1, v)) = true, conform axiomelor (2) şi (5) din
prezentarea TDA vector. Rezultă i+1 ∈ 1..Max ∧ (∀k: 1..i+1 • def(k,
assign(e,k,v)). Reprezentarea noii stive obţinută prin push satisface Inv.

b) Verificarea axiomelor tipului stack în raport cu axiomele de implementare


prin abstractizare.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 24

empty(newstack) = true ?
empty(newstack) ⎯ axioma A.1 → empty(Abs〈0,newvec〉)
⎯ axioma A.5 → 0 = 0
⎯ axiomă a lui int → true

empty(push(e,s)) = false ?
empty(push(e,s)) ⎯ s = Abs〈i,v〉 → empty(push(e,Abs〈i,v〉))
⎯ axioma A.2 → empty(Abs〈i+1,assign(e,i+1,v)〉)
⎯ axioma A.5 → i+1 = 0
⎯ Inv → i ≥ 0 ∧ i+1 = 0
⎯ axiome pt. int → false

top(push(e,s)) = e ?
top(push(e,s)) ⎯ s = Abs〈i,v〉 → top(push(e,Abs〈i,v〉))
⎯ axioma A.2 → top(Abs〈i+1,assign(e,i+1,v)〉)
⎯ axioma A.4 → val(i+1,assign(e,i+1,v))
⎯ axioma vector.3 → e

pop(push(e,s)) = s ?
pop(push(e,s)) ⎯ s = Abs〈i,v〉 → pop(push(e,Abs〈i,v〉))
⎯ axioma A.2 → pop(Abs〈i+1, assign(e,i+1,v)〉)
⎯ axioma A.3→ Abs〈i, assign(e,i+1,v)〉
⎯ Re,Inv → Abs〈i,v〉 = s

7 Un exemplu complet - TDA coadă


Se consideră tipul queue (coadă), cu următoarea descriere informativă. Coada
este un container care stochează elemente de tip neprecizat, numit elm, în vederea
prelucrării lor în funcţie de ordinea sosirii în coadă: primul venit primul prelucrat
(servit). Strategia de acces la elementele din coadă este FIFO (First-In First-Out), spre
deosebire de stivă, care foloseşte strategia LIFO (Last-In Last-Out).

Principalele operaţii alese aici pentru tipul queue sunt: insq - introducerea
unui nou element la sfârşitul cozii; delq - eliminarea elementului de la vârful cozii;
topq - obţinerea elementului de la vârful cozii (fără eliminarea lui din coadă); emptyq -
testul de coadă vidă; lengthq - numărul de elemente din coadă (lungimea cozii);
concq - concatenarea a două cozi.

7.1 Specificare abstractă

Ca de obicei, abordarea este constructivistă: inserţiile şi eliminările elementelor


dintr-o coadă conduc la construcţia unei noi cozi.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 25

queue(elm)::= newq | insq 〈〈elm × queue〉〉

delq: queue \ {newq} → queue --- eliminarea primului element din coadă
topq: queue \ {newq} → elm --- selectare prim element din coadă
concq: queue × queue → queue --- concatenare cozi
lengthq: queue → int --- numărul elementelor din coadă
emptyq: queue → bool --- test coadă vidă

(1) emptyq(newq) = true


(2) emptyq(insq(e,q)) = false
(3) delq(insq(e,q)) = if emptyq(q) then newq
else insq(e,delq(q))
(4) concq(q,newq) = q
(5) concq(q,insq(e,p)) = insq(e,concq(q,p))
(6) topq(insq(e,q)) = if emptyq(q) then e else topq(q)
(7) lengthq(newq) = 0
(8) lengthq(insq(e,q)) = 1 + lengthq(q)

Corectitudinea parţială a tipului queue va fi verificată în raport cu proprietăţile


P1(q) - proprietatea universală, conform căreia orice coadă poate fi reprezentată
doar cu newq şi insq - şi P2(q), care arată că lungimea unei cozi se diminuează la
eliminarea unui element -, proprietăţi definite pentru ∀q ∈ queue(elm):

P1(q) =def (q= newq) ∨ (∃e: elm; p: queue • q= insq(e,p))


P2(q) =def ¬emptyq(q) ⇒ (lengthq(delq(q)) = lengthq(q) - 1)

Propoziţia 1 TDA queue este parţial corect relativ la P1 şi P2.

P1(q) rezultă din corolarul 2. Pentru verificarea lui P2(q), folosim inducţia
structurală. Cazul newq satisface imediat P2. Fie p o coadă care satisface P2 şi x:
elm. Trebuie să arătăm că q = insq(x,p) satisface P2.

(a) lengthq(delq(q)) = lengthq(delq(insq(x,p)))


⎯ axioma 3 → lengthq(insq(x,delq(p)))
⎯ axioma 8 → lengthq(delq(p)) +1
⎯ P2(p) → lengthq(p) - 1 + 1
⎯ axiomă a lui int → lengthq(p)

(b) lengthq(q) = lengthq(insq(x,p)) ⎯ axioma 8 → lengthq(p) + 1

Din (a) şi (b) rezultă imediat lengthq(delq(q))= lengthq(q) - 1.

7.2 Implementarea TDA Queue

Fie suportul de implementare T = int × List(elm), unde List are


prezentarea tipică unei liste unidirecţionale, completată cu operatorii app - de inserţie
a unui element la sfârşitul listei - şi conc - de concatenare a două liste.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 26

List(elm)::= nil | cons 〈〈elm × List〉〉

app: elm × List → List --- inserează un element la sfârşitul unei liste
cdr: List \ {nil} → List --- selectează restul unei liste, fără primul element
car: List \ {nil} → elm --- selectează elementul de la începutul unei liste
null: List → bool --- testează dacă o listă este vidă
conc: List × List → List --- concatenează două liste
length: List → int --- calculează numărul de elemente dintr-o listă

(l.1) null(nil) = true


(l.2) null(cons(e,l)) = false
(l.3) cdr(cons(e,l)) = l
(l.4) app(e,nil) = cons(e,nil)
(l.5) app(e,cons(x,l)) = cons(x,app(e,l))
(l.6) car(cons(e,l)) = e
(l.7) conc(nil,l) = l
(l.8) conc(cons(e,ll),l) = cons(e,conc(ll,l))
(l.9) length(nil) = 0
(l.10) length(cons(e,l)) = 1 + length(l)

Pentru cele ce urmează, este utilă probarea următoarelor proprietăţi ale


operatorilor conc şi length:

P1conc(l) =def conc(l,nil)= l


P2conc(l) =def app(e,conc(l,ll)) = conc(l,app(e,ll))
P1length(l) =def length(app(e,l)) = 1 + length(l)

Pentru verificarea P1conc, prin inducţie structurală, considerăm în primul rând


cazul listei vide:
P1conc(nil): conc(nil,nil) ⎯ axioma l.7 → nil

Fie ll astfel încât P1conc(ll) = true. Să demonstrăm că l = cons(x,ll),


x:elm, satisface P1conc.

conc(l,nil) =
conc(cons(x,ll),nil)⎯ axioma l.8 → cons(x,conc(ll,nil))
⎯ P1conc(ll) → cons(x,ll) = l
Pentru verificarea lui P2conc, prin inducţie structurală, cazul listei vide este:

app(e,conc(nil,ll)) ⎯ axioma l.7 → app(e,ll) şi


conc(nil,app(e,ll)) ⎯ axioma l.7 → app(e,ll)

Fie L astfel încât P2conc(L) = true. Să demonstrăm că l = cons(x,L),


x:elm, satisface P2conc.

app(e,conc(l,ll) =
app(e,conc(cons(x,L),ll))
⎯ axioma l.8 → app(e,cons(x,conc(L,ll)))
⎯ axioma l.5 → cons(x,app(e,conc(L,ll)))
⎯ P2conc(L) → cons(x,conc(L,app(e,ll)))
⎯ axioma l.8 → conc(cons(x,L),app(e,ll))= conc(l,app(e,ll))

Proprietatea P1length se demonstrează similar, fiind lăsată ca exerciţiu.


Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 27

O coadă va fi reprezentată prin dubletul 〈i,l〉, i ∈ int, l ∈ List(elm), i


desemnând lungimea cozii, iar l conţinutul cozii. Axiomele de implementare prin
abstractizare sunt:

(A.1) newq = Abs〈0,nil〉


(A.2) insq(e, Abs〈i,l〉) = Abs〈i+1,app(e,l)〉
(A.3) delq(Abs〈i,l〉) = Abs〈i-1,cdr(l)〉
(A.4) concq(Abs〈i,l1〉, Abs〈j,l2〉)= Abs〈i+j, conc(l1,l2)〉
(A.5) topq(Abs〈i,l〉) = car(l)
(A.6) lengthq(Abs〈i,l〉) = i
(A.7) emptyq(Abs〈i,l〉) = (i = 0)

Relaţia de echivalenţă Re, definită pe suportul implementării int × List(elm),


şi invariantul reprezentării sunt:

〈i,l1〉 a 〈j,l2〉 ∈ Re ⇔ (i = j) ∧ (l1 = l2)


Inv〈i,l〉 =def i = length(l)

Cei doi invarianţi arată că o coadă are o reprezentare unică, prin dubletul
〈length(l), l〉, unde l stochează elementele din coadă, în ordinea inserării lor în
coadă.

1) Verificarea invariantului reprezentării. Trebuie arătat că invariantul este


respectat de implementarea constructorilor newq şi insq.

newq ⎯ axioma A.1 → Abs〈0,nil〉, unde 〈0,nil〉 satisface Inv.

Fie 〈i,l〉 care satisface Inv. Să arătăm că reprezentarea corespunzătoare cozii


insq(e,Abs〈i,l〉) satisface Inv. Avem,

insq(e,Abs〈i,l〉) ⎯ axioma A.2 → Abs〈i+1,app(e,l)〉

Dar length(app(e,l)) = length(l) + 1, conform proprietăţii P1length.


Deoarece reprezentarea 〈i,l〉 satisface Inv rezultă length(l) = i şi
length(app(e,l)) = i + 1, deci insq(e,Abs〈i,l〉) satisface Inv.

2) Verificarea axiomelor tipului queue. Trebuie arătat că axiomele tipului queue


sunt respectate de implementarea operatorilor, conform axiomelor de abstractizare.
Fie q = Abs〈i,l〉, cu Inv〈i,l〉 = true.

(1) emptyq(newq) = true ?


emptyq(newq) ⎯ axioma A.1 → emptyq(Abs〈0,nil〉)
⎯ axioma A.7 → 0 = 0 → true

(2) emptyq(insq(e,q)) = false ?


emptyq(insq(e,Abs〈i,l〉))
⎯ axioma A.2 → emptyq(Abs〈i+1,app(e,l)〉)
⎯ axioma A.7 → i+1 = 0
⎯ Inv〈i,l〉 → i ≥ 0 ∧ i+1 = 0 → false

(3) delq(insq(e,q)) = if emptyq(q) then newq else insq(e,delq(q))?


Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 28

Caz 3.1) emptyq(Abs〈i,l〉) = true


⎯ axioma A.7 → i = 0 ⎯ Inv〈i,l〉 → length(l) = 0
⎯ axioma l.9 → l = nil

Trebuie verificată echivalenţa delq(insq(e,q)) = newq cu q = Abs〈0,nil〉


delq(insq(e,Abs〈0,nil〉))
⎯ axioma A.2 → delq(Abs〈1,app(e,nil)〉)
⎯ axioma l.4 → delq(Abs〈1,cons(e,nil)〉)
⎯ axioma A.3 → Abs〈0,cdr(cons(e,nil))〉
⎯ axioma l.3 → Abs〈0,nil〉
⎯ axioma A.1 → newq

Caz 3.2) emptyq(Abs〈i,l〉) = false


⎯ axioma A.7 → i ≠ 0 ⎯ Inv〈i,l〉 → length(l) > 0
⎯ axioma l.10 → l = cons(x,ll), cu x:elm, ll:List(elm)

Pentru o coadă q = Abs〈i,l〉, cu l = cons(x,ll), trebuie verificat:


delq(insq(e,q)) = insq(e,delq(q)).

delq(insq(e,Abs〈i,l〉))
⎯ axioma A.2 → delq(Abs〈i+1,app(e,cons(x,ll))〉)
⎯ axioma l.5 → delq(Abs〈i+1,cons(x,app(e,ll))〉)
⎯ axioma A.3 → Abs〈i,cdr(cons(x,app(e,ll)))〉
⎯ axioma l.3 → Abs〈i,app(e,ll)〉

insq(e,delq(Abs〈i,l〉))
⎯ axioma A.3 →insq(e,Abs〈i-1,cdr(cons(x,ll))〉)
⎯ axioma l.3 → insq(e,Abs〈i-1,ll〉)
⎯ axioma A.2 → Abs〈i,app(e,ll)〉
(4) concq(q,newq) = q ?
concq(Abs〈i,l〉,newq)
⎯ axioma A.1 → concq(Abs〈i,l〉,Abs〈0,nil〉)
⎯ axioma A.4 → Abs〈i,conc(l,nil)〉
⎯ P1conc(l) → Abs〈i,l〉 = q

(5) concq(q,insq(e,p)) = insq(e,concq(q,p)) ?


concq(Abs〈i,l〉,insq(e,Abs〈j,ll〉))
⎯ axioma A.2 → concq(Abs〈i,l〉,Abs〈j+1,app(e,ll)〉)
⎯ axioma A.4 → Abs〈i+j+1,conc(l,app(e,ll))〉

insq(e,concq(Abs〈i,l〉,Abs〈j,ll〉))
⎯ axioma A.4 → insq(e,Abs〈i+j,conc(l,ll)〉)
⎯ axioma A.2 → Abs〈i+j+1,app(e,conc(l,ll))〉
⎯ P2conc(l) → Abs〈i+j+1,conc(l,app(e,ll))〉
(6) topq(insq(e,q)) = if emptyq(q) then e else topq(q) ?
Caz 6.1) emptyq(Abs〈i,l〉) = true
⎯ axioma A.7 → i = 0 ⎯ Inv → length(l) = 0
⎯ axioma l.9 → l = nil

Trebuie verificată echivalenţa topq(insq(e,q)) = e cu q= Abs〈0,nil〉


topq(insq(e,Abs〈0,nil〉))
⎯ axioma A.2 → topq(Abs〈1,app(e,nil)〉)
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 29

⎯ axioma l.4 → topq(Abs〈1,cons(e,nil)〉)


⎯ axioma A.5 → car(cons(e,nil))
⎯ axioma l.6 → e

Caz 6.2) emptyq(Abs〈i,l〉) = false


⎯ axioma A.7 → i ≠ 0
⎯ Inv〈i,l〉 → length(l) > 0
⎯ axioma l.2 → l = cons(x,ll)

Pentru q = Abs〈i,l〉, cu l= cons(x,ll), trebuie verificată echivalenţa


topq(insq(e,q)) = topq(q)).

topq(insq(e,Abs〈i,l〉))
⎯ axioma A.2 → topq(Abs〈i+1,app(e,cons(x,ll))〉)
⎯ axioma l.5 → topq(Abs〈i+1,cons(x,app(e,ll))〉)
⎯ axioma A.5 → car(cons(x,app(e,ll)))
⎯ axioma l.6 → x

topq(Abs〈i,cons(x,ll)〉)
⎯ axioma A.5 → car(cons(x,ll))
⎯ axioma l.6 → x

(7) lengthq(newq) = 0 ?
lengthq(newq) ⎯ axioma A.1 → lengthq(Abs〈0,nil〉)
⎯ axiomaA.6 → 0

(8) lengthq(insq(e,q))= 1+ lengthq(q) ?


lengthq(insq(e,Abs〈i,l〉))
⎯ axioma A.2 → lengthq(Abs〈i+1,app(e,l)〉)
⎯ axioma A.6 → i+1

1 + lengthq(Abs〈i,l〉) ⎯ axioma A.6 → 1 + i

Codificarea axiomelor de abstractizare, folosind limbaj de programare, va


conduce la o implementare concretă şi corectă. De exemplu, codificarea de mai jos
utilizează în locul tipului List tipul Caml 'a list, pentru care operatorul app este
implementat imediat conform app(e,l) = l @ [e], iar conc este @.

queue.ml

type 'a queue = Abs of int * 'a list;;


let error s = raise (queue_error s);;

let newq = Abs(0,[])


and insq(e, Abs(i,l)) = Abs(i+1, l @ [e]);;

let delq(Abs(i,l)) = if i = 0 then error "delq on empty queue"


else Abs(i-1,tl l);;

let concq(Abs(i,l1), Abs(j,l2)) = Abs(i+j, l1 @ l2);;

let topq(Abs(i,l)) = if i = 0 then error "topq on empty queue" else hd l;;

let lengthq(Abs(i,l)) = i
and emptyq(Abs(i,l)) = (i = 0);;
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 30

queue.mli
type 'a queue;;
exception queue_error of string;;
value newq: 'a queue
and insq: 'a * 'a queue -> 'a queue
and delq: 'a queue -> 'a queue
and topq: 'a queue -> 'a
and emptyq: 'a queue -> bool
and lengthq: 'a queue -> int
and concq: 'a queue * 'a queue -> 'a queue;;

Implementarea este foarte aproape de cea ilustrată în secţiunea 8.5. De altfel,


programul de sortare după ranguri discutat acolo funcţionează fără nici o modificare şi
cu noua implementare a tipului coadă.

8 Un exemplu complet - TDA inel unidirecţional


Un inel nevid este caracterizat printr-un vârf ce poate fi deplasat prin inel
element cu element astfel încât doar elementul de la vârful inelului este accesibil.
Evident, după parcurgerea tuturor elementelor din inel, vârful ajunge în poziţia iniţială.

8.1 Descriere informală

Notăm T Ring mulţimea inelelor cu elemente de tip T şi descriem operaţiile


inelului folosind o reprezentare grafică ad-hoc, unde simbolul ∗ marchează vârful
curent al inelului.

− void:→ T Ring construieşte un inel vid.

− ins:T × T Ring → T Ring. ins(e,R) inserează elementul e la vârful inelului R.


Vârful inelului este poziţionat pe elementul e.

e , void ins e ∗

en en
∗ ∗
e1 e
ins
e ,
e2 e2 e1

− del:T Ring\{void} → T Ring. del(R) elimină elementul de la vârful inelului


nevid R. Vârful este poziţionat pe următorul element din inel, dacă inelul
obţinut nu este vid.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 31

e ∗ del void

en en

e e1 ∗
del

e1 e2
e2

− top:T Ring\{void} → T. top(R) este elementul de la vârful inelului nevid R.

en

e1
top
e1

e2

− move:T Ring\{void} → T Ring. Prin move(R) vârful inelului nevid R este


deplasat în sensul acelor de ceasornic peste elementul indicat de poziţia
curentă a vârfului. Dacă inelul are un singur element, poziţia vârfului
rămâne neschimbată.

en en

e1 e1
move

e2 e2

e ∗ move e ∗

− empty:T Ring → bool = {0,1}. empty(R) testează dacă inelul R este vid.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 32

8.2 Specificarea algebrică şi reificarea inelului

Descrierea informală de mai sus corespunde următoarei specificaţii algebrice a


tipului T Ring.

T Ring ::= void | ins(T, T Ring)

empty: T Ring → bool


top: T Ring \{void} → T
del: T Ring\{void} → T Ring
move: T Ring\{void} → T Ring

(1) empty(void) = 1
(2) empty(ins(e,X)) = 0
(3) top(ins(e,X)) = e
(4) del(ins(e,X)) = X
(5) move(ins(e,void)) = ins(e,void)
(6) move(ins(e,ins(e',X))) = ins(e',move(ins(e,X)))

Cum verificăm dacă specificaţia corespunde într-adevăr unui inel


unidirecţional? Cu alte cuvinte, cum putem proba corectitudinea specificaţiei? O
posibilitate constă în alegerea unei proprietăţi reprezentative inelului şi verificarea
acesteia folosind specificaţia. De exemplu, ∀i∈Ν; X∈T Ring | ¬empty(X) •
top(movei(X)) = element(i,X), unde movei(X) reprezintă aplicarea de i ori a
operaţiei move pornind cu inelul X, element(i,X) reîntoarce elementul i modulo
size(X) din X, iar size(X) reîntoarce numărul elementelor din X. Un asemenea
exerciţiu este efectuat în secţiunea 8.3.

Inelul poate fi implementat ca o listă circulară, iar prelucrarea este banală dacă
limbajul de programare folosit lucrează direct cu pointerii din celulele listei. În acest
caz toate operaţiile sunt în Θ(1). Să considerăm că limbajul de programare folosit nu
lucrează în mod direct cu pointeri ci dispune de tipul T List (listă unidirecţională cu
elemente de tip T), cu operatorii şi specificaţia formală de mai jos, unde:

− nil este lista vidă.


− cons(e,L) construieşte din lista L o listă care are ca prim element e.
− head(L) reîntoarce elementul de la vârful listei nevide L.
− tail(L) reîntoarce lista rezultată prin decuparea elementului de la vârful lui L.
− null(L) testează dacă lista L este vidă.

T List ::= nil | cons(T, T List)

head: T List\{nil} → T
tail: T List\{nil} → T List
null: T List → bool

(1L) head(cons(e,L)) = e
(2L) tail(cons(e,L)) = L
(3L) null(nil) = 1
(4L) null(cons(e,L)) = 0

O implementare posibilă a tipului T Ring folosind tipul T List consideră un inel


nevid ca abstractizare Absp〈L,R〉 a unei perechi 〈L,R〉 de liste unidirecţionale L şi R.
Lista R conţine elementele din inel situate între vârful curent şi "ultimul" element al
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 33

inelului, iar L conţine elementele, aranjate în ordine inversă, aflate între poziţia iniţială
şi poziţia curentă a vârfului inelului. Inelul vid este abstractizat prin Absp〈nil,nil〉,
unde operatorul distribuit 〈_,_〉: T1×T2 → T1 T2 Pair construieşte o valoare a tipului
"pereche cu elemente de tip T1 şi T2".
vârf
e1 ek ek+1 en

L R

ek e1 ek+1 en

Fie inelul X = Absp〈L,R〉. Convenim ca lista R să fie vidă doar dacă inelul X
este vid. O astfel de reprezentare a inelului o vom numi normalizată şi poate fi
obţinută prin operaţia:
(norm) normalize〈L,R〉 = if null(R) then 〈nil,reverse(L)〉 else 〈L,R〉

unde operaţia reverse(L) construieşte inversa listei L. Dacă inversarea unei liste
este
reverse(L) = rev(L,nil), cu rev(nil,R) = R
rev(cons(e,L),R) = rev(L,cons(e,R)),

atunci complexitatea operaţiei reverse(L) este Θ(n), n fiind numărul de elemente din
lista L. Aşadar, în cazul cel mai defavorabil, complexitatea operaţiei normalize〈L,R〉
este proporţională cu numărul de elemente din inelul Absp〈L,R〉.

Operaţiile inelului presupun un inel cu reprezentare normalizată şi conduc la un


inel cu reprezentare normalizată, fiind implementate conform ecuaţiilor următoare,
numite axiome de implementare:

(1i) void = Absp〈nil,nil〉


(2i) ins(e,Absp〈L,R〉) = Absp〈L,cons(e,R)〉
(3i) del(Absp〈L,R〉) = Absp(normalize〈L,tail(R)〉)
(4i) move(Absp〈L,R〉) = Absp(normalize〈cons(head(R),L),tail(R)〉)
(5i) top(Absp〈L,R〉) = head(R)
(6i) empty(Absp〈L,R〉) = null(R)

Este interesant de observat că funcţia Absp este definită peste clase de


echivalenţă ale perechilor 〈L,R〉. Într-adevăr, în cursul prelucrărilor, se pot obţine
implementări diferite – dar echivalente – ale aceluiaşi inel. De exemplu,

Absp〈nil,cons(e,cons(e',nil))〉 ⎯ move →
Absp〈cons(e,nil),cons(e',nil)〉 ⎯ del →
Absp〈nil,cons(e,nil)〉 ⎯ ins e' →
Absp〈nil,cons(e',cons(e,nil))〉 ⎯ move →
Absp〈cons(e',nil),cons(e,nil)〉

Inelul Absp〈cons(e',nil),cons(e,nil)〉 este echivalent cu inelul iniţial


Absp〈nil,cons(e,cons(e',nil))〉.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 34

Spunem că reprezentarea 〈L,R〉 este echivalentă cu reprezentarea 〈L',R'〉 a


unui inel, cu alte cuvinte spunem că Absp〈L,R〉 şi Absp〈L',R'〉 reprezintă acelaşi inel şi
scriem Absp〈L,R〉 = Absp〈L',R'〉, dacă R @ (reverse L) = R' @ (reverse L'),
unde @ reprezintă concatenarea listelor. Dacă Z şi W sunt valori de tipul T List, iar e
este un element de tip T, concatenarea poate fi implementată conform axiomelor:
nil @ Z = Z
cons(e,W) @ Z = cons(e, W @ Z)

Folosind relaţia de echivalenţă

Absp〈L,R〉 = Absp〈L',R'〉 ⇔ R @ (reverse L) = R' @ (reverse L'),

se poate verifica că implementarea conformă ecuaţiilor de mai sus respectă axiomele


din specificarea algebrică a tipului T Ring, deci că implementarea este corectă. De
exemplu, fie X = Absp〈L,R〉 şi axioma
move(ins(e,ins(e',X))) = ins(e',move(ins(e,X)))

Prin rescrierea celor doi termeni ai axiomei, conform axiomelor tipurilor T Ring,
T List şi ecuaţiilor de implementare, se ajunge la acelaşi inel.

move(ins(e,ins(e',X)))
= move(ins(e,ins(e',Absp〈L,R〉)))
⎯ 2i → move(ins(e,Absp〈L,cons(e',R)〉))
⎯ 2i → move(Absp〈L,cons(e,cons(e',R))〉)
⎯ 4i,1L,2L → Absp(normalize〈cons(e,L),cons(e',R)〉)
⎯ norm → Absp〈cons(e,L),cons(e',R)〉
= X'

ins(e',move(ins(e,X))
= ins(e',move(ins(e,Absp〈L,R〉)))
⎯ 2i → ins(e',move(Absp〈L,cons(e,R)〉))
⎯ 4i,1L,2L → ins(e',Absp(normalize〈cons(e,L),R〉))

Caz 1. R = nil, L = nil


⎯ norm → ins(e',Absp〈nil,cons(e,nil)〉)
⎯ 2i → Absp〈nil,cons(e',cons(e,nil))〉
= X"

Dar, în acest caz, X' = Absp〈cons(e,nil),cons(e',nil)〉, iar cons(e',nil)


@ reverse(cons(e,nil)) = cons(e',cons(e,nil)). Deci X' = X".

Caz 2. R ≠ nil
⎯ norm → ins(e',Absp〈cons(e,L),R〉)
⎯ 2i → Absp〈cons(e,L),cons(e',R)〉
= X'

8.3 Verificarea unor proprietăţi ale inelului

Să verificăm proprietatea ∀i∈Ν,X∈T Ring | ¬empty(X) • Prop(i,X), unde


Prop(i,X) =def top(movei(X)) = element(i,X)
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 35

Proprietatea este reprezentativă unui inel unidirecţional şi arată că după i


deplasări succesive ale vârfului unui inel nevid, elementul de la vârf este al i-lea
element din inelul iniţial. În acest fel, se precizează că deplasarea vârfului păstrează
ordinea elementelor din inel. Proprietatea foloseşte următorii operatori auxiliari:

- element: Ν × (T Ring\{void}) → T – element(i,X) reîntoarce elementul


i mod size(X) din inelul nevid X.

- elm: Ν × (T Ring\{void} → T – elm(i,X) reîntoarce elementul din poziţia i,


0 ≤ i < size(X), din inelul nevid X.

- size: T Ring → Ν – size(i,X) este numărul de elemente din inelul X.


Pentru a scurta scrierea, adoptăm #X ca notaţie alternativă a lui size(X).

Extinsă cu aceşti operatori, specificaţia tipului T Ring devine:

T Ring ::= void | ins(T, T Ring)

top: T Ring \{void} → T


move: T Ring\{void} → T Ring
del: T Ring\{void} → T Ring
empty: T Ring → bool

element: Ν × (T Ring \{void})→ T


elm: Ν × (T Ring\{void}) → T
size: T Ring → Ν

(1) empty(void) = 1
(2) empty(ins(e,X)) = 0
(3) top(ins(e,X)) = e
(4) del(ins(e,X)) = X
(5) move(ins(e,void)) = ins(e,void)
(6) move(ins(e,ins(e',X))) = ins(e',move(ins(e,X)))

(7) element(i,X) = elm(i mod size(X),X)


(8) elm(0,X) = top(X)
(9) elm((i+1),ins(e,X)) = elm(i,X), pentru i < size(X)
(10) size(void) = 0
(11) size(ins(e,X)) = 1+size(X)

Pentru verificarea Prop(i,X), sunt utile următoarele două propoziţii,


demonstrate prin inducţie structurală.

Propoziţia 8.1 ∀X∈T Ring | ¬empty(X) • P1(X), cu P1(X)=def #move(X))= #X

Caz de bază. X = ins(e,void).

#move(ins(e,void)) ⎯ 5 → #ins(e,void)

Pas de inducţie. X = ins(e,ins(e',X')). Ca ipoteză inductivă considerăm


adevărată proprietatea ∀e∈T • P1(ins(e,X')).

#move(ins(e,ins(e',X')))
⎯ 6 → #ins(e',move(ins(e,X')))
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 36

⎯ 11 → 1+ #move(ins(e,X'))
⎯ ip.ind → 1+#ins(e,X')
⎯ 11 → 2+#X'

#ins(e,ins(e',X'))
⎯ 11 → 1+#ins(e',X')
⎯ 11 → 2+#X'
„

Propoziţia 8.2 ∀i∈Ν,X∈T Ring | ¬empty(X) • P2(i,X), unde

P2(i,X)=def element(i,move(X)) = element(i+1,X)

Caz de bază. X = ins(e,void). Să verificăm P2(i,X), pentru i∈Ν ales


arbitrar.
element(i,move(ins(e,void)))
⎯ 5 → element(i,ins(e,void))
⎯ 7 → elm(i mod #ins(e,void),ins(e,void))
⎯ 11,10 → elm(i mod 1, ins(e,void))
⎯ mod → elm(0, ins(e,void))
⎯ 8,3 → e

element(i+1,X)
⎯ 7 → elm((i+1) mod #ins(e,void),ins(e,void))
⎯ 11,10 → elm((i+1) mod 1, ins(e,void))
⎯ mod → elm(0, ins(e,void))
⎯ 8,3 → e

Pas de inducţie. X = ins(e,ins(e',X')). Ca ipoteză inductivă considerăm că


este adevărată proprietatea ∀e∈T • P2(i,ins(e,X')), pentru i∈Ν ales arbitrar. Să
arătăm că proprietatea P2(i,X) este adevărată. Notăm:

LHS = element(i,move(X))
RHS = element(i+1,X)

LHS = element(i,move(ins(e,ins(e',X'))))
⎯ 6 → element(i,ins(e',move(ins(e,X'))))
⎯ 7,11,P1 → elm(i mod (2+#X'),ins(e',move(ins(e,X'))))
Caz (a): i mod (2+#X')=0. Atunci, avem (i+1) mod (2+#X') = 1.

LHS = elm(0,ins(e',move(ins(e,X')))) ⎯ 8,3 → e'

RHS = element(i+1,ins(e,ins(e',X')))
⎯ 7,11 → elm((i+1) mod (2+#X'), ins(e,ins(e',X')))
⎯ [(i+1) mod (2+#X') = 1] → elm(1, ins(e,ins(e',X')))
⎯ 9 → elm(0,ins(e'X'))
⎯ 8,3 → e'

Caz (b): i mod (2+#X')>0. Atunci, i>0 şi i mod (2+#X')= (i-1) mod (2+#X')+1.

LHS = elm((i-1) mod (2+#X')+1,ins(e',move(ins(e,X'))))


⎯ 9 → elm((i-1) mod (2+#X'),move(ins(e,X')))
⎯ [(i-1) mod (2+#X') < 1+#X'] →
elm(((i-1) mod (2+#X')) mod(1+#X'), move(ins(e,X')))
⎯ P1,7 → element((i-1) mod (2+#X'), move(ins(e,X')))
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 37

⎯ ip.ind → element((i-1) mod (2+#X')+1, ins(e,X'))


⎯ [i mod (2+#X') = (i-1) mod (2+#X')+1] →
element(i mod (2+#X'), ins(e,X'))

Caz (b1): i mod (2+#X') = 1+#X'

LHS = element(1+#X', ins(e,X'))


⎯ 7 → elm((1+#X') mod ins(e,X'), ins(e,X'))
⎯ 11 → elm((1+#X') mod (1+#X'), ins(e,X'))
⎯ mod → elm(0, ins(e,X'))
⎯ 8,3 → e

Totodată, i mod (2+#X') = 1+#X' ⇒ (i+1) mod (2+#X') = 0

RHS = element(i+1, ins(e,ins(e',X')))


⎯ 7,11 → elm((i+1) mod (2+#X'), ins(e,ins(e',X')))
⎯ [(i+1) mod (2+#X') = 0] → elm(0, ins(e,ins(e',X')))
⎯ 8,3 → e

Caz (b2): i mod (2+#X') < 1+#X'

LHS = element(i mod (2+#X'), ins(e,X'))


⎯ 7,11 → elm((i mod (2+#X')) mod (1+#X'), ins(e,X'))
⎯ mod → elm(i mod (2+#X'), ins(e,X'))
⎯ 9,[0 < i mod (2+#X') < 1+#X'] → elm(i mod (2+#X')-1, X')

RHS = element(i+1, ins(e,ins(e',X')))


⎯ 7,11 → elm((i+1) mod (2+#X'), ins(e,(ins(e',X')))
⎯ [(i+1) mod (2+#X') = (i mod (2+#X'))+1] →
elm((i mod (2+#X'))+1, ins(e,(ins(e',X')))
⎯ 9 → elm(i mod (2+#X'), ins(e',X'))
⎯ 9,[0 < i mod (2+#X') < 1+#X'] → elm(i mod (2+#X')-1, X')
„

Propoziţia 8.3 Există proprietatea ∀i∈Ν,X∈T Ring |¬empty(X) • Prop(i,X),


unde Prop(i,X)=def top(movei(X)) = element(i,X).

Demonstraţia este prin inducţie după i.

Caz de bază. i=0.

top(move0(X)) = top(X)
element(0,X) ⎯ 7 → elm(0 mod #X,X)
⎯ mod → elm(0,X)
⎯ 8 → top(X)

Pas de inducţie. i≥0. Ipoteza inductivă este ∀X∈T Ring|¬empty(X)•Prop(i,X).


Să arătăm că Prop(i+1,X) este adevărată, adică top(movei+1(X)) =
element(i+1,X)

top(movei+1(X)) = top(movei(move(X)))
⎯ ip.ind → element(i,move(X))
⎯ P2 → element(i+1,X)
„
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 38

8.4 O implementare Haskell a inelului

Programele de mai jos corespund aproape ad-litteram specificaţiei T Ring şi


ecuaţiilor de implementare, diferenţele de notaţie impuse de Haskell fiind minore. În
particular, programul corespunzător specificaţiei T Ring poate fi considerat ca o
implementare "abstractă" a inelului.

Rescrierea Haskell a specificaţiei inelului


module T_Ring where

-- Constructorii de bază: Void şi Ins


-- Limbajul impune nume ce încep cu litere mari

data Ring t = Void


| Ins (t, Ring t) deriving (Eq,Show)

-- Funcţii corespunzătoare constructorilor de bază, utile


-- pentru compatibilitate deplină cu implementarea inelului

void = Void
ins(e,x) = Ins(e,x)

-- Axiomele operatorilor (signaturile sunt sintetizate automat)

empty(Void) = True
empty(Ins(_,_)) = False
top(Ins(e,_)) = e
del(Ins(_,x)) = x

move(Ins(e,Void)) = Ins(e,Void)
move(Ins(e,Ins(e',x))) = Ins(e',move(Ins(e,x)))

-- Axiomele operatorilor necesari proprietăţilor P1,P2 şi Prop

element(i,x) = elm(i `mod` size(x), x)


elm(0,x) = top(x)
elm(i+1,Ins(_,x)) = elm(i,x)

size(Void) = 0
size(Ins(_,x)) = 1+size(x)

-- Proprietatile P1, P2 şi Prop din secţiunea 2.7.5, unde


-- move_i(n,x) este move aplicat de n ori inelului x

p1 x = size(x) == size(move(x))
p2(i,x) = element(i,move(x)) == element(i+1,x)
prop(i,x) = top(move_i(i,x)) == element(i,x)
where move_i(0,x) = x
move_i(i+1,x) = move_i(i,move(x))

-- Funcţie care construieşte un inel cu cheile unei liste


make_ring [] = Void
make_ring (e:l) = Ins(e,make_ring l)

-- Un inel
x = make_ring [1,2,3,4,5,6,7,8]
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 39

Aplicaţiile p1(x), p2(i,x) şi prop(i,x) reîntorc valoarea True pentru orice i≥0
si orice inel nevid x.

Codificarea Haskell a implementării inelului


module Ring where
-- O implementare a tipului T Ring in Haskell, ca o pereche de liste

-- Reprezentarea inelului ca pereche de liste cu elemente de tip t


data Ring t = Abs([t],[t]) deriving (Eq,Show)

normalize(l,r) = if r==[] then ([],(reverse l)) else (l,r)

-- Ecuaţiile de implementare a operatorilor inelului


void = Abs([],[])
ins (e,Abs(l,r)) = Abs(l,e:r)
del (Abs(l,r)) = Abs(normalize(l,(tail r)))
move (Abs(l,r)) = Abs(normalize((head r):l, (tail r)))
top (Abs(l,r)) = head r
empty (Abs(l,r)) = r == []

-- Axiomele operatorilor necesari proprietăţilor P1,P2 şi Prop

size(x) = if empty(x) then 0 else 1+size(del(x));;


elm(0,x) = top(x)
elm(i+1,x) = elm(i,del(x))
element(i,x) = elm(i `mod` size(x), x)

{-
Axioma elm(i+1,ins(e,x))) = elm(i,x) este rescrisa în stilul
elm(i+1,y) = elm(i,del(y)) folosind schimbarea de variabilă
y = ins(e,x) => x = del(y).

La fel s-a procedat şi pentru axioma size(ins(e,x)) = 1+size(x)


scrisă size(x) = 1+size(del(x))
-}

-- Proprietatile P1, P2 şi Prop din secţiunea 2.7.5, unde


-- move_i(i,x) este move aplicat de i ori inelului x

p1 x = size(x) == size(move(x))
p2(i,x) = element(i, move(x)) == element(i+1,x)
prop(i,x) = top(move_i(i,x)) == element(i,x)

move_i(0,x) = x
move_i(i+1,x) = move_i(i,move(x))

-- Funcţie care construieşte un inel cu cheile unei liste


make_ring [] = void
make_ring (e:l) = ins(e,make_ring l)

-- Un inel
x = make_ring [1,2,3,4,5,6,7,8]

Aplicaţiile p1(x), p2(i,x) şi prop(i,x) reîntorc valoarea True pentru orice


i≥0 şi orice inel nevid x. Evident, din moment ce implementarea respectă axiomele
specificaţiei T Ring, ea conservă şi proprietăţile inelului.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 40

8.5 O aplicaţie a inelului: sortare

Sortarea unei liste unidirecţionale folosind un inel de tip T Ring se aseamănă


cu sortarea prin inserţie directă a unui vector. Pentru simplitate, considerăm sortarea
nedescrescătoare a unei liste cu chei întregi, deci presupunem că tipul generic T din
specificaţiile T List şi T Ring este particularizat la tipul de date int.

O variantă simplă de sortare a unei liste nevide l constă în construirea unui nou
inel sortat prin inserţia elementului head(l) într-un inel sortat în aceeaşi manieră şi
care conţine elementele listei tail(l). Evident, pentru o listă vidă, inelul sortat este
vid. Operaţia de bază a sortării este

put: int × int Ring → int Ring

şi satisface axiomele
(p1) put(e,void) = ins(e,void)
(p2) put(e,ins(e',X)) = if e < e' then ins(e,ins(e',X))
else ins(e',put(e,X))

Să arătăm că, pentru X∈int Ring deja sortat nedescrescător, put(e,X)


produce un inel sortat nedescrescător. Pentru că avem nevoie de formalizarea noţiunii
de inel sortat nedescrescător, definim următoarele predicate:

ord: int Ring → bool şi


min: int × (int Ring) → bool

astfel încât:
(o1) ord(void) = 1
(o2) ord(ins(e,X)) = min(e,X) ∧ ord(X)

(m1) min(e,void) = 1
(m2) min(e,ins(e',X)) = e ≤ e' ∧ min(e,X)

De asemenea, considerăm adevărate proprietăţile

∀X∈int Ring, e∈int,e'∈int • P'(e,e',X)


∀X∈int Ring, e∈int,e'∈int • P"(e,e',X)
unde,
P'(e,e',X) =def min(e,put(e',X)) = e ≤ e' ∧ min(e,X)
P"(e,e',X) =def e ≤ e' ∧ min(e',X) ⇒ min(e,X)

Demonstraţiile sunt prin inducţie structurală, fiind lăsate ca exerciţiu.

Folosind ord, proprietatea esenţială pe care se bazează procesul de sortare


poate fi formulată ∀X∈int Ring, e∈int • P(e,X), cu

P(e,X) =def ord(X) ⇒ ord(put(e,X))

Propoziţia 8.4 ∀X∈int Ring • P(X). Demonstraţia este prin inducţie structurală.

Caz de bază: X = void. Să alegem e∈T arbitrar.


Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 41

P(e,void) = ord(void) ⇒ ord(put(e,void))


⎯ o1 → 1 ⇒ ord(put(e,void))
⎯ p1 → 1 ⇒ ord(ins(e,void))
⎯ o2 → 1 ⇒ min(e,void) ∧ ord(void)
⎯ m1,o1 → 1 ⇒ 1

Pas de inducţie: X = ins(e',X'), unde e'∈int şi X'∈int Ring sunt valori alese
arbitrar. Ca ipoteză inductivă, considerăm adevărată proprietatea P(e,X') pentru
e∈int ales arbitrar. Să arătăm că proprietatea P(e,X) este adevărată, anume că:

ord(ins(e',X')) ⇒ ord(put(e,ins(e',X')))

Dacă termenul ord(ins(e',X')) este fals, implicaţia este adevărată banal. Să


considerăm că ord(ins(e',X')) este adevărat. Atunci, conform axiomei (o2) avem
următoarele concluzii:

(c1) min(e',X') = 1
(c2) ord(X') = 1

Pentru ca implicaţia P(e,X) să fie adevărată trebuie ca termenul


ord(put(e,ins(e',X'))) să fie adevărat.

Caz 1. e < e'

ord(put(e,ins(e',X')))
⎯ p2 → ord(ins(e,ins(e',X')))
⎯ o2 → min(e,ins(e',X')) ∧ ord(ins(e',X'))
⎯ m2 → e ≤ e' ∧ min(e,X') ∧ ord(ins(e',X'))
⎯ Caz 1, c1, P"(e,e',X')→ ord(ins(e',X'))1
⎯ ipoteză → 1

Caz 2. e ≥ e'

ord(put(e,ins(e',X')))
⎯ p2 → ord(ins(e',put(e,X')))
⎯ o2 → min(e',put(e,X')) ∧ ord(put(e,X'))
⎯ ip.induct. P(e,X'), c2 → min(e',put(e,X'))2
⎯ P'(e',e,X') → e' ≤ e ∧ min(e',X')
⎯ Caz 2, c1 → 1
„
Considerând lista de sortat l = cons(l1,cons(l2,...,cons(ln,nil)...)),
atunci, prin inducţie după n şi conform propoziţiei (2.11), inelul

r = put(l1,put(l2,...,put(ln,void)...)

rezultă sortat nedescrescător. Funcţia care care construieşte inelul r este:

ring_sort: int List → int Ring


ring_sort(nil) = void
ring_sort(cons(e,L)) = put(e,ring_sort(L))

1
Termenul min(e,X') este adevărat conform implicaţiei P"(e,e',X')
2
Termenul ord(put(e,X')) este adevărat conform implicaţiei P(e,X')
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 42

Dacă dorim ca rezultatul sortării să fie o listă, inventăm o funcţie care descarcă
elementele unui inel (indiferent de tipul elementelor) într-o listă iniţial vidă.

ring_to_list: T Ring → T List


ring_to_list(void) = nil
ring_to_list(ins(e,X)) = cons(e,ring_to_list(X))

În final, funcţia de sortare este reprezentată de compunerea funcţiilor


ring_sort şi ring_to_list:

sort: int List → int List


sort L = (ring_to_list ο ring_sort)(L)
= ring_to_list(ring_sort(L))

Transcrierea Haskell a sortării, impune modificarea axiomelor funcţiilor put şi


3
ring_to_list. Prima axiomă a funcţiei put este transformată prin schimbarea de
variabilă x = void, impunând restricţia empty(x). Cea de a doua axiomă a lui put
este transformată folosind schimbarea de variabilă x = ins(e',X) şi observând, pe
baza axiomelor din specificaţia inelului, că: e' = top(x) şi X = del(x). Se obţine:

| ins(e,void), dacă empty(X)


put(e,X) = | ins(e,X), dacă ¬empty(X) ∧ e < top(X)
| ins(top(X),put(e,del(X))), dacă ¬empty(X) ∧ e ≥ top(X)

Similar, sunt transformate şi axiomele funcţiei ring_to_list:

| nil, dacă empty(X)


ring_to_list(X) = |
| cons(top(X),ring_to_list(del(X)))

Într-o primă variantă, codificarea Haskell a sortării urmăreşte îndeaproape


specificarea funcţiei sort.

module RingSort where


import RingImpl

sort l = (ring_to_list . ring_sort) l


where -- f . g este compunerea lui f cu g
ring_sort [] = void
ring_sort (e:l) = put(e,ring_sort(l))

put(e,x)
| empty(x) = ins(e,void)
| e < top(x) = ins(e,x)
| otherwise = ins(top(x),put(e,del(x)))

ring_to_list x
| empty(x) = []
| otherwise = (top x):(ring_to_list (del x))

3
Motivul îl constituie definirea constructorilor void şi ins ca funcţii implementate pe
baza constructorului Abs. Haskell nu acceptă ca parametrii formali ai funcţiilor să fie specificaţi
în forma unor aplicaţii de funcţii, aşa cum cer axiomele nemodificate ale lui put şi
ring_to_list. Totodată, codificarea din modulul RingSort are un avantaj: funcţionează
corect cu ambele implementări ale inelului, conforme modulelor T_Ring şi RingImpl.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 43

O variantă mai sofisticată foloseşte o prelucrare interesantă, dar comună în


limbajele de programare funcţionale. Prelucrarea corspunde următoarei funcţii:

foldr: ((T × T') → T') × T' × (T List) → T'


foldr(f,X,nil) = X
foldr(f,X,cons(e,L)) = f(e,foldr(f,X,L))

Prin urmare, aplicaţia foldr(put,void,l), unde l = cons(l1,cons(l2,...,


cons(ln,nil)...)), este echivalentă cu put(l1,put(l2,...,put(ln,void)...),
adică are efectul ring_sort(l).

module RingSort where


import RingImpl

sort l = ring_to_list(foldr put void l)


where
4
put e x -- vezi comentariul
| empty(x) = ins(e,void)
| e < top(x) = ins(e,x)
| otherwise = ins(top(x), put e (del x))

ring_to_list x
| empty(x) = []
| otherwise = (top x):(ring_to_list (del x))

La încărcarea şi execuţia programului, se poate obţine un rezultat de forma:

RingSort> sort [5,1,2,7,1,2,5,6,4]


[1,1,2,2,4,5,5,6,7] :: [Integer]

Modulele Haskell de mai sus implementează mai mult decât sortarea unei liste
de întregi. Ele descriu sortarea unei liste cu elemente de orice tip peste care este
definită o relaţie de ordine. Generalitatea este vizibilă din signatura Haskell a funcţiei
sort, sintetizată automat, anume:

RingSort> :type sort


sort :: Ord t => [t] -> [t]

Signatura arată că tipul t trebuie să facă parte din clasa Ord, clasa tipurilor
peste care este definită o relaţie de ordine. Astfel, se poate scrie:

RingSort> sort [True,False]


[False,True] :: [Bool]
RingSort> sort [2.1,3.0,3.14,0.1,4.5]
[0.1,2.1,3.0,3.14,4.5] :: [Double]

4
foldr cere ca put să fie o funcţie binară. În Haskell, put(e,x) defineşte o funcţie cu
un singur parametru din mulţimea produs int × int Ring, în timp ce put e x descrie o funcţie
binară cu primul parametru de tip int şi al doilea parametru de tip int Ring. Diferenţa este
mai subtilă: o funcţie binară poate fi aplicată parţial. Aplicarea asupra primului parametru
produce o funcţie unară care aşteaptă cel de al doilea parametru. Din program, se vede că
însăşi foldr este o funcţie ternară care are proprietăţi similare.
Cristian Giumale/ Sisteme de tipuri şi programare funcţională/ Note de curs 44

Referinţe
Diller Antoni. (1994). Z: An Introduction to Formal Methods, (2nd edition), John
Wiley & Sons, Inc.
Stănăşilă Octavian. (1978). Elemente de matematică discretă, Litografia
Universităţii Politehnice Bucureşti.
Turner John and McCluskey T. Lee. (1994). The Construction of Formal
Specifications, An Introduction to the Model-based and Algebraic
Approaches, McGraw-Hill International Series in Software Engineering.

S-ar putea să vă placă și