A statikus kellékeken, ismereteken túl:
Annak definiálását, hogy milyen leképezést valósítanak meg az egyes műveletek (szemantika-definíció), és hogyan lehet „megszólítani” őket (szintaktika-definíció):
A fenti 2-3.-at egyaránt vonatkoztathatjuk algoritmusra
és konkrét programozási nyelvre. A következő („1.3. A
modul”) fejezetben mi az algoritmikus nyelvünket bővítjük
ki úgy, hogy kezelhető legyen benne e kérdéskör. Mivel nem állunk meg a puszta
elmélet síkján tisztázzuk azt is („
Röviden utalunk a nyelv azon kiegészítésére, amelyben tisztázni fogjuk „globális” elvárásainkat az adott típussal szemben. Ezt a legabsztraktabb elképzelést formálisan egy algebrai alapú nyelven írjuk le, az alábbi keretben:
Szintaktikai „minta” |
Magyarázat |
Típus TípusKonstrukció(paraméterek): Asszociált műveletek: Operáció(értelmezési
tartomány): Értékkészlet … |
A definiálandó típuskonstrukció
„alkalmazási sablona” (feje). Operációinak szignatúrája
(értelmezési tartománya, értékkészlete) |
Axiómák: Jelölések: objektum: Típus(paraméterek) = … 1o …
az axióma informális szövege ... a
formalizált axióma 2o … … |
Az
operációk kapcsolatát leíró axiómák, és a bennük fölhasznált objektumok
„deklarációi”.
|
Mint a példából is kiderül alkalmazunk néhány konvenciót, amelyet most –az idő rövidsége miatt– nem taglalunk. (A részletekhez lásd a Típus-specifikációkban.)
Visszatérő példa legyen a hét napjainak típusa! Nézzük ennek egy lehetséges algebrai leírását! Megjegyzem: a példa nem „tipikus” bizonyos értelemben, mivel a napok típusa egy konkrét típus, így nem tudunk élni a fenti típus-specifikálási „nyelvezet” paraméterezési szolgáltatásaival.
Példa: |
Típus TNap: Hétfő,Kedd,Szerda,Csütörtök, Axiómák: a:TNap n:Egész 0o A minimális érték a Hétfő, a maximális a Vasárnap, a típus számossága 7. Számosság=7 Ù Min=Hétfő Ù Max=Vasárnap 1o Létrehozás utáni értéke Hétfő. a=Létrehoz Þ a=Hétfő 2o Lerombolás után nincs értelme a műveletek (a Létrehoz kivételével) alkalmazásának. Következő(Lerombol(a))=NemDef Ù … 3o Nem létezik Hétfőt megelőző, s Vasárnapot követő. Előző(Hétfő)=NemDef Ù Következő(Vasárnap)=NemDef 4o A Következő és Előző műveletek egymás inverzei. $Előző(a) Þ Következő(Előző(a))=a Ù $Következő(a) Þ Előző(Következő(a))=a 5o A Sorszám és TNap műveletek egymás inverzei. nÎ[0..Számosság) Þ Sorszám(TNap(n))=n Ù 6o A fenti felsorolás sorrendjében növekvően rendezettek. Következő(Hétfő)=Kedd Ù Következő(Kedd)=Szerda … 7o A
Hétfő sorszáma Sorszám(Hétfő)=0 Ù Sorszám(Vasárnap)=6 Állítás:
Sorszám(a)Î[0..Számosság); és a Sorszám(a)=a fenti felsorolásbeli pozíciója
+ 1. Bizonyítás: Nyilvánvaló. 8o A felsorolásban előbb
szereplő kisebb, mint bármely a<b Û Sorszám(a)<Sorszám(b)[2]… |
Elsőként a programozói felületet leíró, ún. export modul szintaxisát adjuk meg:
ExportModul TípusNév(InputParaméterek):
[most következnek az exportálandó fogalmak „értelmes” csoportosításban:]
Típus
[ritkán van ilyenre
szükség]
Tip1,Tip2,... [csak azonosítók felsorolása!]
Konstans
[ritkán van ilyenre
szükség]
Kons1,Kons2,...[a TípusNév típus néhány kiemelendő
konstansa]
[az
alábbi részben soroljuk föl a típushoz tartozó tevékenységek „fejsorait”
és működésüket leíró elő-utófeltételeket]
Függvény Fv1(FormParam): FvTip1
Ef: …
Uf: …
Függvény Fv2(FormParam): FvTip2
Ef: …
Uf: …
…
Eljárás Elj1(FormParam)
Ef: …
Uf: …
Eljárás Elj2(FormParam)
Ef: …
Uf: …
…
Operátor Op1(FormParam): OpTip1
Ef: …
Uf: …
Operátor Op2(FormParam): OpTip2
Ef: …
Uf: …
…
Modul
vége.
Megjegyzések:
Példa: |
ExportModul Tömb(Típus TIndex: Típus TElem): A fenti definíció után a felhasználás
szintaxisa:
|
Ki fogjuk egészíteni az algebrai specifikáció műveletkészletét továbbiakkal is: egyenlőség-vizsgálattal, értékadással, beolvasás és kiírás műveletekkel, valamint a hibás elvégzés tényét rögzítő hiba-flag-et kezelő függvénnyel. (Ezek nélkül ugyanis felhasználhatatlan eszköz maradna a legtöbb típus, vagy mindenegyes esetben a programozónak külön kellene jól-rosszul ezen műveletekkel kiegészítenie.) Mindezt annak ellenére tesszük, hogy sok esetben a programozási nyelvek nem adnak mindegyikre (pl. a „típusos” be/ki-re) módot.
Egy-két típusnál szokatlan lehet valamely létrehozó, ill. leromboló művelet használatának explicit felkínálása, mivel a programozási nyelvek legtöbb implementációja automatikusan gondoskodik a létrehozásról. (Pl. Tömb, Táblázat, Statikus gráf.) A leírás teljességére való törekvés, illetve az esetleges „hagyományostól” eltérő ábrázolás lehetővé tétele mégis indokolttá tehetik ezt.
Példa: |
ExportModul
TNap: [kiindulási alap az algebrai leírás] Operátor Min:TNap Operátor Max:TNap Operátor Számosság:Egész Függvény Következő(Változó x:TNap):TNap[SzP1] Függvény Előző(Változó x:TNap):TNap Függvény Sorszám(Konstans
x:TNap):Egész Függvény TNap(Változó x:Egész):TNap
Infix Operátor
Egyenlő(Konstans x,y:TNap):Logikai Infix Operátor LegyenEgyenlő(Változó
x:TNap Infix Operátor Kisebb(Konstans
x,y:TNap):Logikai Operátor Be(Változó x:TNap) Operátor Ki(Konstans x:TNap) Függvény Hibás?(Változó x:TNap):Logikai az előfeltétele nem teljesült) az adott TNap típusú (x) adatra; majd inicializálódik a hiba-flag] Modul vége. |
Megjegyzések:
· A Létrehoz és Lerombol műveleteket kihagytuk az algoritmikus specifikációból. Ezzel eltértünk az algebrai specifikációtól. Ok: statikusan kívánjuk megvalósítani e felsorolástípust, építünk a blokk-struktúrájú nyelvek adatlétrehozó, -leromboló mechanizmusainak létére. (Gondoljon az eljárás-/függvény-paraméterekre és a lokális adatok kezelésére!) A Létrehoz-axióma persze még fel kell bukkanjon a megvalósításban a szükséges kezdőérték megadása miatt.
· A Min, Max és Számosság konstansokat (prefix) 0-változós operátorként definiáltuk, s nem konstansként, mert alkalmazásuk speciális szintaxisú (típusparaméterű). Vegyük észre, hogy ezek a konstansok minden rendezett, illetőleg véges típus esetén értelmesek. Így a használó program szintjén való megkülönböztetés érdekében olyan szintaxist kell hozzájuk rendelni, amely lehetővé teszi a típushoz kapcsolást. Ilyen probléma nem vetődik föl a többi (Hétfő, Kedd stb.), natív konstanssal kapcsolatban. (Ez a magyarázata a Min’Típus, Max’Típus, illetve a Számosság’Típus különleges szintaxisának.)
Példa: |
… … |
· Látható, hogy akkurátusan törekedtünk –bármi áron is!–, hogy az utófeltételekben ne nyúljunk vissza más művelethez, csak saját „hatáskörben” definiáltuk az elvárásokat. Ha ezt az elvet nem tartottuk volna tiszteletben, akkor egyszerűen meg lehetett volna fogalmazni a := és = operátorok utófeltételét. Például:
Egyenlő.Uf: x=y Û Sorszám(x)=Sorszám(y).
Ekkor azonban könnyen előadódhatna az a helyzet, hogy egymásra kölcsönösen hivatkozva specifikáljuk őket, ami egy logikailag értelmezhetetlen specifikációt eredményezne.
· Az értékadás és az értékazonosság operációkról feltesszük, hogy bitről-bitre történő másolást, illetőleg azonosságvizsgálatot jelent, ezért nem tartjuk fontosnak feltételekkel körülbástyázni. Másrészt, ha ezt formálisan is le akarnánk írni, vissza kellene nyúlni (esetleg bit-szintű) reprezentációig, amit effajta specifikációnál elkerülendőnek tekintünk.
· Az időnként felbukkanó „…” folytatás jelzése formálisan megengedhetetlen, azonban itt csupán egy példáról van szó, így megelégedtünk a kitalálható folytatás jelzésével.
· Fölvethető kérdés: mennyire ugyanarról szól a kétféle specifikáció. Valóban ez külön vizsgálandó, sőt bizonyítandó probléma.
· A „Be:” és „Ki:” operátorok működésénél két problémát kell tudni formalizálni:
1. a külvilággal való kapcsolatét (amely karakteres alapú),
2. Szöveg↔TNap transzformációét.
Az 1. általános megoldására bevezettünk két függvényt. Az Input megadja azt a szöveget, amelyet a beolvasó utasítás talál az input-pufferben; az Output szimbolizálja az output-puffer állapotát (azt a szöveget, amely kiíródik a végrehajtás során, pl. a képernyőre). Így a 2. probléma már kezelhetővé vált, hisz az adat és transzformáltja is egy-egy memóriabeli objektumban csücsül. Fel kell figyelnünk a szöveges és a „belsőállapotú” konstansok merev megkülönböztetésére (’hétfő’≠ Hétfő…)!
· Operátornak azt az alprogramot nevezzük, amely az eljárástól vagy függvénytől eltérő szintaxissal építhető be a programba (azaz hívható). Lehet Infix, ekkor a két operandus közé illeszkedik, lehet Prefix, ekkor megelőzi az egyetlen paraméterét, ill. Postfix esetben követi. S lehet „egyéni” szintaxisa, ekkor a Másnéven kulcs-szónál adhatjuk meg ezt. A leggyakoribb eset a Prefix, ezért ezt a minősítőt elhagyhatjuk.
· Egyik-másik operátornál „meglepő” a paraméter hozzáférési joga. A várható konstans helyett változó lett azért, mert a végrehajtás során hiba következhet be, amely által az „állapota” kényszerűen és szándékunk ellenére megváltozik. (Következő, Előző… Hibás?. Nagyjából azoknál, amelyeknél nem üres az előfeltétel, tehát van „esélye” a hibás használatnak. Pontosan azoknál, amelyeknek bármi köze is van a hiba-flag-hez.)
Folytassuk a megvalósítást betetéző fogalommal, a reprezentációs-implementációs modullal, röviden a modullal! Ennek feladata, hogy amit eddig elterveztünk, azt kidolgozza: azaz tisztázza, hogy
A modul-szintaxis a következő:
Modul TípusNév(InputParaméterek):
Reprezentáció
…
[a
típusábrázoláshoz szökséges adatleíró „kellékek”:
konstansok,
saját típusok,
a
változó-deklarációs rész már magának a típusnak a „mezőit” határozza meg]
Implementáció
[itt
szerepelnek az export-modulban „megjósolt”
tevékenységek
működésének részletezése…]
Függvény Fv1(FormParam): FvTip1
Ef: …
Uf: …
…
Függvény Fv2(FormParam): FvTip2
Ef: …
Uf: …
…
Eljárás Elj1(FormParam)
Ef: …
Uf: …
…
Eljárás Elj2(FormParam)
Ef: …
Uf: …
…
Operátor Op1(FormParam): OpTip1
Ef: …
Uf: …
…
Operátor Op2(FormParam): OpTip2
Ef: …
Uf: …
…
Inicializálás
[egy ilyen típusú adat létrehozása az itt leírt utasításokkal történik]
Modul
vége.
Megjegyzések:
·
Az egyes műveletek egyedi specifikációját
jelentő elő-utófeltétel párok, amennyiben megegyeznek az exportmodulbelivel,
nyilván elhagyhatók. Természetesen „értelmesen” igazítva a reprezentációhoz,
újabb biztonsági támpontul szolgálhat a fejlesztéshez. (Ekkor persze újabb
feladatként járul az eddigiek mellé, hogy ezek következését az „elődjükből” be
kell látni:
operációExportmodul.Ef Þ operációModul.Ef
Ù operációExportmodul.Uf
Þ operációModul.Uf.)
Példa: |
Modul TNap: [kiindulási alap az export modul] Változó Konstans Implementáció Operátor Min:TNap Min:=Hétfő Operátor Max:TNap Max:=Vasárnap Operátor Számosság:Egész Számosság:=7 Függvény Következő(Változó x:TNap):TNap Ha felsKód=Vasárnap.felsKód akkor hiba:=Igaz Függvény Előző(Változó x:TNap):TNap … Függvény Sorszám(Konstans
x:TNap):Egész … Függvény TNap(Változó x:Egész):TNap … Infix Operátor Egyenlő(Konstans
x,y:TNap):Logikai Infix Operátor LegyenEgyenlő(Változó
x:TNap Infix Operátor Kisebb(Konstans
x,y:TNap):Logikai Operátor Be(Változó x:TNap): Operátor Ki(Konstans x:TNap): … Függvény Hibás?(Változó x:TNap):Logikai … Inicializálás hiba:=Hamis; felsKód:=Hétfő Modul vége. |
Megjegyzés:
Az nyilvánvaló, hogy a Pascal-ban
Algoritmikus |
Þ |
Pascal |
Operátor
Min:TNap |
Þ |
Procedure Min(Var minKi:TNap); |
· A unit szintaxisa igazodik a Pascal nyelvhez, így túl sok magyarázatra nincs szükség. Annyit előljáróban: a unit egyesíti az exportmodul (Interface-rész) és a modul (Implementation-, inicializáló rész) fogalmat.
Unit TipusNev;{InputParaméterek}
Interface
…
{a
kívülről láttatandó fogalmak:
unit-ok,
konstansok, típusok, változók; eljárások
és függvények fejsorai}
…
Implementation
…
{az
elrejthető reprezentációs dolgok:
unit-ok,
konstansok, változók, típusok, „saját”
eljárások és függvények;
az
exportált eljárások és függvények törzsükkel
együtt}
…
Begin
…
{a
típus adatainak inicializálását végző utasítások}
…
End.
Megjegyzések:
Például: írható saját ClrScr-eljárás, amelyben
építhetünk a Crt unit hasonló nevű eljárására: |
… … Begin ClrScr(’Ügyes
kis program’); |
Az alábbi példa bemutatja, hogyan lehet a TNap típust unit-tal megvalósítani. A Pascal nyelv fentebb említett kellemetlen vonásai miatt a TNap-értékű függvényeket procedure-ákkal kell helyettesíteni.
Megjegyzések:
Példa: |
Program AProgram; Begin … End. |
Egy más elvű megoldást is találhatunk, ami azonban nem a modulból, hanem az exportmodulból indul ki. Ötlete a következő: a napok absztrakt felsorolását egészítsük ki egy további, pl. NemDef-fel elkeresztelt értékkel. Az előbbi TNapK és TNap kettőse helyett ábrázoljuk így:
Típus
TNapK=(Hétfő, Kedd, Szerda, Csütörtök, Péntek,
Szombat, Vasárnap, NemDef)
Változó érték:TNapK
Ekkor egyszerű értéktípusúak lesznek az eleddig rekord-típusú értéket szolgáltató függvények. Pl.:
Függvény Következő(Változó x:TNap):TNap
Ef: x¹Vasárnap
Uf: x=Hétfő Þ Következő(x)=Kedd
Ù …
Ha érték=Vasárnap akkor x:=NemDef
különben Következő:=Következő[SzP3] (érték)
Függvény vége.
Így a Pascal-megfeleltetés úgyszólván problémamentes:
Unit TNapUnit;{InputParaméterek}
Interface
Type
TNapK=(Hetfo,Kedd,Szerda,Csutortok,
Pentek,Szombat,Vasarnap, NemDef);
{a TNap=Record ertek:TNapK End helyett:}
TNap=TNapK;
…
Function Kovetkezo(Var x:TNap):TNap;
…
Implementation
…
Function Kovetkezo(Var
x:TNap):TNap;
{Ef: x<>Vasarnap
Uf: x=Hetfo Þ Kovetkezo(x)=Kedd
ES …}
Begin
If
x=Vasarnap then x:=NemDef
else Kovetkezo:=Succ(x)
End;
…
A unitok nyilvánvaló hátrányát igyekszik kiküszöbölni az „állomány automatikus beillesztés” lehetősége. A lényeg: a Pascal fordítóprogramot „rávesszük”, hogy a kódtöredéket tartalmazó file-t a befoglaló program adott pontján illessze be. Ennek módja: {$i töredék-file-név} direktíva az adott helyen.
Példa: |
Legyen az alábbi töredék a TNap.inc nevű file-ban: {TNap Type Const Procedure Kovetkezo(Var
x:TNap; Var kovetkezoKi:TNap); … A felhasználás: Program
AProgram; Begin End. |
Megjegyzés:
Objektum, helyesebben objektum-osztály fogalma számunkra azzal az előnnyel jár, hogy a programon belül megvalósíthatjuk a megfelelő típusfogalmat, azaz az értékhalmaz és művelethalmaz egységét, egységbezárását (encapsulation).[3] A szintaxisáról elegendő ennyit tudni:
…
Type
TipusNev=Object
…
{mező-deklarációk, amit itt az objektum
attribútumainak
vagy tulajdonságainak hívnak}
…
{a
típus műveleteinek, metódusainak fejsorai következnek:}
Constructor EljC(…); {elhagyható}
Destructor EljD(…); {elhagyható}
Procedure Elj1(…);
…
Function Fv1(…):TFv1;
…
End;
{a
típus műveleteinek kifejtése:}
Constructor TipusNev.EljC(…);
…
Begin
…
End;
Destructor TipusNev.EljD(…);
…
Begin
…
End;
Procedure TipusNev.Elj1(…);
…
Begin
…
End;
…
Function TipusNev.Fv1(…):TFv1;
…
Begin
…
End;
…
Var
a,b,c:TipusNev;
…
Begin
…
a.EljC(…);
{az a
objektum konstruktorának „hívása” a létrehozás
kedvéért}
a.Elj1(…);
{az a
objektum Elj1 műveletének „hívása”}
…
Megjegyzések:
Példa: |
Legyen a töredék az alábbi, ONap.inc nevű file-ban: {Osztály ONap[4] Type Type End; … Function ONap.Szamossag:Word; Procedure ONap.Kovetkezo(Var
kovetkezoKi:ONap); … A felhasználás: Program
AProgram; Begin End. |
ProgramozásMódszertan
2. előadás’2005 (vázlat)
1. A
modul mint a típusmegvalósítás kerete.
Amit
már tudunk – „statikus kellékek”
Miket
kell megadni ahhoz, hogy mondhassuk: a típust „teljesen” ismerjük?
Mit
értünk a típus megvalósítása alatt?
1.2.
A típus algebrai specifikációjáról
2. A
modulfogalom és a Pascal nyelv
2.2.
Kódtöredék automatikus beillesztése
[1] A konstansokat 0-változós függvényeknek tekintjük.
[2] <:TNapxTNap→Logikai rendezés visszavezetése <:EgészxEgész→Logikai rendezésre.
[3] Persze elismerjük, hogy ennél jóval több újdonságot rejt az objektumok fogalma, számunkra most ennyi pontosan elegendő.
[4] Az „O” a „T” helyett utalás, hogy „Objektum-Osztály”-ról van szó. Csak „helyi” konvenció!
[SzP1] Lehetne így is:
Függvény Következő(Konstans x:TNap): TNap
És ha hiba történt, akkor e tényt a TNap típusú eredmény-adat árulja el, az ő hiba-flag-je Igaz. Ez azonban sok gondot vet föl. Pl. egy kifejezés eredményeként születik egy hibás TNap-konstants?!?
[SzP2]Típus-konstrukciós függvény
[SzP3]Ez a TNapK nem pedig az éppen definiálandó TNap Következő művelete.