A CLU igen hatékony eszköze, hogy típuskonstrukciókat lehet készíteni. Ez a példa egy rekurzióra épített fa típust valósít meg. A rekurzió abban nyilvánul meg, hogy a típus egyszerre reprezentál egy csomópontot és az alatta található részfát, egy teljes fa és a részfái között nincs semmi különbség csak az, hogy a gyökérnek nincs szülője. A megvalósítás rekurzív típusdefiníciót és rekurzív eljáráshívásokat használ.Az ilyen típusú (mutable) változók a fák egy-egy csomópontjára mutatnak, több változó is mutathat egy csomópontra, vagy egy fának más-más csomópontjaira. Bármennyi fát készíthetünk a new eljárással és ezek a fák összekapcsolhatók, vagy szétvághatók. Ha egy fáról "levesszük" az összes hivatkozást, akkor nincs mód arra, hogy újra hivatkozzunk rá. Ekkor az adott fa elveszett a program számára (a CLU-ban nincs destruktor, ez a módja, hogy eldobjunk egy objektumot - ezután a szemétgyűjtő fogja kitörölni). Ebben a típusban nincs üres fa (null pointer), azt a fát nevezzük üresnek, amelyik csak egy csomópontból áll. (A CLU egyáltalán nem ismeri az üres hivatkozás (null pointer) fogalmát, mivel pointer típusa sincs. Minden változó egy létező objektumra mutat, kivéve, ha még nem kapott értéket, de ekkor a ráhivatkozás mindig futási hibát okoz.)
A típusban a gyerekek között nincs semmilyen kapcsolat (pl. sorrend), az egyetlen reláció a szülő-gyerek reláció, az összes funkció ennek a relációnak a kiterjesztése. (Ennek következménye, hogy pl. nincs "i. gyerek" eljárás, csak "gyerekek" eljárás - ez egy iteráció). A típus a szülő-gyerek reláció absztrakciója. (Tulajdonképpen ez nem is fa, hanem fenyő, azaz irányított fa.) Viszonylag könnyen módosítható lehetne ez a típus, hogy irányított, körmentes gráfot valósítson meg, csak azt kell megengedni, hogy egy csomópontnak több szülője is lehessen. Ez a típus a parciális rendezési reláció absztrakciója lenne.
A megvalósított típus mutable. (Ez abból adódik, hogy a reprezentáció típusa is mutable.) A beépített összetett típusoknak van mutable és immutable változata is, de én személy szerint nem látom értelmét, hogy ennek a típusnak is legyen immutable változata. Ugyanis kihasználjuk azt, hogy ugyanarra a fa objektumra egyszerre több hivatkozás is történhet. Ha ez immutable típus lenne, akkor minden részfára való hivatkozás az adott részfából készített másolatot eredményezne. Ráadásul, a szerkezetéből adódóan, minden részfa tárolná a szülőjének a részfáját is, meg a szülője szülőjének a részfáját, stb. egészen a gyökérig. A szülő és a gyerek részfája is független lenne egymástól, azaz az első konstruktor művelet után már nem is felelnének meg egymásnak. A heterogén (félig mutable, félig immutable típus) sem megoldás - ez valójában immutable csomópontokat jelentene, melyeket "dinamikusan" kötünk egymáshoz -, mivel egy csomópont értékadás után a csomópont megduplázódik, így a szülőjének megduplázódik az adott gyereke, a gyerekeknek pedig két szülője lenne, ami valójában az invariáns megsérülését jelentené. Valószínű, hogy egy immutable fa típust inkább úgy kellene megvalósítani, hogy a "fa" és "csomópont" fogalmakat ketté kellene választani - két típusra. Ez viszont nem a fenti mutable fa típus immutable változata lenne.
A megvalósítás tartalmaz egy szintaktikus cukor túlterhelést (overloading). Azzal, hogy az érték beírására és kiolvasására egy csomópontból a set_value és get_value neveket adtuk, a szintaktikus cukrok természete miatt használhatjuk a tree.value := elem és elem := tree.value formákat. Ezek eredetileg a record típushoz tartozó szintaktikus cukrok voltak. (Ezt lehetett volna fokozni (pl. tree.parent, tree.size, tree.move_to := ptree (!), stb.), de beértük ennyivel. Az új cukor egyébként is lehetővé teszi a tree!parent, tree!size, tree!move_to(ptree) hivatozásokat.)
A generáló típusnak (a fa csomópontjaiban található értékek) az egyetlen megszorítása, hogy legyen equal eljárása (függvénye). Ez akkor jelenthet gondot, ha itt saját típuskonstrukciót akarunk használni. Általában minden típushoz "illik" elkészíteni az equal, similar, copy standard eljárásokat, melyekkel minden beépített típus rendelkezik. Ezt az equal eljárást használja a similar és a search eljárás annak eldöntésére, hogy két érték egyenlő-e. Mutable típusoknál ez azt jelenti, hogy a két érték azonos objektum, immutable típusoknál azt jelenti, hogy a két érték egyenlő. (Persze az equal megvalósításakor más szemantikát is kaphat, de célszerű megtartani az eredeti jelentését.)
A copy eljárás csak a fa-struktúrát duplikálja, az abban levő értékeket mutable esetén nem, immutable esetén igen. Ebből adódik, hogy ha a generáló típus mutable, akkor a másolat példány csomópontjaiban az értékek egyszerre változnak a másolt példány értékeivel, míg a fa-struktúrájuk független egymástól. Ha mutable generáló típussal teljes másolatot akarunk készíteni egy fáról, akkor a másolat példányon végigfutó iterációban minden egyes értékről másolatot kell készítenünk a generáló típus copy eljárásával (ha van) - de ez könnyen megoldható az iteráció miatt. (A sok "ha" miatt nem volt érdemes beépíteni egy ezt elvégző copy2 eljárást - ugyanis a generáló típust meg kellett volna szorítani egy copy eljárással is. Eddig még csak-csak rendben volna, de mi van akkor, ha a generáló típusnak is van egy ugyanilyen copy2 eljárása, akkor kellene egy copy3 ... - határ a csillagos ég.) A copy2 eljárásra a végén van egy példa.
A típus tartalmaz egy invariant nevű, invariáns ellenőrző eljárást (nincs visszaadott érték). A CLU-ban nincsenek típushelyességet támogató eszközök, ez az eljárás ennek a hiányosságnak a pótlásához próbál módszert adni. Tetszőleges helyen elhelyezve egy ilyen eljáráshívást - egy fa típusú paramétere van -, az adott fán invariáns ellenőrzést végez, és normális esetben a program ezután tovább fut (célszerű ezt egy konstruktor hívás után elhelyezni). Ha hibát talál, akkor egy paraméteres kivételt vált ki, ha ez nincs lekezelve, akkor a program futása megszakad. Ez az ellenőrzési modell a "hívó által vezérelt" ellenőrzést valósít meg, teszteléskor is így használatos (ellentétben pl. a fordítási direktívákkal). A modell hasznosnak látszik párhuzamos fejlesztéseknél, ha gyanú esetén nem a program íróját kell megkeresnünk, hanem az előre elkészített invariáns ellenőrzést használhatjuk - vita esetén egyfajta bizonyíték is lehet, persze az invariáns ellenőrző is lehet hibás. (Már maga az invariáns ellenőrző elkészítése is jelezheti a típuskészítő szándékának komolyságát, ill. a megvalósított típus értékét.) Az invariant normális lefutása nem garantálja azt, hogy az előtte elvégzett konstruktor helyesen futott le, csak azt, hogy nem rontotta el az invariánst. (Ebben a típusban az előfeltétel ellenőrzést elvégzik a kivételkezelő részek.)
Egy rekurzív fa: (CLU forrás. tree.clu)
% Egy rekurziv fa tipustree_type = cluster [elem_type : type] is%%%%% Konstruktoroknew, % Letrehoz egy ures fat, gyoker van es van erteke new_child, % Letrehoz egy gyereket, ertek van benne insert_to, % Beilleszt egy reszfat egy csomopont ala % (csak gyoker lehet, es nem illeszthet a sajat fajaba) extract, % Kiemel egy reszfat a szuloje alol (csak gyerek lehet) move_to, % Atvisz egy reszfat egy csomopont ala % (nem illeszthet a sajat fajaba) set_value, % A csomopont ertekenek felulirasa (tree.value := elem)%%%%% Fuggvenyek/iteraciokget_value, % A csomopont erteke (elem := tree.value) is_root, % Igaz, ha a csomopont gyoker root, % A csomopont gyokere/faja is_parent_of, % Igaz, ha a csomopont szuloje a masiknak parent, % A csomopont szuloje/annak reszfaja (csak gyerek lehet) level, % A csomopont szintje a fajaban/oseinek szama is_ancestor_of, % Igaz, ha a csomopont ose a masiknak ancestors, % A csomopont osei - iteracio is_empty, % Igaz, ha a reszfa ures/csak gyoker/nincsenek gyerekei child_no, % Csomopont gyerekeinek szama is_child_of, % Igaz, ha a csomopont gyereke a masiknak children, % Csomopont gyerekei/reszfai - iteracio size, % Reszfa merete/csomopontok szama is_descendant_of, % Igaz, ha a csomopont utodja a masiknak in_order, % Reszfa csomopontjai in order bejarasban - iteracio pre_order, % Reszfa csomopontjai pre order bejarasban - iteracio search, % Egy ertek megkeresese a faban search_all, % Egy ertek minden elofordulasanak megkeresese a faban search_by, % Csomopontok, melyek teljesitik a feltetelt - iteracio%%%%% Standard muveletekequal, similar, copy,%%%%% Invarians teszteleshezinvariant% A generalo tipusnak rendelkeznie kell egy equal (=) muvelettel % Erre csak a search es similar fuggvenyeknek van szuksegewhere elem_type has equal : proctype (elem_type, elem_type) returns (bool)%%%%% Reprezentaciorep = record[value : elem_type, parent, children : array[this]] this = tree_type[elem_type]%%%%% Konstruktorok% Letrehoz egy v gyokeru ures fatnew = proc (v : elem_type) returns (this)return (up(rep${value : v, parent : array[this]$new(), children : array[this]$new()}))end new% Letrehoz egy v gyereket egy csomopont ala, % ezutan nem lesz ures, de a gyerek ures lesznew_child = proc (tree : this, v : elem_type) returns (this)tc : this := this$new(v) tc!insert_to(tree) return (tc)end new_child% A csomopont beillesztese a masik csomopont ala, ezzel gyerek leszinsert_to = proc (tree, ptree : this) signals (no_root, own_tree)if ~tree!is_root then signal no_root end if tree!is_ancestor_of(ptree) then signal own_tree enddown(ptree).children!addh(tree) down(tree).parent!addh(ptree)end insert_to% A csomopont kivetele a szuloje alol, ezzel gyoker leszextract = proc (tree : this) signals (is_root, bad_struct)if tree!is_root then signal is_root endptree : this := tree!parent i : int := 1 while i <= ptree!child_no cand ~down(ptree).children[i] = tree do i := i + 1 end %whileif i > ptree!child_no then signal bad_struct endwhile i < ptree!child_no do down(ptree).children[i] := down(ptree).children[i + 1] i := i + 1 end %while ptree := down(ptree).children!remh ptree := down(tree).parent!remhend extract% A csomopont kivetele a szuloje alol, ha van, % es beillesztese a masik csomopont ala, ezzel gyerek leszmove_to = proc (tree, ptree : this) signals (own_tree)if tree!is_ancestor_of(ptree) then signal own_tree endif ~tree!is_root then tree!extract end %if tree!insert_to(ptree)end move_to% A csomopont ertekenek felulirasaset_value = proc (tree : this, e : elem_type)down(tree).value := eend set_value%%%%% Fuggvenyek/iteraciok% A csomopont ertekeget_value = proc (tree : this) returns (elem_type)return (down(tree).value)end get_value% Ez a csomopont a gyoker?is_root = proc (tree : this) returns ( bool )return (down(tree).parent!empty)end is_root% A csomopont gyokereroot = proc (tree : this) returns (this)t : this := tree while ~t!is_root do t := t!parent end %while return (t)end root% Ez a csomopont a szuloje a masiknak?is_parent_of = proc (tree, ctree : this) returns ( bool )return (~ctree!is_root cand tree = ctree!parent)end is_parent_of% A csomopont szulojeparent = proc (tree : this) returns (this) signals (is_root)if tree!is_root then signal is_root endreturn (down(tree).parent!top)end parent% A csomopont szintje a fajabanlevel = proc (tree : this) returns (int)l : int := 0 for t : this in tree!ancestors do l := l + 1 end %for return (l)end level% Ez a csomopont az ose a masiknak?is_ancestor_of = proc (tree, ctree : this) returns ( bool )for t : this in ctree!ancestors do if tree = t then return (true) end %if end %for return (false)end is_ancestor_of% A csomopont oseiancestors = iter (tree : this) yields (this)t : this := tree while ~t!is_root do t := t!parent yield (t) end %whileend ancestors% A csomopontnak nincs gyereke?is_empty = proc (tree : this) returns (bool)return (down(tree).children!empty)end is_empty% A csomopont gyerekeinek szamachild_no = proc (tree : this) returns (int)return (down(tree).children!size)end child_no% Ez a csomopont a gyereke a masiknak?is_child_of = proc (tree, ptree : this) returns ( bool )return (ptree!is_parent_of(tree))end is_child_of% A csomopont gyerekeichildren = iter (tree : this) yields (this)for t : this in down(tree).children!elements do yield (t) end %forend children% A csomopont reszfajanak meretesize = proc (tree : this) returns (int)s : int := 1 for t : this in tree!children do s := s + t!size end %for return (s)end size% Ez a csomopont az utodja a masiknak?is_descendant_of = proc (tree, ptree : this) returns ( bool )return (ptree!is_ancestor_of(tree))end is_descendant_of% A reszfa csomopontjai - in orderin_order = iter (tree : this) yields (this)for t : this in tree!children do for tc : this in t!in_order do yield (tc) end %for end %for yield (tree)end in_order% A reszfa csomopontjai - pre orderpre_order = iter (tree : this) yields (this)yield (tree) for t : this in tree!children do for tc : this in t!pre_order do yield (tc) end %for end %forend pre_order% Egy ertek megkeresese a fabansearch = proc (tree : this, v : elem_type) returns (this, bool)for t : this in tree!pre_order do if t.value = v then return (t, true) end %if end %for return (tree, false)end search% Egy ertek minden elofordulasanak megkeresese a fabansearch_all = iter (tree : this, v : elem_type) yields (this)for t : this in tree!pre_order do if t.value = v then yield (t) end %if end %forend search_all% Csomopontok, melyek teljesitik a felteteltsearch_by = iter (tree : this, exp : proctype (this) returns (bool)) yields (this)for t : this in tree!pre_order do if exp(t) then yield (t) end %if end %forend search_by%%%%% Standard muveletek% Igaz ha ugyanarra az objektumra mutatnakequal = proc (tree1, tree2 : this) returns (bool)return (down(tree1) = down(tree2))end equal% Igaz, ha szerkezetuk megegyezik es ugyanazokra az elemekre mutatnaksimilar = proc (tree1, tree2 : this) returns (bool) e1, e2 : intif tree1.value ~= tree2.value then return (false) end if tree1!child_no ~= tree2!child_no then return (false) endfor t1 : this in tree1!children do e2 := 0 for t2 : this in tree2!children do e2 := e2 + x(similar(t1, t2)) end %for if e2 < 1 then return (false) end e1 := 0 for t2 : this in tree1!children do e1 := e1 + x(similar(t1, t2)) end %for if e1 ~= e2 then return (false) end end %for return (true)end similar% Keszit egy masolatot a szerkezetrol, az elemekrol nemcopy = proc (tree : this) returns (this)t : this := this$new(tree.value) for tc : this in tree!children do tc!copy!insert_to(t) end %for return (t)end copy% Invarians ellenorzese a csomopontot tartalmazo fara % Nem tartalmaz megvalositott eljarasokat!invariant = proc (tree : this) signals (bad_struct(this, this, string))check_tree(check_ancestors(tree)) resignal bad_structend invariant% Osok ellenorzese - a gyokeret adja vissza % Csak annyit ellenoriz, hogy a gyokerbol van-e hivatkozas ra, % s igy a teljes fa ellenorzese eljut majd hozza ischeck_ancestors = proc (tree : this) signals (bad_struct(this, this, string))if down(tree).parent!empty then return (tree) end %if for t : this in down(down(tree).parent!top).children!elements do if down(t) = down(tree) then return (check_ancestors(down(tree).parent!top)) resignal bad_struct end %if end %forsignal bad_struct(tree!parent, tree, "missing child in parent")end check_ancestors% Gyerekek ellenorzese % Reszletes ellenorzescheck_tree = proc (tree : this) signals (bad_struct(this, this, string)) tl : array[this] e : inttl := down(tree).parent if tl!low ~= 1 cor tl!high < 0 cor tl!high > 1 then signal bad_struct(tree, tree, "invalid parent list index") end %if tl := down(tree).children if tl!low ~= 1 cor tl!high < 0 then signal bad_struct(tree, tree, "invalid children list index") end %if for t1 : this in down(tree).children!elements do if down(t1).parent!empty cor down(down(t1).parent!top) ~= down(tree) then signal bad_struct(tree, t1, "invalid parent in child") end %if e := 0 for t2 : this in down(tree).children!elements do e := e + x(down(t1) = down(t2)) end %for if e ~= 1 then signal bad_struct(tree, t1, "multiplicated child in parent") end %if check_tree(t1) resignal bad_struct end %forend check_tree% Logikai ertek karakterisztikus fuggvenye % Csak belso hasznalatrax = proc (b : bool) returns (int) if b then return (1) else return (0) end %if end xend tree_typeAz eljárások részletes magyarázatát nélkülözzük, az eljárások rövidek és "magukért beszélnek". A szintaktikai sajátosságok az előző két példában láthatók, talán csak annyit, hogy a down() konverziós függvény - csak fordítási időben érvényesül, a kódba nem épül be - lehetővé teszi, hogy az aktuális típusra (itt. This) úgy hivatkozzunk, mintha a reprezentáció típusa lenne (Rep) és használhassuk annak eljárásait. Hasonlóan az up() lehetővé teszi, hogy a Rep típusra úgy hivatkozzunk, mintha This típus lenne. (This a Tree_Type[Elem_Type] rövidített neve.)A search eljárásnak (CLU-ban az eljárásokat és függvényeket egységesen eljárásnak nevezzük) két visszaadott értéke is van. Ezt az értékadás művelet segítségével fogadhatjuk (máshogy nem):
ugyanezt meg lehet oldani found nélkül is a típusmegvalósítás módosításával, csak a második visszaadott értéket kell törölni: (nincs tesztelve)... tree, ftree : tree[string] found : bool ... ftree, found := tree!search("valami") if found then ... end %if ...A search_by eljárásnak van egy eljárás típusú paramétere. A CLU sajátossága, hogy az eljárásokat pontosan úgy kezeli, mint a többi objektumot, van típusa, lehetnek változói, a változóknak van értékadás művelete, átadható paraméterként és lényegében egy művelete van, el lehet indítani - szükség szerint paraméterezve.... tree, ftree : tree[string] found : bool ... ftree := tree!search("valami") if ftree.value = "valami" then ... end %if ...A copy2 eljárásra egy példa, ennek csak akkor van értelme, ha a generáló típus mutable (immutable esetén már az első copy-nál másolat keletkezik): (nincs tesztelve)
Az invariant eljárást nem a típus programjaiba helyezzük, hanem a külső hivatkozások - elsősorban a konstruktor eljáráshívások - után. Az invariant használatára példa:copy_tree = proc (tree : my_tree) return (my_tree) tree2 : my_tree := tree!copy for t : my_tree in tree2!pre_order do tree2.value := tree2.value!copy end %for return (tree2) end copy_treeA csomagban van egy egyszerű program, mely ennek a típusnak az eljárásait teszteli le. (CLU forrás: treetest.clu) Ebben látható a típus használatának módja.... ptree := tree!parent tree!extract tree!invariant ptree!invariant ...illetvet!invariant except when bad_struct(ptree, tree : my_tree, mess : string): output!putl(" T's structure is damaged!") output!putl(" Parent : " || ptree.value) output!putl(" Child : " || tree.value) output!putl(" Message: " || mess) signal failure endFordítása:
>clu2c tree treetest >clulink -o treetest tree.c treetest.c >treetest