A Limbo programozási nyelv

Absztrakt adattípusok

Absztrakt adattípus (ADT)

Az absztrakt adattípus vagy ADT olyan egység, amely több különböző típusú elemet tartalmaz és műveleteket definiál rajtuk. Az ADT a C rekord típusához hasonlít, de tartalmazhat műveleteket is, mint a C++ osztályok. Lényeges eltérés a C++ osztályokhoz képest, hogy az ADT végleges és nincs polimorfizmus.

A deklarálás általános alakja:

identifier: adt { adt-member-list };

Az ADT tagjait (változókat és függvényeket) ugyanúgy deklaráljuk, mint egyebként.
Példa:
Inventory: adt { id: string; onhand: int; cost: real; value: fn(item: Inventory): real; };

Ha egy ADT-t már deklaráltunk, a vele társított azonosító adattípus név lesz, ezért az ADT deklarációkat célszerű a modul deklarációs fájlokba (.m) írni.
Az ADT tagfüggvényeinek definíciója általában az implementációs fájlokban (.b) szerepel. A definíció megegyezik a Limbo függvények definíciójával, úgy, hogy a függvény neve elé odaírjuk az ADT nevét és a . operátort:
Példa:
Inventory.value(item: Inventory): real { return real(item.onhand) * item.cost; }

ADT típusú változó létrehozása:
Pl:
part1: Inventory;

ADT tagjait a . operátorral érhetjük el, és ugyanúgy használhatjuk őket, mint a közönséges változókat és függvényeket.
Példa:
part1.id = "Widget"; part1.onhand = 250; part1.cost = 4.23; sys->print("Value On Hand: $%5.2f\n", part1.value(part1)); # a fenti értékeket használva a kiírás eredménye: # Value On Hand: $1057.50

Ebben a példában ahhoz, hogy az ADT tagfüggvénye a tagadatokat használja, az ADT-re való hivatkozást explicit paraméterként át kell adni a függvénynek. Hivatkozhatunk az ADT-re implicit módon is, a self kulcsszó használatával. Hasonló a C++ és Java this kulcsszavához.
Példa:
Inventory: adt { id: string; onhand: int; cost: real; value: fn(item: self Inventory): real; }; Inventory.value(item: self Inventory): real { return real(item.onhand) * item.cost; } sys->print("Value On Hand: $%5.2f\n", part1.value()); # a korábban beveztett értékeket használva az utasítás eredménye a köv. kiírás: Value On Hand: $1057.50

Referencia ADT (ref ADT)


A ref ADT olyan ADT, melyet referencia típusként hozunk létre.
Például az Inventory ADT-t referencia típusként a köv. módon hozhatjuk létre:

part2:= ref Inventory; # ez az utasítás új Inventory-t hoz létre, és a part2 referencia lesz rá

A part2 hivatkozás a reguláris ADT-hez hasonlóan használható.
Példa:
part2.id = "Whatsit"; part2.onhand = 1000; part2.cost = 0.17;

Fontos különbség a part és part2 között, hogy nem azonos típusúak. Így pl. a következő utasítások fordítási hibát eredményeznek:

part2 = part1; # típus ütközés part2.value(); # paraméter típusa nem megfelelő

Ahhoz, hogy a ref ADT-ből elérjük az eredeti ADT értékeit, a (*) prefix operátort használhatjuk:

part1 = *part2; # helyes (*part2).value(); # helyes

Megoldható, hogy az ADT tagfüggvények ref ADT típusokat is elfogadjanak. Ehhez a deklarációban a ref kulcsszót kell használni.
Példa:
clear: fn(item: self ref Inventory);

A tagfüggvény definíciója a köv.:

Inventory.clear(item: self ref Inventory) { item.onhand = 0; }

A ref ADT legnagyobb előnye a sima ADT-vel szemben, hogy másolás nélkül függvényparaméter lehet, így a kód hatékonyabb (ez főleg nagy ADT-k esetén jelentős). Az ref ADT-t elfogadó tagfüggvények felülírhatják argumentumaik tartalmát, ami érték ADT-k esetén nem tehető meg.
A ref ADT előnye egyben nagy hátránya is, ugyanis az értékek felülírásának lehetősége miatt a ref ADT interfész homályosabb, zavarosabb az érték ADT felületnél.

Pick ADT


A pick ADT hasonló a C union típusához. Az egyszerű ADT deklaráció különböző típusú ADT-kre példányosítható. A pick ADT a különböző példányokban szereplő közös adatok, az egyes példányokra specifikus adatok és tagfüggvények együttese.
A pick ADT két részből áll:

Pick deklaráció általános formája:

identifier: adt { ... pick { tag-identifier => declaration; ... tag-identifier => declaration; ... } };

Minden változatnak van egy ún. tag-identifier-je, ami lokális az ADT-re nézve, és deklarációk írhatók bele.
Példa:
Apick: adt { str: string; pick { String => val: string; # sztring elem Int => val: int; # egész elem Real => val: real; # valós elem Nothing => ; # üres elem } };

A példában szereplő ADT közös adattagja a string típusú str. A pick elemek a String, Int és Real, melyek az Apick ADT három változatát specifikálják.
Ha az ADT-nek van közös adattagja, a pick blokknak utolsóként kell szerepelnie az ADT deklarációjában, csak a tagfüggvények deklarációja állhat utána.
A pick ADT-t ref ADT-ként kell példányosítani, és meg kell adni a specifikus elemet.
Példa:
r := ref Apick.Real;

Amikor az ADT adattagjához értéket rendelünk, a pick utasításban deklarált változót használjuk:
Példa:
r.str = "Pi"; r.val = 3.141593;

ADT példány létrehozása és a kezdeti értékadás a következő formában történik:
Pl.:
s := ref Apick.String("Greeting", "Hello, World!");

A tagof operátor segítségével lekérdezhetjük egy meghatározptt ADT példányban lévő pick elem indexét. Az elemek 0-tól indexelődnek.
Példal:
tagof r; # az Apick ADT r példányának indexével, vagyis 2-vel tér vissza

A pick szerkezet lehetővé teszi a pick ADT különböző típusain alapuló utasítások kiválasztását. Általános formája:
t: ref Apick = s; pick u := t { String => sys->print("%s is %s\n", u.str, u.val); Int => sys->print("%s is %d\n", u.str, u.val); Real => sys->print("%s is %f\n", u.str, u.val); Nothing => sys->print("%s is Nothing\n", u.str); }

A pick utasítás az ADT-re való ( ún. refadtinst) referencia lokális másolatát (localinst) használja. Az elem(ek) a pick ADT-ben deklarált elemek közül való(k).Pl.:

printval(a: ref Apick) { pick t := a { String => sys->print("%d: %s\n", tagof t, t.val); Int => sys->print("%d: %d\n", tagof t, t.val); Real => sys->print("%d: %f\n", tagof t, t.val); Nothing => sys->print("%d: Nothing\n", tagof t); } }

A pick utasítást megelőzően az s példány ref ADT-jének kezdeti értéket adunk. Ezután a pick utasítás kiválasztja az ADT különböző típusai alapján a futtatandó utasításokat.

A végrehajtás egy másik módja, amikor a pick utasítást egy függvényben helyezzük el, melynek paraméterül adható a ref ADT.
Példa:
printval(a: ref Apick) { pick t := a { String => sys->print("%d: %s\n", tagof t, t.val); Int => sys->print("%d: %d\n", tagof t, t.val); Real => sys->print("%d: %f\n", tagof t, t.val); Nothing => sys->print("%d: Nothing\n", tagof t); } }

A függvény meghívásakor paraméterül adjuk az ADT pick elemet specifikáló példányát:

printval(s);

Másik lehetőség, hogy a fenti függvényt az ADT tagfüggvényeként deklaráljuk. Ekkor az ADT implicit paraméterként használható.
Példa:
Apick.printval(v: self ref Apick) { pick t := v { String => sys->print("%d: %s\n", tagof t, t.val); Int => sys->print("%d: %d\n", tagof t, t.val); Real => sys->print("%d: %f\n", tagof t, t.val); } }

Ekkor a függvényhívás a következő:

s.printval();