Az ML modul rendszerének alapvetô elemei a szignatúrák és a struktúrák. A szignatúrák struktúrákat specifikálnak, szerepüket tekintve tehát más nyelvek interfészeihez, osztály típusaihoz, csomag specifikációihoz hasonlíthatjuk ôket, míg a szignatúrákat implementáló struktúrák az implementációk, osztályok vagy csomagok szerepét töltik be.
Egy szignatúra egy program modul, struktúra leírása, specifikációja. Egy struktúra tartalmazhat minden olyan deklarációt, amelyrôl eddig szó volt: típus konstruktorokat, kivételeket, kötéseket, és olyanokat is amelyek a modularizációt támogatják: alstruktúrákat és alstruktúrák megosztását. Egy szignatúra egy struktúra ezen deklarációink elemenkénti specifikációja. Egy struktúra megfelel egy szignatúrának vagy implementál egy szignatúrát, ha tartalmazza a szignatúrában specifikált, vagy azoknál általánosabb típusú deklarációkat.
Egy szignatúrakifejezés alakja a következô: sig specifikációk end
ahol specifikációk a következô alapelemekbol állhat:
A szignatúrakifejezéseknek egy szignatúrakötéssel nevet is adhatunk a következôképpen: signature azonosító = szignatúrakifejezés
Tekintsük a következô egyszerû példát, amelyben az a egy típusváltozó és használata által a sor alábbi specifikációja polimorf a sor elemtípusát illetôen:
Már meglévô szignatúrákat kétféleképpen használhatjuk újra, új szignatúrák definiálásához: beágyazással és specializációval. Nézzünk ezekre egy-egy példát:
A beágyazást tehát a szignatúra bôvítésére használhatjuk, míg a specializáció az implementációhoz visz közelebb, az absztrakt típusok reprezentációjának megadásával.
Fontos megjegyezni, hogy két szignatúra ekvivalens, ha a specifikációk páronként típusekvivalensek.
Egy struktúra egy deklarációk sorozatából álló program egység. Egy
struktúrakifejezés alakja a következô: struct deklarációk end
ahol deklarációk a korábbi fejezetekben részletezett típus, adattípus, érték
(val vagy fun kötés), és kivétel deklarációk sorozata.
Struktúrakötéssel a struktúrakifejezéseket is köthetjük egy-egy azonosítóhoz a következôképpen: structure azonosító = struktúrakifejezés
Példaként tekintsük a fenti QUEUE szignatúra következô polimorf implementációját (a két listás reprezentációt az indokolja, hogy az ML-ben beépítetten támogatott lista típus, mûveleteit tekintve inkább veremként mûködik):
Egy struktúra elemeire minôsített azonosítókkal hivatkozhatunk, például:
Lehetôség van továbbá struktúrák megnyitására az open struktúraazonosítók paranccsal, aminek hatására az adott struktúrák deklarációi a környezet részévé és minôsítés nélkül elérhetôvé válnak. Amilyen kényelmes, épp olyan veszélyes is ez a lehetôség, hiszen így könnyen, akaratlanul is felüldefiniálhatunk egyes azonosítókat. Éppen ezért óvakodjunk struktúrák megnyitásától, és olyankor is csak a hatáskört korlátozó blokkokon belül éljünk ezzel a lehetôséggel(a deklarációs részben).
Ahogyan minden (jól formált) kifejezésnek megvan a legáltalánosabb típusa, amelynek a típuskompatibilitás során van meghatározó szerepe, ugyanúgy minden (jól formált) struktúrának megvan a legáltalánosabb szignatúrája, amely a szignatúra megfelelés definíciójában játszik hasonló szerepet. Egy struktúra legáltalánosabb szignatúrája egy-az-egyben tartalmazza a struktúra típus, adattípus és kivétel deklarációit, és az érték deklarációkban (val vagy fun kötésekben) szereplô azonosítókat a hozzájuk kötött kifejezések legáltalánosabb típusával specifikálja.
Egy szignatúra Sig1 megfelel egy másik Sig2 szignatúrának, ha annak szigorításának tekinthetô a következô értelemben:
A definíció következménye, hogy a szignatúra megfelelés egy parciális rendezés, amely nem tesz különbséget ekvivalens szignatúrák között.
A fenti példák közül QUEUE_WITH_TEST és QUEUE_AS_LISTS megfelelnek a QUEUE szignatúrának, sôt látható, hogy általában minden szignatúra megfelel minden beágyazott szignatúrájának illetve minden specializált szignatúra megfelel annak a szignatúrának, amelybôl specializációval nyertük. A beágyazás és a specializáció tehát monoton mûveletek a megfelelésre mint részbenrendezésre nézve.
Végezetül egy struktúra pontosan akkor felel meg egy szignatúrának, amikor a legáltalánosabb szignatúrája. A fenti példában említett Queue struktúra tehát megfelel a QUEUE, sôt a QUEUE_AS_LISTS szignatúrának, de nem felel meg QUEUE_WITH_TEST-nek.
A fentiekben elmondottakon túlmenôen lehetôség van struktúrák példányosítására olymódon, hogy egy struktúrakötés során egy azonosítónak megfeleltetünk egy kívánt szignatúrát és egy annak megfelelô, azaz azt implementáló struktúrát. Kétféle megfeleltetés lehetséges: átlátszó és átlátszatlan, amely elnevezések a struktúrában deklarált típusok reprezentációjának nyílt vagy rejtett voltára vonatkoznak. A két megfeleltetés deklarációja a következô szintaxis szerint történhet:
Mindkét esetben követelmény, hogy struktúra a korábban vázolt szabályok szerint megfeleljen szignatúra-nak és mindkét esetben a megadott struktúra kötôdik mint implementáció az azonosítóhoz. A két deklaráció közötti eltérés abban áll, hogy míg az utóbbi (átlátszatlan) esetben az azonosítóhoz a deklarációban rögzített szignatúra kötôdik, addig az első (átlátszó) esetben az azonosítóhoz kötött szignatúra a deklarált szignatúrának az implementációból vett típusinformációkkal kiegészített szigorítása. Pontosabban a deklarációban szereplő struktúra legáltalánosabb szignatúrájából (amely a követelmények értelmében megfelel a deklarált szignatúrának) a deklarált szignatúra absztrakt típusainak struktúrabeli reprezentációja is megjelenik a megfeleltetett szignatúrában.
Tekintsük a következô példákat:
Mindhárom struktúra azonos, a Queue-ból "örökölt" két listával reprezentált polimorf sort valósít meg azonos módon. A második deklarációban átlátszó megfeleltetést alkalmaztunk, míg az elsôben és a harmadikban átlátszatlant. Ennek eredményeképpen
A második és a harmadik deklaráció tehát egyenértékû és jelen esetben az elsô változat ajánlott, amely, helyesen, elrejti a sor típus reprezentációját. A minél hatékonyabb modularizáció érdekében minden olyan esetben célszerû elrejteni egy szignatúrában szereplô típus reprezentációját, amikor csak lehetséges. Az átlátszóság elkerülhetetlen és értelmetlen abban az esetben, ha a szignatúra nem tartalmazza az absztrakt típus elemeinek létrehozásához szükséges érték konstruktorokat. Jó példa erre a következô specifikáció:
Ez a szignatúra csak valamilyen konkrét reprezentációval együtt használható, hiszen nincsen lehetôség t típusú értékek deklarálására.
Fontos megjegyezni, hogy átlátható megfeleltetés során (a megfeleltetett, eredő szignatúra meghatározásához) szükség van az implementáló struktúra forráskódjára, tehát az implementáció változtatása maga után vonja a hivatkozó kódrészek újrafordítását, esetleg megváltoztatását. Átlátszatlan megfeleltetéssel ezzel szemben implementációtól független kliens kódot hozhatunk létre.
Eddigieket összefoglalva valósítsuk meg a map adattípust példaként!
Érdemes elgondolkodni a Map print függvényének a megírásán! Ezt a kis apróságot az olvasóra bízom!
Nagyon hasznos eszköz az új modulok készítésére. Funktorok segítségével olyan struktúrát definiálhatunk, amelyet más struktúrákkal paraméterezhetünk.
A funktorok speciális többváltozós függvények, melyek bizonyos szignatúráknak megfelelő struktúrák teréről képeznek egy adott szignatúrának megfelelő struktúrák terére. A leképezés módját a funktor definíciója során adjuk meg. Az értelmezési tartományt szűkíthetjük az alapértelmezett Descartes szorzatról úgy, hogy megköveteljük egyes, a funktor paraméterrészében lévő szignatúrákban szereplő típusok ekvivalenciáját.
A következő példában egy t egyenlőséggel ellátott típust tartalmazó struktúrából csinálunk egy új struktúrát, amely ugyancsak megfelel a SIG szignatúrának, de a benne lévő t típus a paraméterben lévő típus direkt szorzata, az egyenlőség pedig a komponensenkénti egyenlőség.
Most készítsünk a SIG szignatúrának megfelelő struktúrát és készítsük el a funktorunk segítségével az új t direktszorzatot tartalmazó SS struktúrát:
Most szerepeljen egy példa arra mikor csonkítás történik.:
Funktorokat funktorok segítségével is definiálhatunk:
Az I használatára egy példa.:
Most legyen egy példa többváltozójú funktorra is, ahol még az értelmezési tartományt is szűkítjük. Itt egy olyan modult definiálunk, amely egy vermet és egy listát tartalmaz, de megkötjük hogy ugyazon elemtípust használják.
Ez a típusokra vonatkozó megszorítás szignatúrák definiálása esetén is alkalmazható:
Egy másik példa:
Kiterjesztés | Tartalom |
* .sig * .sml * .ui * .uo |
|
A unitid nevű modul fordítása és a mosml környezetébe való betöltése:
Itt a unititid.sig fájl egy darab szignatúra definíciót kell tartalmazzon, a unitid.sml pedig ennek a szignatúrának megfelelő struktúrát.
Egy példa a verem modulra:
A specifikáció stack.sig tartalma:
Az implementáció stack.sml tartalma:
a mosmlc –structure stack.sig stack.sml parancsal fordítva megkapjuk a stack.ui és a stack.uo lefordított egységeket.
A mosml környezetben a load “stack” parancsal be is tölthetjük a lefordított modult(fordítás után már nincs szükség a forrásállományokra).
Megjegyzés: az implementációs modulban a struktúrát csak átlátszatlan megfeleltetéssel hozhatjuk létre( :> és nem :), hiszen ha nem így tennénk az implementáció(pontosabban az implementációs modulban lévő reprezentáció) megváltoztatásakor nem lenne elég csak a modult újrafordítani, hanem az összes ezen modult felhasználó más programegységet is újra kellene fordítani, holott a modulokra bontás egyik alapvető koncepciója, hogy egymástól függetlenül fordítható programegységeket lehessen létrehozni. Átlátszó megfeleltetés esetén a legáltalánosabb szignatúra változna.
Funktorok segítségével is hozhatunk létre modulokat:
evaluate.sig tartalma:
evaluate.sml tartalma