A Theta programozási nyelv

Specifikációk

Chapter 1

Új típusok és rutinok specifikáció segítségével vezethetők be. A specifikációk lehetnek paraméterezettek is.

Önálló (Stand-Alone) Rutin Specifikációk

Az önálló rutin specifikáció definiálják egy önálló eljárás vagy iterátor interfészét. Az eljárásoknak és iterárátoroknak lehetnek paramétereik (nulla, egy vagy több), és befejeződhetnek normálisan, vagy valamilyen kivétel kiváltásával, és különböző számú és típusú értéket adhatnak vissza a különböző osztályokban. Egy iterátor adhat egy vagy több közbenső visszatérési értéket, de nem lehet visszatérési értéke normál termináláskor. Íme néhány példa:

% eljárások:
search (a: array[int], x: int) returns (int) signals (not_found)
combine (x: sequence[int]) returns (int)
% iterátor:
elements (a: array[int]) yields (int)

A rutin specifikációk formája a következő:

        <routine_interface> -> <proc_interface> | <iter_interface>
<proc_interface> -> <idn> [<parms>] <formal_args> [<returns>] [<signals>] [<where>]
<iter_interface> -> <idn> [<parms>] <formal_args> <yields> [<signals>] [<where>]

A params részt akkor használjuk, ha a rutin paraméterezett. Ezt a későbbiekben tárgyaljuk. A formal_args rész definiálja a rutin formális paramétereit:

        <formal_args> -> "(" [ <decl> ["," <decl>]* ] ")"

A returns rész az eljárás visszatérési értékeinek felsorolása:

        <returns> -> returns "(" <type_designator> ["," <type_designator>]* ")"

Lehet nulla, vagy több visszatérési érték, az első esetben ez a rész el is maradhat.

        <yields> -> yields "(" <type_designator> ["," <type_designator>]* ")"

Egy iterátornál a yields rész megadása mindig kötelező, és a visszaadott elemeknek mindig tartalmazniuk kell legalább egy értéket. A signals rész a rutin által kiváltható kivételek, és ezek visszatérési paraméterei típusainak felsorolása:

        <signals> -> signals "(" <exception> ["," <exception>]* ")"

ahol

        <exception> -> <name> ["(" <type_designator> ["," <type_designator>]* ")"]
<name> -> <idn>

Minden kivétel nevének egyedinek kell lennie, és nem lehet a "failure", hiszen ez minden rutin fejlécéhez explicit hozzáadódik, tehát ez mindig kiváltható egy sztring paraméterrel.

A rutin típusa egyszerű módon származtatható a rutin interfészéből: minden információ az interfészből lényeges, kivéve a rutin nevét és a formális paraméterek nevét. Például:

        a combine eljárás típusa: "proc (sequence[int]) `returns` (int)"
a search eljárás típusa: "`proc` (array[int], int) `returns` (int) `signals` (not_found)"
a elements iterátor típusa: "`iter` (array[int]) `yields` (int)

Típus specifikációk

A típus specifikáció definiálja a típus interfészét. Ennek formája:

<type_interface> -> <idn> "=" type [<parms>] [<supertypes>] [<where>]
[<interface_or_equate>]*
end <idn>

ahol

        <interface_or_equate> -> <routine_interface> | <equate>

Az kezdő idn rész az új típus neve; ezt használjuk az egész specifikáció alatt a típus azonosítására. A befejező idn-nek meg kell egyeznie ezzel. A params és a where rész csak akkor használatos, ha paraméterezett típusról van szó. Ebben a részben csak a paraméter nélküli típusokról lesz szó.

A supertypes rész az új típus szupertípusainak (ezekből származik az új típus) a felsorolása, kivéve az "any" típust, mert ez minden típus szupertípusa, ezért ezt soha nem kell, és nem is lehet itt felsorolni. A pontos forma:

        <supertypes> -> "<" <super_info> ["," <super_info>]*
<super_info> -> <type_designator> ["{" <renames> ["," <renames>]* "}"]
<renames> -> <idn> for <idn>

A type_designator a super_type részeként azonosítja az új típus közvetlen ősét. Az opcionális renames rész a szupertípus metódusainak átnevezését; az első helyen megadott nevű metódus átnevezésre kerül a második helyen megadott névvel. Például:

stack = type < bag {push for put, pop for get}

Ekkor a "stack" altípusa lesz a "bag"-nek, a "bag" "put" nevű metódusa a "stack"-ben "push" néven lesz elérhető, és hasonlóan a "get" "pop" néven.

A definíció hátralévő része a típus metódusainak interfészeiből áll össze. Minden metódushoz egy rutin interfész tartozik, és a metódus típusa ebből az interfészből származik a szokott módon. Minden metódusnak egyedi névvel kell rendelkeznie. A metódusok eljárások vagy iterátorok lehetnek A metódushoz tartoznia kell rutin interfésznek, ha (1) nincs megfelelő metódusa a szupertípusnak, vagy (2) az altípus signatúrája különbözik a szupertípus megfelelő metódusának szignatúrájától. Ezekben az interfészekben az átnevezés utáni (ha ilyenek vannak) neveket kell használni.

A metódusok és az ő szignatúrájuk meghatározzák az altípus deklarációjának helyességét. Az altípusnak az őse minden metódusával rendelkeznie kell, és az altípus minden metódusa altípusa kell legyen az ős megfelelő metódusának. Ha ezen feltételek nem teljesülnek, fordítási hibát kapunk.

A típus specifikáció nem biztosít eszközt objektumok létrehozására a "semmiből" (azaz nincs konstruktor). Ehelyett önálló rutinokat kell hívnunk, hogy új objektumot készíthessünk.

Paraméterezett specifikációk

A rutin és típus specifikációk paraméterezhetők valamilyen típussal. Ebben az esetben a where részben tudunk megkötéseket tenni a paraméterre (milyen metódussal kell rendelkeznie).

        <parms> -> "[" <idn_list> "]"
<where> -> where <restriction> ["," <restriction>]*
<restriction> -> <idn> has <nonparam_interface> [, <nonparam_interface>]*
<nonparam_interface> -> <idn> <nonparam_proc_sig> | <idn> <nonparam_iter_sig>
<nonparam_proc_sig> -> "(" [ <type_list> ] ")" [<returns>] [<signals>]
<nonparam_iter_sig> -> "(" [ <type_list> ] ")" <yields> [<signals>]
<type_list> -> <type_designator> ["," <type_designator>]*

A paraméterek típusok lehetnek. A where részben felsorolhatjuk, hogy a paraméternek milyen metódusokkal kell rendelkeznie. Például:

find [T] (a: collection[T], v: T) returns (int) signals (not_found)
where T has equal (T) returns (bool)

Definiáltunk egy paraméterezett rutint egy "T" paraméterrel. A "T" paraméter csak olyan típus lehet, mely rendelkezik egy equal (T) returns (bool) metódussal.

A paraméterezett típusnak lehet deklarált szupertípusa, például:

stack = type[T] < item, bag[T], stack_pair[T, int]

Ekkor a "stack" minden példányosítása altípusa az "item"-nek, a "bag" és a "stack_pair" megfelelő példányosításának is (, azaz "stack[foo]" altípusa "item"-nek, "bag[foo]"-nak és "stack_pair[foo, int]"-nak is). Nincs altípus/szupertípus reláció viszont a paraméterek között. például: "stack[foo]" nem altípusa "stack[bar]"-nak, még akkor sem, ha "foo" egy deklarált altípusa a "bar"-nak. Ezt úgy szokták megfogalmazni, hogy az altípus vizsgálat csak a szögletes zárójelen kívül vizsgálódik.