A Clean szigorúan típusos nyelv, minden gráfbeli pontnak (kifejezésnek) és minden gráfújraírási szabálynak (függvénynek) van típusa. A típusok megadása nem kötelező a programszövegben, de mivel a nyelv szigorúan, statikusan típusos nyelv, ezért megköveteli, hogy minden részkifejezésnek fordítási időben pontosan meghatározott legáltalánosabb típusa legyen. Ha a programozó nem adja meg a típust, akkor a Hindley-Milner féle korlátozottan polimorfikus típusrendszer alapján a fordító a típuslevezetési szabályok által meghatározza. Nyelvi eszközök az absztrakt és algebrai adattípusok leírásához is biztosítottak. (lsd. Új típusok definiálása)
Az előző példa típusmegadás nélküli változata:
Ez is lefordul, sőt a -lt kapcsolóval megkérhetjük a Clean fordítót, hogy írja ki a kikövetkeztetett típusokat:
A leggyakoribb hiba a típushiba. Egyesek szerint a funkcionális nyelvek esetén minden hiba ilyen jellegű. A fordító ilyen esetekben rögtön figyelmeztet bennünket (Cleanide-del fordítva Windows alól):
Hatékonysági és kényelmi okokból egész és lebegőpontos szám, karakter illetve logikai típust biztosít a rendszer — egyébként ezeket az általános algebrai típus definiálásának lehetőségét kihasználva mi magunk is létrehozhatnánk. Mindegyikből egyfajta, rögzített méretű létezik: az egészek 32, a lebegőpontosak 64, a karakter és logikai típusok 8 bitesek. A szokásos aritmetikai operátorok túlterhelési technika segítségével ugyanazon nevekkel elérhető módon vannak definiálva az egész és a lebegőpontos típusokra.
Mutató típus nincs, de a nyelv logikája alapján ennek nem is lenne értelme.
A listában tetszőleges számú, tetszőleges típusú elemet tárolhatunk, de valamennyi elemnek azonos típusúnak kell lennie. Az üres lista konstruktora [].
Listák létrehozása kétféle módon történhet:
Pont-pont kifejezésekkel
Egy pont-pont kifejezéssel fel lehet sorolni a lista elemeit az első, a második, és az utolsó elem segítségével. A második és az utolsó elem megadása opcionális. Ha csak az első elemet írjuk ki, akkor a lépésköz alapértelmezés szerint egy, viszont ha megadtuk a második elemet, úgy értelemszerűen a második és az első elem különbsége. Alternatívák:
ZF-kifejezésekkel
Egy ún. listagenerátorral (másnéven ZF-kifejezéssel, a Zermelo-Fraenkel-féle halmazelmélet nevéből) lehet listákat más listákból vagy tömbökből vett elemekkel létrehozni.
A <- szimbólummal lehet elemeket lusta listákból venni, a <|- szimbólummal pedig bármilyen (lusta, szigorú, dobozolatlan) listából. Tömbgenerátorral (<-: szimbólum) tömbből lehet elemeket venni.
Kétféle generátortípus létezik:
Vesszővel elválasztva kell megadni a generátorokat. Ebben az esetben a legbelső változik a leggyorsabban. Fontos, hogy egy generátor értéke nem használható a megelőző generátorokban.
& szimbólummal történő elválasztás. Ilyenkor természetesen nincs lehetőség arra, hogy a generátorok egymás értékeire hivatkozzanak, mivel egyszerre változnak.
A ZF-kifejezések váza:
Listák lehetnek:
Lusta listák esetén nem értékelünk ki addig semmit, amíg nincs rá szükségünk; így végtelen adatstruktúrákat is tudunk kezelni.
Példák lusta listák definiálására:
Dobozolatlan listák esetén ahelyett, hogy a listaelemre mutató pointert tárolnánk a listában, magát a listaelemet tároljuk. Megkötés, hogy ilyen listák csak elemi-, rekord-, és n-es típusú elemeket tartalmazhatnak.
Listák típusát a zárójel stílusával jelezhetjük:
A lusta, szigorú, illetve dobozolatlan listák mind különböző típusúak, tehát pl. konverziós függvények szükségeltetnek, hogy egy lusta listát szigorú listává változtassuk. Az egyik típusú listára definiált függvényeket nem lehet más listatípusra alkalmazni, de persze megvan a lehetőség olyan túlterhelt operátor alkalmazására, amit bármely listatípusra alkalmazhatunk. Ezentúl lehetőség van olyan ún. lista használatára, amely minden listatípusra illeszkedik, ezt a [| ] stílusú zárójellel jelezhetjük.
Hatékonyság szempontjából igen fontos lehet, hogy melyik típusú listát használjuk. Általában azonban nem eldönthető, hogy melyik listatípus a legjobb választás, ez attól függ, éppen hogyan használjuk a programban. Egy rossz választásnak lehetnek kellemetlen következményei, például használhatatlan (nem hatékony) vagy nem termináló programot kaphatunk.
Az n-es különböző típusú elemeknek olyan kombinációja (direktszorzat), amit (a rekordokkal ellentétben) külön típusdefiníció nélkül használhatunk. Kiválóan alkalmas például függvény visszatérési értékének, ha valójában több értéket szeretnénk visszaadni. Ezt már többször láttuk az eddigi példák során a startkifejezés esetében.
Példa n-esre:
Lusta n-es elemeket szigorúvá lehet tenni a szigorúságot jelző annotációval (!). A lusta és a szigorú n-esek különböző típusúak. A szigorú annotációkat a fordítóprogram automatikusan lustává alakítja, és fordítva, ha arra szükség van (ez az automatikus konverzió a transzformáció bonyolultsága miatt csak n-es típusra működik). Ez a programozó számára annyit jelent, hogy szabadon keverheti a lusta és szigorú n-eseket. A típusrendszer nem fog panaszkodni amiatt, hogy egy szigorú n-est kapott, mikor egy lustát várt volna.
A tömb tetszőleges, de azonos típusú elemeinek száma csak véges lehet, viszont indexeléssel konstans idő alatt közvetlenül hivatkozhatunk bármelyikre. Az indexek nemnegatív egész számok. Indextúlcsordulás esetén futási hiba lép fel (listákkal ilyen nem történhet meg). Egy tömb típusú értéket valamennyi elem egyszerre történő megadásával hozunk létre, ekkor dől el a tömb mérete is. Tömb értékeket egyben át lehet adni tömb típusú változóknak. Egyedi típusú tömbnél lehetőség van az elemek felülírásos (nem funkcionális!) módosítására. Többdimenziós tömb tömbök tömbjeként hozható létre, az elemeire való hivatkozás viszont speciális szintaxissal is lehetséges. A többdimenziós tömbből altömb (szelet) képezhető.
Hatékonysági okokból a tömbök különbözőképpen vannak implementálva a különböző tömbelemek típusa szerint. Alapértelmezésként tömbök mint lusta tömbök ({a}) vannak implementálva, azaz a tömb egy összefüggő memóriablokk, amely a tömbelemekre mutató pointereket tartalmazza. Hasonló a reprezentáció szigorú tömbök esetén is ({!a}), a különbség az, hogy a szigorú tömb elemei akkor lesznek kiértékelve, amikor maga a tömb. Tömbök esetén nincs értelme különbséget tenni fej- és farok-szigorúság között, mint listák esetén.
Hatékonysági okokból a nyelv külön biztosít olyan tömböt is, amelynek az elemei egyedi típusúak (a * vagy . annotáció jelzi a típus előtt); illetve elemi-, rekord- és n-es típusú elemek esetén dobozolatlan tömböt ({#a}) is használhatunk. Hasonlóan, mint listák esetén, a lusta, szigorú és dobozolatlan tömbök külön típusúak, bár a legtöbb előre definiált operátor túlterhelt oly módon, hogy minden típusú tömbre lehessen alkalmazni.
Egy tömb elemeire a . szimbólummal lehet hivatkozni, pl. az a tömb i. elemét a.[i] adja meg. A tömbelem-hivatkozás bal-asszociatív, azaz a.[i,j,k] jelentése: ((a.[i]).[j]).[k]. Egyedi kiválasztás a ! szimbólummal történhet, ekkor visszakapunk egy n-est, ami a kívánt tömbelemet és az eredeti tömböt tartalmazza. Ez a fajta kiválasztás nagyon praktikus lehet egyedi típusú tömbök destruktív frissítésénél, amikor a tömbelemek értékei a tömb jelenlegi tartalmától függenek.
A destruktív frissítés a klasszikus értelemben vett frissítés, amely nem hoz létre új példányt, hanem felülírással módosítja a már meglévőt: tömbök esetén például az a & [i] = v kifejezés is így módosítja az a tömböt. A & jeltől balra a régi tömb áll, amelynek egyedi típusúnak kell lennie, hogy lehessen ezt a frissítési módszert alkalmazni. A jobb oldalon azokat az elemeket soroljuk fel, amelyekben az új tömb különbözik a régitől. Ily módon a tömb bármely elemét megváltoztathatjuk. A & operátor szigorúan értékeli ki az argumentumait.
Példák tömbök definiálására:
Lássuk a mátrixszorzást többdimenziós tömbökkel:
A tömbök nagyon hasznosak, ha szűkös az idő- és tárkapacitás (Clean-ben a tömbök nagyon hatékonyan vannak implementálva). Ha a hatékonyság nem olyan fontos számunkra, akkor ajánlatos inkább a listákat választani: a listák sokkal rugalmasabbak, és kevésbé hajlamosak hibára (lásd tömbindexek túlcsordulása)
A -> szimbólummal függvénytípust definiálhatunk. Ez a típus ugyanúgy lehet típuskonstruktorok paramétere, az ilyen típusú objektumok pedig függvények, adatkonstruktorok paraméterei, mint ahogyan az bármilyen más típus esetén természetes.
Példa függvénytípusra: ((a -> b -> c) -> [a] -> [b] -> [c]). Megjegyezzük, hogy a nyilacskák elhagyhatók, azaz írható a függvény Descartes-szorzatként is. Tehát ((a b -> c) [a] [b] -> [c]) ekvivalens az előző formában leírt függvénytípussal. Ennek alapja az ún. Curry módszer.
Curry módszere alapján minden függvényt tekinthetünk egyváltozósnak. Ha egy függvénynek több változója (többváltozós függvény) van, akkor az első változót tekintjük a függvényhez tartozónak. Az első argumentum megadásával már egy újabb függvényhez jutunk, amelyet a következő argumentumra alkalmazhatunk. Így többváltozós függvény alkalmazását tekinthetjük egyváltozós függvények alkalmazása sorozatának. Többváltozós függvény részleges alkalmazása alatt azt értjük, hogy argumentuainak egy részének rögzítésével (balról jobbra haladva) új függvényt kapunk eredményül.
A funkcionális programozásban gyakran használt map függvény egy struktúra elemeire alkalmaz egy adott függvényt. Az egyébként a standard könyvtárban is szereplő, listákhoz használható változat így írható le mintaillesztést alkalmazva:
A fenti map függvény egy ún. magasabbrendű függvény (higher order functions). Magasabbrendű függvényeknek nevezzük az olyan függvényeket, amelyeknek valamelyik argumentuma vagy értéke is függvény. A map esetén függvény az első argumentum, valamint a kiszámításnál is megjelenik az f. Ezek a függvények hatással vannak a program modularitására, számítási folyamatára. Pédául egy magasabbrendű függvény függvényargumentuma mohó kiértékelés esetén előbb kerül kiértékelésre, így jól elválasztható blokkokra bontja a számítási folyamatot.
Érdemes még itt is megemlíteni a rekurzió fogalmát, mely nem más mint a függvény alkalmazása önmagára, valamint a kölcsönös rekurzó kifejezést, amely azt jelenti, hogy például az A függvény hívja a B függvényt, és hasonlóan a B függvény is hívja a függvénytörzsében az A-t.
A korábban példaként említett map függvényt polimorf függvénynek nevezzük. Ez azt jelenti, hogy a függvényt egymástól különböző típusú argumentumokra alkalmazhatjuk. Mivel a függvény megvalósítása nem függ a típustól, tehát nem lényeges, hogy a lista milyen típusú elemeket tartalmaz, csak az, hogy az argumentum lista legyen (ezt is lehet még tovább általánosítani), így definíciója is megadható típusfüggetlen formában. Hogy a polimorf tulajdonságot kifejezzük ún. típusváltozókkal deiniáljuk a függvényt (a map esetén típusváltozók: a, b). Fontos megjegyezni, hogy a Clean különbséget tesz típusállandó (nagybetűvel, pl.: Char, Int) és típusváltozó (minidg kisbetűvel kezdődik) között.
Ha a következőkben a + műveletre gondolunk, akkor láthatjuk, hogy ez is polimorf függvény. Jobban belegondolva viszont itt a különböző típusokra (pl.: Int, Bool) másképp van definiálva a művelet, többszörösen használjuk a műveleti jelet. Tehát ez egy másfajta polimorfizmus mint a map függvényé, melyet úgy hívunk, hogy túlterhelés vagy "ad hoc" polimorfizmus.
Az ilyen jellegű műveletnek, mint az összeadás létezik egy absztrakt, típusfüggetlen, közös szignatúrája (függvény szignatúrája: a paraméterei és eredményei típusának leírása). Ebből a szignatúrából később példányosítással lehet megvalósítani az adott típushoz tartozó művelet definicióját. Clean-ben a példányosítást az instance kulcsszó, az absztrakt szignatúrát a class jelzi. Egy adott class deklaráció absztrakt szignatúráihoz tartozó példányok halmazát típusosztálynak nevezzük. Ebben az esetben meg kell adni, hogy a művelet melyik oldalról asszociatív (infixl vagy infixr) és mekkora a precedenciaszintje.
A double függvényt nem kell példányosítani, mivel ahogy a definíciójában is jeleztük a + művelettől függ (| + a jelölés). A double függvényt származtatott függvénynek nevezzük, mivel értelmezése attól függ, hogy az adott típushozdefiniáltunk-e, és ha igen, akkor hogyan definiáltuk a + típusosztály megfelelő példányát. A :== jelölés pedig azt jelenti, hogy fordítási időben történjen a kiértékelés.
Ismertebb és hasznos típusosztályok például: Ord a - struktúrális rendezés, Eq a - struktúrális egyenlőségvizsgálat. Ezek segítségével is korlátozhatjuk a típus vagy a függvény deklarációkban, definíciókban a lehetséges értékeket. Ezt úgy hívjük, hogy osztálykörnyezet megadása.
Tehát az isEqual függvény csak azokra a típusokra használható, amelyekre példányosítva van a == művelet.
Egy algebrai adattípussal egy új típuskonstruktort (egy új típust) rendelünk egy újonnan bevezetett adatstruktúrához. Az adatstruktúra egy új konstans értékből (adatkonstruktor) áll, aminek nulla vagy több (bármilyen típusú) argumentuma lehet. Minden adatkonstruktor egyértelműen (előre) definiálva kell hogy legyen egy algebrai adattípus definícióban. Egy algebrai adattípus definícióban több adatkonstruktort is definiálhatunk, ami lehetővé teszi ugyanolyan algebrai adattípusú adatszerkezetek definiálását.
Ha a típuskonstruktort típusokkal (akár magasabb rendűekkel is) paraméterezzük, akkor polimorfikus típusról beszélünk. Az adatkonstruktorok paraméterei típuspéldányok lehetnek.
Magasabb rendű típusok a típuskonstruktorok nyilacskázott alkalmazásával hozhatók létre (lásd Curry-módszer), ezek ugyanúgy alkalmazhatóak a típusok világában, mint a magasabb rendű függvények a függvények világában. A magasabb rendű típusok használata rugalmasabbá teszi az algebrai típusok definícióját.
Bevezetjük a típus fajtájának (kind) fogalmát. Ez lényegében a "típus típusa". A típus fajtája kifejezi a típusargumentumok számát. Az X típus az ún. elsőrendű típusokat jelöli, amiknek nincsen további argumentuma (pl. Int, Bool, [Int]). Minden függvényargumentum elsőrendű típusú. Az X -> X azokat a típusokat jelöli, amelyeket elsőrendű típusokra alkalmazhatunk, aminek eredményeképpen egy újabb elsőrendű típust kapunk. X -> X -> X két argumentumot vár, és így tovább...
Példa magasabb rendű típus használatára (egész számok valamilyen struktúrájának fája):
Megjegyezzük, hogy ebben az esetben a t típusváltozó X -> X "fajta" típusú.
Ha mind a típuskonstruktor, mind az adatkonstruktorok paraméter nélküliek, akkor felsorolási típust definiálnak:
Lássuk, milyen típust következtet ki a rendszer:
A felsorolási típus alapból nem rendezett, de persze definiálhatunk rá rendezést. A típus értékei a belső ábrázolásban se nem 0-val se nem 1-gyel nem kezdődnek, hanem címkézett gráfpontokként vannak reprezentálva. A logikai típus viselkedését tekintve lehetne felsorolási típus (akár definiálhatnánk is egyet ilyen módon), hatékonysági szempontok miatt azonban az elemi logikai típust más módon valósították meg.
Paraméterrel rendelkező és paraméter nélküli adatkonstruktorok vegyesen is szerepelhetnek a definícióban:
Egy algebrai adattípus definíció tartalmazhat egzisztenciálisan kvantált típusváltozókat. Ezeket a speciális változókat a típusdefiníció jobb oldalán definiáljuk oly módon, hogy egy E. minősítést teszünk eléjük. Az egzisztenciális típusok hasznosak lehetnek, ha olyan (rekurzív) adatszerkezeteket akarunk létrehozni, amelyekben különböző típusú objektumokat tárolunk (pl. egy lista különböző típusú elemekkel).
Az alábbi példában az Object objektum metódusai csak az objektum állapotára alkalmazhatóak, így lehetővé téve egyfajta objektum-orientált programozási stílust. Az állapot konkrét típusa el van rejtve az objektumban, kívülről nem látható. Hogy az állapotot megmutassuk a külvilágnak, át kell konvertálnunk a típust (ami bármi lehet, esetünkben pl. string). Azokat az objektumokat, amelyek különböző típusú állapottal rendelkeznek, nem tekintjük külön típusnak.
Amennyiben egy adatszerkezet tartalmaz egzisztenciálisan kvantált részeket, akkor ezen részek típusát nem vesszük figyelembe. Ez általában azt jelenti, hogy ha egy adatszerkezetet átadunk egy függvénynek, akkor statikusan lehetetlen meghatározni azon részek aktuális típusát. Ezért nem megengedett, hogy egy függvény, amelynek argumentuma egzisztenciálisan kvantált adatszerkezet, bármilyen specifikus típusfeltevésekkel éljen azon részeket illetően, amik az egzisztenciális típusváltozókhoz tartoznak. Ebből az következik, hogy egy egzisztenciális típusváltozót kizárólag konkrét típussal lehet példányosítani. Tekintsük a következő példát:
Legyen a típusdefiníciónk a következő:
Ebben az esetben a Hd függvény, ami a lista fejelemét adja vissza, nem legális:
A Tl függvény azonban, ami a maradékrészt adja vissza, gond nélkül alkalmazható:
Egy algebrai adattípus definíció tartalmazhat univerzálisan kvantált típusváltozókat az adatkonstruktor argumentumai között. Ezeket a speciális változókat a típusdefiníció jobb oldalán az adatkonstruktor argumentumai között definiáljuk oly módon, hogy egy A. minősítést teszünk eléjük. Univerzálisan kvantált változók arra használhatók, hogy olyan polimorfikus függvényeket tároljunk, amelyek argumentumai bármilyen típusúak lehetnek.
Az alábbi példában T tartalmaz egy univerzálisan kvantált b -> b függvényt. Az Id identitás függvény tárolható T-ben, mivel a típusa Id:: a -> a tulajdonképpen az Id:: A.a:a -> a rövidítése. Ez azért fontos, mert ha nem írtuk volna oda a A. minősítést a b -> b függvény elé, akkor T bármilyen függvényt tartalmazhatna, ami egyeztethető lenne b -> b -vel (pl. az Int ->Int). Ekkor viszont típushibát kapnánk f-re, mivel g-t mind Int-re, mint Char-ra alkalmazzuk.
Rekortípusokat a szokásos módon, mezőnevek és típuspéldányok felsorolásával definiálhatunk. Rekord létrehozásakor valamennyi mezőnek értéket kell adni, ezt azonban tetszőleges sorrendben megtehetjük. A rekord típusú értékeket egyben átadhatjuk, rekord típusú konstans létrehozható. Mintaillesztéskor illetve rekord értékének módosításakor elég csak azokra a mezőkre hivatkozni, amelyekre szükségünk van. Variáns rekordot külön nyelvi eszközökkel nem támogat a rendszer.
A mezőneveknek egy rekordon belül különbözőeknek kell lenniük, de megengedett ugyanazokat a mezőneveket különböző rekordokban használni. Rekordok létrehozásánál a típuskonstruktort el lehet hagyni, ha van legalább egy olyan mezőnév definiálva, amit semmilyen más rekordban nem definiáltunk.
Példa rekordtípus definíciójára:
Rekord létrehozása már létező rekordból (funkcionális frissítés) az r & f = v kifejezéssel történik. A & bal oldalára írt rekord az a rekord, amelyből az új rekordot akarjuk képezni. A & jobb oldalára azokat a struktúrákat írjuk, amelyekben az új rekord különbözik a régitől. Egy ilyen struktúra lehet a rekord bármely mezője. Minden más mező implicit meg lesz kettőzve. A funkcionális frissítés nem klasszikus értelemben vett destruktív frissítés, mivel egy új rekordot hozunk létre. Ilyen destruktív frissítésre nincs is szükség, mert a rekrodok funkcionális frissítése nagyon hatékony (megjegyezzük, hogy tömbök esetén lehetőség volt destruktív frissítésre). A & operátor szigorúan értékeli ki az argumentumait.
Példa rekord funkcionális frissítésére:
Rekord mezőire a . szimbólummal lehet hivatkozni. Egyedi kiválasztás hasonló okokból és hasonló módon történik, mint tömbök estén.
Szinonímatípusok: definícióval új nevet adhatunk egy meglevő típusnak. Az ekvivalencia strukturális. Az ekvivalencia szempontjából a rekordok mezőnevei számítanak, sorrendjük viszont nem; a Clean tömbjeinél az egyenlő számosságú index egyben egyező indexhatárokat is jelent, de a tömbök típusának egyezése szempontjából nemcsak hogy ez, de még a különböző méret sem jelent különbséget. Altípusok nem definiálhatók, fordítási időben cserélőnek ki.
Szinonímatípusok ciklikus definíciója (pl. ::Paint :== Color; ::Color :== Paint) nem megengedett.
Globális konstansok: csak egyszer értékelődnek ki, szintén fordítási időben. Hátránya, hogy növekszik a memóriaigény, mivel újrafelhasználhatóak, viszont csökkenthető a futási idő.
Ezek tulajdonképpen magasabb rendű típusok; típuskonstruktoraikat elsőrendű típusokkal paraméterezve kapunk közvetlenül felhasználható típuspéldányokat. Ilyen magasabb rendű típus a lista, a háromféle tömb, az n-es, és a függvénytípus. Pl. a lista típuskonstruktora a [], ezt egy konkrét típussal, pl. Int-tel paraméterezve az [Int] közvetlenül használható egészek listája típuspéldányt kapjuk.
A magasabb rendű típusok önállóan is felhasználhatók, illetve típusváltozóval is paraméterezhetők. Az alábbi függvény pl. tetszőleges típusú listán működik:
Beépített unió és halmaztípus nincs, de a meglevő eszközökkel létrehozható.
Az unió típus egy megvalósítása:
Halmaztípus néhány halmazon értelmezett művelettel, relációval és konverzióval:
Olyan halmaztípust is létrehozhatunk, amelyben különböző típusú elemek is lehetnek:
Erre a halmaztípusra azonban nem tudjuk megfogalmazni a szokásos műveleteket, az elemeket ugyanis nem tudjuk összehasonlítani.
A funkcionális nyelvekben nincsenek klasszikus értelemben vett változók. A függvények, típus- és adatkonstruktorok formális paramétereit, az illesztett minták részeit, a részszámítások eredményeit jelöljük meg a programszövegben változókkal. Ezek azonban csak a programgráf egyes részfái megcímkézésének feladatát látják el, nem tárolnak adatokat, nem alkalmazható rájuk az értékadás klasszikus művelete. Ha egy változószimbólumhoz egy kifejezést rendelünk, akkor az általában csak azt jelenti, hogy létrehozunk egy, a kifejezésnek megfelelő gráfot, és a változószimbólum ezentúl ezt fogja megcímkézni. Bizonyos esetekben azonban lehetőség van felülírásos módosításra is (lásd alább).
Valamennyi típusnak van egy eddig még nem tárgyalt jellemzője, ami két értéket vehet fel: jelölhetünk egyedinek egy típust (!a), vagy ahogyan eddig, felkiáltójel nélkül, nem-egyedinek. A szigorú típusosság szempontjából az a és !a teljesen külön típusnak számít. Az egyedi típusú objektumok használata során garantált, hogy egyszerre csak egy hivatkozás lehet rájuk, ezért ezek esetében lehetséges az értékük felülírásos módosítása. Ez a tulajdonság leginkább a külvilággal való kommunikáció szempontjából jelentős, interakciónál, file-műveleteknél használatos.
Értelmes példát erre az input-output témakörből lehetne hozni, de ezt kiterjedtsége folytán itt nem tárgyaljuk. Az egyedi típusjellemző használatához egyébként is sok apró szabály kapcsolódik, az ezekkel való megismerkedés komolyabb belemerülést igényel.
A Clean kiértékelési sorrendje alapértelmezésben lusta: nem helyettesíti az argumentumot, amíg nem muszáj. Ennek előnye, hogy olyan kifejezések is kiértékelhetőek, amelyek behelyettesíthetlen argumentumokat tartalmaznak. A Clean ezen tulajdonsága teszi lehetővé, hogy pl. végtelen listákat definiáljunk, azokon műveleteket végezhessünk.
A kiértékelési sorrend azonban igény szerint mohóvá tehető, sok esetben ugyanis idő és tárhely szempontjából is hatékonyabb, ha az argumentumokat gyorsan kiértékeljük (más esetekben viszont nem terminálna a kiértékelés, mint például az imént említett végtelen listáknál). Függvények illetve adatkonstruktorok paramétereit jelölhetjük meg úgy, hogy azok mohó kiértékelésűek legyenek.
Példaként nézzük meg a Fibonacci-számok kiszámítását lusta illetve mohó kiértékeléssel. A különbséget csupán a függvény paraméterénél található ! jelzés jelenti:
A fordítást az -nsa kapcsolóval végezve kiiktatjuk a fordító mohóság-elemzőjét, ami az első programot is automatikusan mohóvá fordítaná le:
Nézzük meg a futási idők közti különbséget:
Automatikus konverzió nincs. Az elemi típusú értékek közötti konverziót a standard könyvtári függvények biztosítják.
Minden típusra automatikusan létrejövő toString konverzió programon belüli használatra nincs, a program eredményének kiírásához viszont létezik, azaz bármilyen típushoz tartozó eredményt képes szövegesen kiírni a program.