430 likes | 612 Views
Definició de nous tipus. Es habitual en molts llenguatges de programació la possibilitat de donar noms a tipus construïts a partir del sistema habitual de tipus. Així, a SML podem fer type arc = string * string * int type parell = (int x int) list
E N D
Definició de nous tipus • Es habitual en molts llenguatges de programació la possibilitat de donar noms a tipus construïts a partir del sistema habitual de tipus. Així, a SML podem fer • type arc = string * string * int • type parell = (int x int) list • En molts llenguatges de programació aquesta és l’única opció. • En llenguatges moderns és possible definir nous tipus, les seves constants i els constructors, sense necessitat de fer servir els tipus predefinits. • Per tal de fer-ho necessitem una forma d’especificar el nom del tipus, definir-ne els valors i determinar els valors de una forma canònica per tal de mostrar-los. Això ho podrem fer via el concepte de constructor.
Nous tipus • Els constructors que veurem seran o bé constants del nou tipus, o bé funcions que produiran com a resultat nous valors del mateix tipus. Les funcions serviran per a construir nous valors i també serviran de representació canònica dels valors. • El problema bàsic és definir els contructors. En termes de què? Altres llenguages (v.g. Pascal) permeten definir nous tipus (v.g. records) a partir de detalls de representació en memòria. Nosaltres simplement definirem el noms dels constructors i deixarem els detalls de representació als compiladors. • A més, no necessitarem definir funcions de selecció de valors ja que podrem definir funcions sobre ells via acarament de patrons. • Per tant, per tenir nous tipus la única cosa necessària és poder definir (1) el nom, (2) els noms dels constructors, i (3) els tipus dels arguments del constructor.
Tipus amb constants • Alguns tipus consisteixen en l’enumeració dels seus valors, com en Pascal. datatype vents = Mestral | Garbi | Migjorn | Xaloc datatype peixos = Rap | Neru | Llobarro | Dentol | Orada datatype salses = Allioli | Romesco | Holandesa | Mahonesa datatype coccio = Forn | Brasa | Fregit | Alvapor | Bullit datatype acompanyament = Patates | Pesols | Pure Ara els valors (constants) definits tenen el tipus al qual pertanyen. Mestral:vents, Neru:peixos, Brasa:coccio, Romesco:salses Els nous tipus es poden combinar amb tipus preexistents. (Rap, Romesco, Forn, “esbo”):peixos*salses*coccio*string
Us dels nous tipus datatype bool = true | false datatype unit = () • Les constants introduïdes fins • ara (predefinides) són: Tots els tipus definits per enumeració són tipus amb igualtat. Per tant, es permeten comparacions com: Mestral = Garbi, member [Llobarro, Neru] Llobarro = true Tanmateix, les constants poden fer-se servir en patrons fun combi (Rap, Romesco) = “bonissim” | combi (Llobarro, x) = “bo” | combi (Dentol, Mahonesa) = “regular” | combi (x, y) = ““
Constructors amb arguments • Un constructor amb un argument és una funció que produeix un valor del nou tipus quan rep un argument del tipus adient. El tipus de l’argument del constructor es defineix quan es defineix el nom del nou tipus. datatype plat = Plat of (peixos * coccio) plat és el nom del nou tipus i Plat és el nom del constructor. El constructor té el tipus següent: Plat : (peixos*coccio)->plat i podem construir expressions com: Plat(Llobarro, Alvapor) Letval combi = (Dentol, Fregit) in Plat combi end map Plat [(Orada,Alvapor),(Neru,Fregit),(Dentol,Bullit)]
Paper dual dels constructors • Les expressions anterior, un cop avaluades, donen com a resultats • Plat(Llobarro, Alvapor) • Plat(Dentol, Fregit) • [Plat(Orada, Alvapor), Plat(Neru, Fregit), Plat(Dentol, Bullit)] • En aquestes avaluacions es veu el paper dual dels constructors: com a funcions i com a components dels valors canònics. • En general si C: T -> T’ és un constructor i E és una expressió de tipus T que avalua en el valor a llavors C E avalua a la forma canònica C a. • Un valor de tipus (peixos*coccio), per exemple (Llobarro, Alvapor), no és un valor del tipus plat. Tots els valors de tipus plat han de tenir la forma Plat(x,y).
Ús dels constructors en acaraments datatype alçada = Al of int datatype pes = Pe of int Al(180) + Pe(100) donarà error! • La importància del paper dual dels constructors es veu en les definicions per acarament, com les següents: fun tall (Plat(peix, coccio)) = peix fun company(Plat(peix, coccio)) = coccio fun alabrasa (Plat(peix, Brasa)) = true | alabrasa (Plat(peix, coccio)) = false Es considera una bona pràctica de programació definir nous tipus; àdhuc en els casos en que existeix un tipus predefinit que ens podria servir. La raó és la millora en la llegibilitat i la protecció contra operacions no desitjades.
Tipus amb diversos constructrs • Així podem veure el tipus dels constructors: • Autor: string -> autor • Anonim: autor • Butxaca: (string * autor list) -> llibre • Pasta: (string * autor list) -> llibre datatype autor = Autor of string |Anonim datatype llibre = Butxaca of (string * autor list) |Pasta of (string * autor list) fun titol(Butxaca(s, l)) = s | titol(Pasta(s, l)) = s fun autors(Butxaca(s, l)) = l | autors(Pasta(s, l)) = l
Igualtat • Si els components d’un tipus són tipus amb igualtat també ho serà el tipus definit a partir d’ells. • (Pasta(a, b) = Pasta(c, d)) == (a = c) & (b = d) • (Pasta(a, b) = Butxaca(c, d)) == false • Podem entendre els tipus així definits com a unions disjuntes dels components arguments dels constructors. Així, si volguéssim definir llistes amb components heterogenis podriem fer-ho a traves de la definició d’un nou tipus que barregés diferents tipus amb constructors diferents. datatype mix = Int of int | Str of string val mix1 = [Int 5, Str “pepet”, Str “anna”, Int 6] fun getint [] = [] | getint (Int n :: x) = n::getint x | getint (Str s :: x) = getint x
Tipus recursius • Les definicions de tipus es consideren recursives per defecte, es a dir, el nom del tipus essent definit pot aparéixer a la part dreta de la definició. datatype fitxer = Text of string list | Directori of fitxer list val fitxer1 = Directori[Text s1, Text s2, Directori[Text s3, Directory []], Text s4] Amb definicions de tipus recursives es poden definir conjunts infinits de valors. datatype number = Zero | Succ of number fun numplus (Zero, m) = m | numplus (Succ n, m) = Succ(numplus (n, m))
Tipus mutuament recursius • Podem definir més d’un tipus alhora i definir-los de forma mútuament recursiva. datatype part = Basicpart of int | Compoundpart of (int * components list) and components = Quantity of (part * int) Els constructors tenen els tipus següents: Basicpart: int -> part Compoundpart: (int * components list) -> part Quantity: (part * int) -> components Així, tenim: val motor = Basicpart 32 val roda = Basicpart 35 val cotxe = Compoundparts (36, [Quantity(motor, 1), Quantity(roda, 4)]
Operadors de tipus datatype int_matrix = Matrix of (int list list) • Els tipus bàsics (int, string, ...) es poden veure com a casos degenerats de constructors de tipus (sense arguments). Els operadors * i -> són paramètrics sobre arguments de tipus tipus. • Podem definir nous tipus paramètrics de la mateixa manera que fins ara, però precedint el nom del nou tipus per una n-pla de variables de tipus. El nou tipus així definit s’anomena tipus paramètric. Les variables de tipus es poden fer servir com a arguments dels constructors, fent que aquests siguin polimòrfics. Així, per exemple, podem generalitzar la definició de matrius d’enters: definint: datatype ‘a matrix = Matrix of (‘a list list)
Més tipus paramètrics • Ara, per a qualsevol tipus T, també tenim el tipus T matrix. Essent, per tant, el constructor Matrix del tipus polimòrfic següent: • Matrix: (‘a list list) -> ‘a matrix • Podem definir ara: • Matrix [[1,2],[3,4][5,6],[7,8]]: int matrix • Matrix[[Matrix [[true,false],[false,false]]]]: (bool matrix) matrix • ara podem veure que l’operador de construcció de llistes no és necessari entendre’l com a un operador predefinit especial. datatype ‘a list = :: of ‘a * ‘a list | [] Tots els tipus paramètrics poden ésser definits d’aquesta manera excepte els operadors bàsics * i ->.
Example • El tractament del fracàs representa un problema en els llenguatges fortament tipats. Per exemple, en un problema de cerca, en cas de tenir èxit podem retornar un component de l’estructura, però en cas de fracàs hem de retornar algun valor especial que normalment no és del tipus dels components de l’estructura. Una solució més elegant és fer servir l’operador de tipus següent: • Una funció que cerqui un valor de tipus ‘a retornarà un valor de tipus ‘a possible. Ok(n) en cas d’èxit o Fail en cas de fracàs. datatype ‘a possible = Ok of ‘a | Fail fun findpos [] = Fail | findpos (n::x) = if n>0 then Ok(n) else findpos x fun report Fail = “Fracas en trobar un positiu” | report (Ok n) = “The value is:” ^ stringofint n
Arbres binaris 1 7 9 4 2 /\ /\ datatype ‘a bintree = Lf of ‘a | /\ of (‘a bintree * ‘a bintree) Lf /\ /\ 1 Lf Lf Lf Lf 2 4 7 9 • Els arbres i els grafs són unes de les estructures més comunes en informàtica. Començarem amb l’estudi dels arbres binaris. • Per definir l’operador de tipus per representar arbres binaris només ens hem de fixar en la forma en que aquests es poden construir. val bt = Lf 1 /\ ((Lf 2 /\ Lf 4) /\ (Lf 7 /\ Lf 9))
Funcions sobre arbres binaris fun left (t1 /\ t2) = t1 | left (Lf a) = error “left undefined” fun right (t1 /\ t2) = t2 | right (Lf a) = error “right undefined” fun isleaf (Lf a) = true | isleaf (t1 /\ t2) = false fun leftmostleaf (t1 /\ t2) = leftmostleaf t1 | leftmostleaf (Lf a) = a fun sumoflf (t1 /\ t2) = sumoflf t1 + sumoflf t2 | sumoflf (Lf a) = a ;; I així tenim leftmostleaf ((Lf 2 /\ Lf 4) /\ (Lf 7 /\ Lf 9)) == leftmostleaf (Lf 2 /\ Lf 4) == leftmostleaf Lf 2 == 2
Operacions d’ordre superior fun btreeop f g (Lf a) = f a | btreeop f g (t1/\t2)= g(btreop f g t1)(btreeop f g t2) val sumoflf = btreeop I plus fun btreemap f = btreeop (Lf o f) join and join t1 t2 = t1 /\ t2 btreemap (plus 1) (Lf 5 /\ (Lf 2 /\ Lf 8)) == (Lf 6 /\ (Lf 3 /\ Lf 9)) • Moltes operacions sobre arbres impliquen el seu recorregut i l’aplicació de determinades funcions sobre les fulles, els resultats de les quals són combinats amb una altra funció. Utilitzarem programació d’ordre superior per fer-ho. Així operacions definides anteriorment es poden veure de forma simple amb aquesta funció: Altres impliquen el manteniment de l’estructura de l’arbre binari però aplicant-hi una funció a les fulles. Vegeu:
Arbres amb valors als nodes interns datatype ‘a tree = Empty | Tr of (‘a tree *’a * ‘a tree) fun leaf a = Tr (Empty, a, Empty) i així val tree1 = leaf 3 val tree2 = Tr(leaf 2, 5, leaf 6) • El primer constructor és un cas degenerat d’arbres sense nodes. Per evitar haver d’escriure en excés introduïm: Vegeu ara funcions sobre arbres: fun flatten Empty = [] | flatten (Tr(t1,a,t2)) = flatten t1@[a]@flatten t2 fun treemap f Empty = Empty | treemap f (Tr(t1, a, t2)) = Tr(treemap f t1, f a, treemap f t2)
Recorreguts d’arbres • Els arbres binaris tenen tres formes d’ésser recorreguts: val inorder = flatten fun preorder Empty = [] | preorder (Tr(t1, a, t2)) = [a] @ preorder t1 @ preorder t2 fun postorder Empty = [] | postorder (Tr(t1, a, t2)) = postorder t1 @ postorder t2 @ [a] 6 inorder t = [3,4,5,6,7,8] preorder t = [6,4,3,5,7,8] postorder t = [3,5,4,8,7,6] 4 7 8 5 3
Ordre superior i ordenacións • Aquesta mena d’arbres s’utilitza habitualment per a mantenir l’ordre d’elements de manera més eficient que els algorismes sobre llistes. Un cop ordenats els elements es fa un aplanament per obtenir la llista ordenada. fun treeop c g Empty = c | treeop c g (Tr(t1, a, t2)) = g(treeop c g t1, a, treeop c g t2) fun treeinsert Empty m = leaf m | treeinsert (Tr(t1, n, t2)) m = if (lessthan n) m then Tr(treeinsert t1 m, n, t2) else Tr(t1, n, treeinsert t2 m) fun treesort x = flatten (accumulate treeinsert Empty x)
Arbres més generals • Els tipus ‘a bintree i ‘b tree són casos particulars de: datatype (‘a, ‘b) abtree = Leaf of ‘a | Node of ((‘a, ‘b) abtree * ‘b * (‘a, ‘b) abtree) Són arbres que tenen valors de tipus ‘b als nodes i valors de tipus ‘a a les fulles. Quan ‘b és unit no hi ha informació als nodes. Així Leaf i Node juguen el paper de Lf i /\. Per tant ‘a bintree és isomòrfic amb (‘a, unit) abtree. Si és a les fulles que tenim unit llavors Node juga el paper de Tr i Leaf () juga el paper de Empty; així (unit, ‘b) abtree és isomòrfic amb ‘b tree. Una altra abstracció és arbres amb un nombre arbitrari de fills: datatype ‘a vtree = Vtree of ‘a * (‘a vtree) list type ‘a forest = (‘a vtree) list
Grafs • Un graf dirigit es defineix com a un conjunt de nodes i fletxes (arcs) lligant parelles de nodes Hi han diferents variants, grafs amb etiquetes als arcs, grafs no dirigits, multigrafs (amb més d’un arc entre dos nodes), acíclics, etc. El conjunt de nodes es considera finit habitualment, tot i que es poden considerar grafs infinits. Definirem un tipus particular de graf i algorismes de cerca a sobre d’ell. Per tal de definir algorismes de cerca ens concentrarem en representar l’accessibilitat dels nodes veins a un node donat.
Tipus de dades graf • ‘a és el tipus dels nodes del graf. Els grafs es construeixen a partir d’una funció successor que donat un element de tipus ‘a ens dóna la llista dels seus nodes veins. • Graf: (‘a -> ‘a list) -> ‘a graph • Per exemple, veiem la construcció de dos grafs a partir de dues funcions successor. datatype ‘a graf = Graf of (‘a -> ‘a list) fun succ1 b = [not b] val graf1 = Graf (succ1) fun succ2 n = if n < 2 or n > 20 then [] else (n-1)::succ2 (n div 2) val graf2 = Graf (succ2)
Cerca • La cerca d’un graf es fa a partir d’un node. La funció successor ens donarà els successors del node per a continuar la cerca a partir d’ells. Guardant els nodes visitats en una llista evitarem repetir la cerca a partir de nodes ja visitats. fun depthsearch (Graph succ) p startnode = letfun find visited [] = Fail | find visited (a::x) = if member visited a then find visited x elseif p a then Ok a else find (a::visited) (succ a @ x) in find [] [startnode] end
Cerca II • Fixeu-vos que la única diferència entre els dos algorismes de cerca és la forma d’enmagatzemar els nodes que resten per visitar. En depthfirst els successors d’un node són visitats primer, en breadthfirst al final. fun breadthsearch (Graph succ) p startnode = letfun find visited [] = Fail | find visited (a::x) = if member visited a then find visited x elseif p a then Ok a else find (a::visited) (x @ succ a) in find [] [startnode] end
Associacions • Les associacions, igual que els grafs, són estructures de dades que contenen funcions com a components. • Un concepte natural en llenguatges de programació són els conjunts de vinculacions, també anomenats associacions. En realitat són funcions parcials. Per exemple, una llista de noms de persones i edats, ens permet, donat un nom, obtenir la seva edat; a més, podem definir operacions per a afegir noves parelles, canviar valors, etc. Una solució tradicional és considerar les associacions com a llistes sobre les que es cerquen valors: datatype (‘a, ‘b) associations = Assoc of (‘a * ‘b) list * (‘a, ‘b) associations | Emptyassoc fun assoc list oldassocs = Assoc(list, oldassocs)
Implementació funcional • Hi ha una manera més natural d’implementar les associacions, i és a través de tipus funcionals. type (‘a, ‘b) associations = ‘a -> ‘b fun assoc [] oldassocs a = oldassocs a | assoc ((a1,b1)::rest) oldassocs a = if a = a1 then b1 else assoc rest oldassocs a assoc: (‘‘a * ‘b) list -> (‘‘a -> ‘b) -> ‘‘a -> ‘b fun emptyassoc a = error “cap assocoacio trobada” val exemple1 = assoc [(1, “a”),(2,”b”)] emptyassoc val exemple2 = assoc [(2,”dos”),(5,”pep”)] exemple1 ;;o bé directament amb funcions val atlocation = assoc [(1,10),(2,20)] I val tostring = assoc [] chr Aquests darrers exemples mostren la similitut entre les associacions i els arrays dels llenguatges imperatius.
Estructures de prioritats • Hi ha molts conceptes de programació (cues, piles, llistes de prioritat) que es poden unificar amb una estructura de dades basada en components funcionals. • 1) Les piles afegeixen i eliminen elements de forma LIFO. • 2) Les cues afegeixen i eliminen elements de forma FIFO. • 3) Les llistes amb prioritat afegeixen i eliminen elements segons un ordre de prioritat. • Podriem fer tipus diferents per a cadascuna de les estructures. Ara bé, també es pot fer amb una estructura que permeti: • 1) Afegir nous elements per a obtenir una nova estructura. • 2) Obtenir un element a partir d’una estructura deixant una estructura del mateix tipus. • 3) Calcular quants elements hi són representats.
Definició del tipus de dades • datatype ‘a priority = Prio of • (‘a -> ‘a priority * • unit -> (‘a * ‘a priority) * • int) • Els dos components funcionals s’utilitzen per a afegir i eliminar components. El tercer component s’utilitza per a mantenir el nombre d’elements presents en l’estructura. • Ja que el tipus unit només té un valor, (), ens proporciona un argument buit per a la segona funció. La rao per no col.locar directament el resultat de la funció en comptes de la funció s’explica per la possibilitat de definir avaluació mandrosa en llenguatges estrictes (ho veurem més endavant). • Estem definint les funcions per afegir i eliminar components d’una estructura com a components locals de l’estructura en un estil orientat objecte.
Piles fun additem (Prio (f, g, n)) a = f a fun getitem (Prio (f, g, n)) x = g () fun sizestruct (Prio (f, g, n)) = n ;; amb tipus additem: ‘a priority -> ‘a -> ‘a priority getitem: ‘a priority -> (‘a * ‘a priority) sizestruct: ‘a priority -> int • Abans de definir valors del tipus de dades definit necessitem definir la representació interna a partir de la qual definir els valors. fun mkstack (list, n) = letfun f a = mkstack (a::list, n+1) fun g () = if n < 1 then error “underflow” else (hd list, mkstack (tl list, n-1) in Prio(f, g, n) end val pilabuida = mkstack ([], 0)
Encapsulament • Ens interessa que l’únic ús de la funció mkstack es faci a partir de la pila buida. Així, encapsulem: local fun mkstack (list, n) = letfun f a = mkstack (a::list, n+1) fun g () = if n < 1 then error “underflow” else (hd list, mkstack (tl list, n-1) in Prio(f, g, n) end in val emptystack = mkstack ([], 0) end Ara podem definir piles a partir de la pila buida i les operacions additem i getitem.
Cues • Le cues les definim d’una manera un pel diferent, malgrat que tornem a fer servir un enter i una llista com a representacions internes. La diferència rau en el fet que l’enter no representa la longitut de la llista sinó que és una fita inferior del nombre d’elements. local fun mkqueue (list, n) = letfun f a = mkqueue (a::list, n+1) fun g () = if n < 1 then error “underflow” else (select n list, mkqueue (list, n-1) in Prio(f, g, n) end in val emptyqueue = mkqueue ([], 0) end Els elements són afegits pel capdevant de la llista però no són eliminats. El valor n ens dóna la posició de l’element a treure.
Prioritats • Si suposem una funció before:‘a -> ‘a -> bool que ens dóna un ordre total sobre els elements de ‘a. fun emptyprio (before) = letfun mkprio (list, n) = letfun f a = mkprio (insertwrt before a list, n+1) fun g () = if n < 1 then error “underflow” else (hd list, mkprio (tl list, n-1) in Prio(f, g, n) end in mkprio ([], 0) end fun insertwrt before a [] = [a] | insertwrt before a (b::x) = if (before b) a then a::b::x else b::(insertwrt before a x) Si before b a és sempre cert tenim piles; si és sempre fals tenim cues.
Problemes i solució • Les definicions locals poden ésser redefinides a nivell superior i per tant poden donar lloc a resultats erronis. Això pot ser convenient si són resultats volguts, però poden donar problemes si no és així. • Quan definirem els tipus abstractes veurem que podem definir totes les funcions additem getitem, emptystack, etc, en el mateix moment en que definim els tipus. • Localitzarem totes les definicions i evitarem que el programador pugui introduir, per exemple, estructures de prioritats amb representacions internes diferents.
Algebres lliures datatype t = C0 | C1 of t | C2 of t * t • La definició matemàtica d’àlgebra és la d’un conjunt de valors junt amb un conjunt de funcions que operen a sobre d’ells. • La construcció de l’algebra lliure és una manera de construir l’àlgebra a partir d’un conjunt de constructors i la informació de llurs tipus. Ho veurem amb un exemple. t és el tipus definit, C0, C1 i C2 són els constructors. Si classifiquem els termes segons la complexitat (profunditat), tenim a primer nivell (profunditat 1): C0. A segon nivell: C1(C0), C2(C0, C0). A profunditat tres tenim:C1(C1(C0)), C1(C2(C0,C0)), ... L’àlgebra es diu generada lliurement a partir del conjunt de valors i dels constructors.
Inducció estructural • Donada una definició d’un nou tipus t que introdueix els constructors C0, ..., Cn tenim el següent: • Principi d’inducció estructural per al tipus t • Per tal de provar que P(x) es satisfà per tots els valors x:t. • és suficient provar que per a qualsevol constructor Ci: • (1) si Ci és una constant llavors P(Ci) és cert; • (2) si Ci és un constructor amb un argument de tipus: • T1 x T2 x ... x Tj (j >0) • llavors P(Ci(x1, ..., xj)) se segueix de les assumpcions P(y1), ..., P(yk), on y1, ..., yk són els membres de x1, ..., xj de tipus t. • Assumim que Tk (1 <= k <= j) es o bé t o no involucra t. • (És un cas particular de la inducció completa prenent l’ordre ben fundat • de les subexpressions.)
Exemple: Arbres binaris datatype ‘a bintree = Lf of ‘a | /\ of (‘a bintree * ‘a bintree) • Per tal de fer una prova per inducció estructural de P(x) per a tot x:T bintree, hem de provar: • (1) P(Lf(a)) es verifica per a tot a: T, • (2) P(t1 /\ t2) es verifica assumint P(t1) i P(t2). • Considerem, per exemple, la funció fun reflect (Lf a) = Lf a | reflect (t1 /\ t2) = (reflect t2) /\ (reflect t1) Volem provar reflect(reflect x) = x
Prova • Cas base: • reflect(reflect(Lf a)) = reflect(Lf a) = Lf a • Cas inducció: • reflect(reflect t1) = t1 • reflect(reflect t2) = t2 • és fàcil veure • reflect(reflect(t1 /\ t2)) = (def reflect) • reflect((reflect t2) /\ (reflect t1)) = (def reflect) • (reflect(reflect t1)) /\ (reflect(reflect t2)) = (HI) • t1 /\ t2
Exemple: Arbres datatype ‘a tree = Empty | Tr of (‘a tree *’a * ‘a tree) • Per tal de fer una prova per inducció estructural P(x) per a tot x:T tree, hem de provar: • (1) P(Empty) es verifica, • (2) P(Tr(t1, a, t2)) es segueix de P(t1) i P(t2) per a tot a:T i t1, t2 : ‘a tree. • Considerem, per exemple, la funció fun orderedtree (Empty) = true | orderedtree (Tr(t1,a,t2)) = alltree (lessthan a) t1 & alltree (greatereq a) t2 & orderedtree t1 & orderedtree t2 and alltree p (Empty) = true | alltree p (Tr(t1,b,t2)) = alltree p t1 & p b & alltree p t2 and greatereq a b = not (lessthan a b)
Recordem la funció d’inserció ordenada en arbres: fun treeinsert Empty m = leaf m | treeinsert (Tr(t1, n, t2)) m = if (lessthan n) m then Tr(treeinsert t1 m, n, t2) else Tr(t1, n, treeinsert t2 m) Provarem que per a tot t:int tree and n:int if orderedtree t == true then orderedtree (treeinsert t n) == true Cas base: orderedtree(treinsert Empty n) == orderedtree(Tr(Empty, n, Empty)) == true Pas d’inducció: Suposem (1) orderedtree(t1) == true implies orderedtree(treeinsert t1 n) == true (2) orderedtree(t2) == true implies orderedtree(treeinsert t2 n) == true
Prova • hem de provar que de (3) orderedtree(Tr(t1, a, t2)) == true • es pot obtenir (4) orderedtree (treeinsert (Tr(t1, a, t2)) n) == true • Suposarem (3) i analitzarem dos casos • Cas n < a: (4) es simplifica a • orderedtre(Tr(treeinsert t1 n, a, t2)) ==true • això és equivalent a la conjunció de les sentències següents: • (5) orderedtree(treeinsert t1 n) == true • (6) orderedtree(t2) == true • (7) alltree (greatereq a) t2 • (8) alltree (lessthan a) (treeinsert t1 n) • (5, 6, 7) segueixen de (3) i (1). (8) segueix del lema següent: • alltree p t == true and p n == true implies alltree p (treeinsert t n) == true • Cas n>= a: anàleg a partir de (2) en comptes de (1) i del lema previ.
Forma general de definició de tipus datatypeTVars1 Top1 = C11ofT11|C12ofT12 ...|C1n1ofT1n1 and TVars2 Top2 = C21ofT21|C22ofT22 ...|C2n2ofT2n2 ... and TVarsm Topm = Cm1ofTm1|Cm2ofTm2 ...|CmnmofTmnm • Topi són els nous operadors de tipus. TVarsi són sequències de variables de tipus. Tij és una expressió de tipus. Cij és un nou constructor de tipus Tij ->(TVarsi Topi). Qualsevol de les expressions ofTij pot ser suprimida. Tots els constructors en la definició han de ser diferents. Les expressions de tipus poden utilitzar tipus definits prèviament però també referències recursives a Top1...Topm i també a les variables de la seva part esquerra TVarsi . Per tant les variables poden repetir-se ja que el seu abast és només la part dreta. datatype ‘a point = Point of ‘a and ‘a line = Line of (‘a point * ‘a point) ;;és equivalent a datatype ‘a point = Point of ‘a and ‘b line = Line of (‘b point * ‘b point)
Localitat i errors let datatype bool = true | false in true & false end • Aquesta expressió donarà error ja que la redefinició de bool crea un nou tipus i fa que l’operació & no estigui definida per aquest nou tipus. Aquesta forma de definició de nous tipus es diu generativa. • Problemes similars apareixen quan recarreguem un fitxer que conté definicions de tipus. Al recarregar, objectes creats anteriorment (funcions) i utilitzats en el nou contexte poden donar errors de tipus. let datatype T1 = Localval1 | Localval2 in Localval2 end El resultat d’aquesta expressió seria Localval2:T1, però que vol dir això en el contexte extern? El resultat hauria de ser un error. Els modules de SML proporcionaran un mecanisme per controlar l’abast de la definició de tipus.