Nem minden funkcionális programozási nyelv gyengén típusos. Az ML egy olyan funkcionális programozási nyelv, amely szigorúan típusos, és statikus típusellenőrzéssel rendelkezik (tehát fordítási/értelmezési időben történik a típusellenőrzés). Az ML -ben erős típus-kitalálás is van. Nem úgy mint a legtöbb programozási nyelvben, az ML programozónak nem kell specifikálnia minden esetben a függvények argumentumainak és visszatérési értékeinek a típusát, mivel az interpreter nagyon jó a helyes típus megtalálásában. Például, mivel az értelmező tudja, hogy a 2 egész szám, kitalálja, hogy a visszatérési értéknek is egész számnak kell lennie. Az ML értelmező érték - típus párokat ad vissza:
Felhasználói túlterhelés nincs, mert aláássa a típuslevezetést.
Az ML -ben megtalálható egyszerű típusok:
Ezekből objektumokat konstruálhatunk rendezett n -esek (tuple), listák, függvények és rekordok segítségével. Saját alaptípust is készíthetünk - erről részletesebben később lesz szó. Egy rendezett n -es (akár különböző típusú) objektumok sorozata. Néhány rendezett n -es:
Amíg a rendezett n -esekben különböző típusú objektumok lehetnek, de fix hosszan, addig a listákban megegyező típusú objektumok lehetnek, de tetszőleges hosszan. Néhány lista:
Konstrukciós művelettel:
Megjegyzés: az [1,2] és az [1,2,3] objektumoknak ugyanaz a típusa, nevezetesen egész számokból álló lista, de az (1,2) és az (1,2,3) különböző típusúak! Az egyik "int * int", míg a másik "int * int * int". Fontos ismernünk az egyes objektumok típusait. Amíg valaki az ML -t tanulja, a legtöbb hiba a nem megfelelő típuskezelésből származik.
Segítségével írhatunk generic függvényeket - tehát a típusát a függvénynek nem kell megadni. (Időnként azért nem árt; típusa pl.: 'a->'a -a szerk.) Például, vegyünk egy olyan függvényt (length) amely visszaadja egy lista hosszát. Természetesen ez egy előre definiált függvény. Nem számít, hogy milyen típusú listának kívánjuk meghatározni a hosszát. Ennek a függvénynek a típusa így:
az 'a típusváltozó minden ML típus előtt állhat.
MJ: A list ekkor önmagában nem is egy típus, hanem egy postfix pozíciójú típusoperátor függvény. Az 'a típusváltozóval együtt alkot típust ebben a példában.
Amikor a függvényeket úgy definiáljuk, hogy a paraméterének vagy a visszatérési értékének típusát nem adjuk meg, akkor a fordító ezeket automatikusan vezeti le a függvény definíciója alapján. Ha a definícióban nem használjuk ki valamely típus speciális műveletét, akkor típusváltozó lesz a típusa az argumentumnak vagy a visszatérési értéknek. A fenti példában ez az 'a (ejtsd: alfa). Ekkor a függvény bármely típusú paraméterrel meghívva működni fog és a visszatérési értéke e típusok alapján lesz meghatározva. Pl.: ha a max: 'a list -> 'a függvényt int list típusú paraméterrel hívjuk meg, akkor int típusú értéket ad vissza. Nem csak 'a típusváltozó van az ML-ben, ugyanis lehetséges, hogy a típus felhasználása közben szükségünk van az egyenlőségre. Ekkor a fordító egyenlőségi típust vezet le, amelyet ''a -val jelöl(két percjel és nem aposztróf). Ez esetben a paraméterek között az egyenlőségi típus helyén nem adhatunk át függvényt, vagy bármely más nem egyenlőségi típust.
A polimorfizmus több válfaját alkalmazzuk a programozásban:
A típus nélküli és a gyengén típusos nyelvek a programozónak nagyobb szabadságot adnak, de a tévedés lehetősége is nagyobb. Az erősen típusos nyelvek a programozót jobban korlátozzák, de biztonságosabbak. Az SML-ben polimorf típusellenőrzés van: a szigorú típusellenőrzés flexibilis, automatikus típuslevezetéssel (type derivation, type inference) társul.
Nézzük a következő definíciót!
Milyen típusú itt az x? Mindegy! A id függvény ún. polimorf függvény, az x pedig politípusú azonosító. A típussémák elméletében a politípus jele α,ß, γ stb, az SML-ben ’a, ’b, ’c stb. Az SML-értelmező válasza a fenti definícióra tehát a következő:
A percjellel kezdődő típusneveket (’a-t, ’b-t, ’c-t stb.) típusváltozónak nevezzük, és alfának, bétának, gammának stb. olvassuk.
Az egyenlőségvizsgálatot is megengedő ún. egyenlőségi típusok (equality types) típusváltozóinak jelölésére két percjellel kezdődő neveket használunk: ’’a, ’’b, ’’c stb.
A polimorf típus: típusséma. Amikor a típusváltozót konkrét típussal helyettesítjük, e séma egy-egy példányát kapjuk.
Nézzünk két újabb, nagyon egyszerű példát polimorf függvények definiálására! Egy pár első, ill. második tagjának kiválasztására használhatók az alábbi projekciós függvények, ahol ’a és ’b nem feltétlenül különböző típusok:
Képzeljük el azt a függvényt, amelyik megvizsgálja, hogy egy ls listában benne van-e egy bizonyos e elem. Polimorf-e ez a függvény? A lista minden eleméről el kell tudni dönteni, hogy egyenlő-e e-vel. Csakhogy az egyenlőségvizsgálatot nem minden függvényre és absztrakt típusra lehet elvégezni! Miért is nem?
Az egyenlőség tehát csak korlátozott értelemben polimorf. Egyenlőségi típusnak (equality type) nevezzük az olyan típust, amelyen az egyenlőségvizsgálat elvégezhető. Amint már említettük, az ilyen típusváltozókat az SML két percjelből (prime-ból) és egy betűből álló azonosítóval (’’a, ’’b, ’’c stb.) jelöli. Pl.:
Most már definiálhatjuk az isMem (eleme) függvényt, ill. operátort:
A newMem függvény egy új elemet rak be egy listába, ha az elem még nins benne:
newMem halmazt hoz létre (ha a sorrendtől eltekintünk). A setof függvény halmazt készít egy lsitából úgy, hogy kiszedi belőle az ismétlődő elemeket:
A setof függvénynek elég rossz a hatékonysága. Szerencsésebb, ha a halmazokat a megszokott halmazműveletekkel kezeljük. Most továbbra is egyszerű listaként ábrázoljuk őket, de később valamilyen hatékonyabb tárolást választhatunk, pl. rendezett listát vagy bináris fát.
Öt halmazműveletet definiálunk:
A listák egyenlőségvizsgálata belső művelet az SML-ben. Halmazokra mégsem használható, mert pl. a [ 3, 4 ] és a [ 4, 3, 4 ] listák ugyan különböznek, de mint halmazok egyenlőek. Halmazként egyenlő pl. [ 3, 4 ] és [ 4, 3 ] is.
A hatványhalmaz egy halmaz összes részhalmazának a halmaza, az eredeti halmazt és az üres halmazt is beleértve. Jelöljük S-sel az eredeti halmazt. S hatványhalmazát úgy állíthatjuk elő, hogy S-ból kiveszünk egy x elemet, és azután rekurzív módon előállítjuk az S – {x} hatványhalmazát. Ha tetszőleges T halmazra T Í S és T E {x} Í S, így mind T, mint T E {x} eleme S hatványhalmazának. A pws függvényben a base argumentum gyűjti a hatványhalmaz elemeit; kezdetben üresnek kell lennie.
A pws(xs, base) @ pws(xs, x::base) kifejezésben pws(xs, base) valósítja meg az S – {x} rekurzív hívást (hiszen x::xs felel meg S-nek), azaz állítja elő az összes olyan halmazt, amelyekben x nincs benne, pws( xs, x::base) pedig ugyancsak rekurzív módon base-ben gyűjti az x elemeket, vagyis előállítja az összes olyan halmazt, amelyben x benne van. Halmazegyenlettel pws eredménye így adható meg:
Kötések segítségével úgy hivatkozhatunk egy objektumra, mint egy szimbolikus névre. Megjegyzés: a címke nem ugyanaz, mint a változó a 3 -ik generációs programozási nyelvekben. A kulcsszó amivel kötéseket hozhatunk létre a val. A kötések a környezet részévé válnak. Egy tipikus ML folyamat során kötéseket hozunk létre így gazdagítva a globális környezetet (és kifejezés kiértékelést). Ha egy olyan kifejezést adunk meg az ML értelmezőnek, amelynek nincs kötése, az automatikusan az it -hez kötődik. A nevekhez új értéket is lehet kötni, de azok a függvények amelyeknek a definíciójában felhasználtuk a névhez kötött értéket, azok továbbra is a régi értéket használják, mely az alábbi példán is látható.
Lehetőség van mintaillesztést is magában foglaló kötések deklarálására, amelyeket jól használhatunk összetett kifejezések tagolására. Egy lista fejelemének leválasztását például megoldhatjuk egy (egyébként hasznos) szelektor függvény segítségével:
de ugyanezt egyszerűbben megtehetjük egy adattagoló kötéssel:
Egy másik példa:
Adattagoló kötések használata elsősorban akkor lehet előnyös, ha egy olyan speciális tagolásra van szükség, amelyre nem akarunk külön függvényt definiálni, azonban ne felejtsük el, hogy a mintaillesztés sikertelensége futási időben egy Bind kivételt vált ki.
A változók értéke nem változik, ugyanazt a nevet viszont lehet több deklarációban használni, ilyenkor az új deklaráció eltakarja a régit. Minden deklaráció hatóköre statikus, a kiértékelési sorrendtől független.
Az ML-ben lehetőség van a változók kötéseinek hatáskörének korlátozására, azaz lokális kötések létrehozására a következő nyelvi szerkezettel:
let deklarációk in kifejezés end
Lokális kötések létrehozására több okból is szükség lehet: egy összetett kifejezés átláthatóbbá tétele érdekében, vagy ha egy kifejezésben egy többször előforduló részkifejezést, a kiértékelésének mellékhatása miatt csak egyszer szeretnénk kiértékelni, például:
Vagy pl: másodfokú egyenletmegoldó:
Természetesen lehetőség van ilyen hatáskört lokalizáló blokkok tetszőleges egymásba ágyazására. A láthatóság szabályai a struktúrált programozási nyelvekben megszokott módon alakulnak: beágyazott let blokkokban szereplő lokális kötések eltakarják az azonos változókra vonatkozó blokkon kívüli kötéseket, valamint az azonos nevű formális paramétereket. Például n értéke a következő deklarációk után 25 lesz:
Nem csak kifejezésekben lehet lokális kötéseket létrehozni, hanem deklarációkban is, persze ezek ritkábban használatosak. A szintaxis a következő:
local deklarációk in deklaráció end
Pl.: local val n=5 in fun ot()=n end; :ez után az ot() 5-öt ad eredményül
Vagy pl: A jól ismert faktorképzés:
Az ML -ben megtalálhatóak az ún. szintaktikus cukorkák, amik lehetővé teszik, hogy ínfix operátokat / függvényeket írjunk, vagy a beépített függvényeket ínfix módon használjuk, és így áttekinthetőbbé tegyük programjainkat. Például a szintaktikus cukorkák nélkül a 3 + 5 -t, add(3, 5) -nek kellene írnunk, ami sokkal kevésbé átlátható kódot eredményezne. Például elhagyhatjuk a zárójeleket, a cons (::) operátor (amelyről részletesen a listáknál olvashatunk) infixé tételével. Az infixr kulcsszó biztosítja a jobbról asszociativitást:
Ezzel megkaptuk a lista adattípus definícióját. Megjegyzés: az [ 1,2,3 ] jelölés egy további kiegészítés.
Az ML-ben nem lehet az operátorokat túlterhelni. Persze ez nyilvánvaló, hiszen ellenkező esetben nem tudná azon kifejezések típusát levezetni, amelyek az operátort tartalmazzák. Van viszont egy igen jó tulajdonsága, nevezetesen lehet új operátorokat bevezetni, melyeket nem csak ínfix, hanem akár postfix vagy prefix formában is használhatunk, és itt még nem álltak meg a nyelv készítői, ugyanis az operátoroknak meg lehet változtatni a precedenciáját is (ezért van értelme a prefix operátornak, ugyanis a függvény precedenciáját nem lehet megváltoztatni). Az ínfix operátor alapértelmezésben balra köt (bal asszociatív), de lehet jobbra kötőnek is definiálni (gondoljuk meg, ez milyen jól jön például a hatványozás operátoránál).
Mint a legtöbb modern programozási nyelvben, az ML -ben is lehetséges saját adattípust létrehozni. Miután létrehoztuk az adattípusunkat, készíthetünk függvényeket, amelyek az új adattípust használják, és a mintaillesztést is ugyanúgy használhatjuk, mint a beépített adattípusok esetében.
Talán a legegyszerűbb példa felsorolási típusokra a C -ben vagy a Pascal -ban található.
datatype direction = north | east | south | west;
Négy konstruktor készül ezen deklaráció hatására, amiket mintaként (pattern) használhatunk függvények definiálásakor. Például készítsünk egy olyan right függvényt amely visszaadja azt az irányt amit 90 fokkal jobbra fordulva kapunk az eredetiből:
Ahogyan várható volt, ezeket a függvényeket pont úgy kezelhetjük, mint bármelyik másik (akár beépített) függvényt. Például:
A mintaillesztés működik adatkonstruktorokkal:
Alkothatunk olyan típusokat is amelyek adatokat hordoznak - ezek hasonlóak a Ada variáns rekord típusához. Minden változat (variáns) tartalmaz egy konstruktort és különböző komponenseket. Például: a pénz típus lehet kézpénz vagy csekk.
A kézpénz (cash) szereplő egész szám pennie -k számát adja meg, a csekk típus egy bank nevét és a számla egyenlegét tartalmazza font -ban. Például:
Mintaillesztést is használhatunk ezekre a típusokra, például:
A floor egy beépített függvény amivel valós számokat tudunk egésszé konvertálni.
Még egy példa a paraméteres adattípus
A paraméteres adatkonstruktor függvényként is használható, például az INT típusa függvényként int -> int_real.>
A típuskonstruktornak is lehet paramétere, egy vagy több típusváltozó:
Ahol szükséges, ott a fordító automatikusan behelyettesíti a konkrét típust, például a SOME 1 kifejezés típusa int option.
Több típusparaméter is megadható:
A rekord a direkt szorzat általánosítása:
a komponensekhez nevet lehet rendelni, így több adat is könnyen kezelhető
A rekord érték létrehozása:
{ nev=”Vajda Péter”, cím=”Bp.”, szul=”1982” }
Ennek a rekordnak a típusát így írhatjuk le:
Szelekciós függvény: #nevm #cim
Teljes minta:
Rövidítési lehetőség a változók nevére:
A nem használt mezők elhagyhatók, ha ismert a pontos típus:
Ilyenkor nyilván meg kell adni a típusát!
Amíg kicsik és egyszerűek a programok, struktúrájuk moduláris. Amikor nagy programokat írunk, néhány eljárást egységesítünk adattípus segítségével. ML-ben az adattípus strukturája pedig abstype ... with ... end.
Most implementálni fogjuk a halmaz absztract adattípusát.
Ha megengedett az egyenlőség a halmazokban, a következőképpen fogjuk implementálni.
Itt implementáljuk a halmaz adattítpust, mint rendezett listát.