93
ABSZTRAKCIÓ
ABSZTRAKCIÓ Az informatika arra való, hogy a valós világról ismereteket szerezzünk, és ezeket automatikusan fel tudjuk dolgozni. A valós világot azonban nem tudjuk egy az egyben leírni, alkalmazzuk az absztrakciót: a közös jellemzőket kiemeljük, a különbözőket elhanyagoljuk, és innentől kezdve a közös jellemzőkkel írjuk le. Az absztrakció megjelenik a programozásban is: −
Procedurális absztrakció: Az alprogram paraméterezhető, de típus nem adható át paraméterként. A specifikáció és az implementáció elválik.
−
Adatabsztrakció: −
Első lépés a típus megjelenése az adatabsztrakció terén. A típussal rendelkező objektum egy csomó konzisztencia ellenőrzésen megy keresztül.
−
Következő lépésben megjelenik a saját típus definiálási lehetőség. Kérdés, hogy ténylegesen új típust hozunk-e létre.
−
A harmadik lépés: az absztrakt adattípus - Abstract Data Type (ADT) megjelenése. Vannak nyelvek, amelyek lehetővé teszik, hogy ne csak saját típust definiálhassunk, hanem a típusokhoz saját műveleteket is megadhassunk. Úgy tudunk saját típust megadni, hogy a típushoz megadom a típushoz tartozó elemek reprezentációját és típust kezelő műveleteket. Mindezt úgy, hogy az adott típus elemeihez csak irányított módon, a megadott műveleteken keresztül lehet hozzáférni. Az absztrakt adattípus elemeinek a reprezentációja nem látható, elválik a specifikáció és az implementáció. A viszonylag késői nyelvekben jelenik meg ez az eszközrendszer.
Az absztrakt adattípus defíniálása során három szempontot kell figyelembe venni: −
Reprezentáció: Az új típus értékeit reprezentálni kell. Ez a reprezentáció egy valamilyen korábbi típus reprezentációján nyugszik. A beépített típusokon alapszik, és azoktól teljesen független. Ld. minden adatszerkezet reprezentálható egydimenziós tömb segítségével. Kívülről nem látható, hogy ez tömb. Míg például a Pascalban egy byte típusnak ismerem a reprezentációját, el tudom érni a bitjeit. Absztrakt adattípusról akkor beszélünk, ha a reprezentáció nem látható.
−
Műveleteket is definiálnunk kell: a beépített műveletekre építjük fel a saját műveleteinket. Absztrakt adattípus esetén a műveletek (∼ függvények) implementációja nem látszik.
−
Viselkedés: Úgy kell viselkednie az absztrakt adatszerkezetnek ahogy elvárjuk tőle: ld. verem LIFO. Tükröznie kell az adott adattípus viselkedését.
Tehát az absztrakt adatszerkezetet csak a megadott műveletei segítségével használhatom, másként nem (bezárás). Kérdés, hogy vannak-e eszközök az adott programnyelvben, amivel az irányított hozzáférés megoldható. A mai programozásban alapvető szerepet játszik az absztrakt adattípus.
94
CSOMAG
CSOMAG A csomag az Ada környékén jelenik meg, és egyre több nyelv veszi át. Szokásos elnevezés még: unit, modul. Programegység fajta, amely olyan programelemeket, szolgáltatásokat
tartalmaz, melyeket a program más
pontjain felhasználhatunk. Programelem (programozási eszköz) gyűjtemény. Ezek a programelemek lehetnek: −
típus
−
változó
−
nevesített konstans
−
alprogram
−
újabb csomag.
Elsősorban az újrafelhasználhatóságot szolgálja, másrészt az adatabsztrakció eszköze. Segítségével absztrakt adattípus implementálható a nyelvben. Mellette ott van a procedurális absztrakció.
AZ ADA CSOMAGJA Az Ada csomag két részből áll: −
csomag specifikáció
−
csomag törzs
-- specifikációs rész: PACKAGE név IS deklarációs rész [PRIVATE
-- a csomag kívülről látható része -- a csomag kívülről nem látható része
deklarációs rész [reprezentáció_előírás]] END [név]; A specifikációs részben szereplő deklarációs rész a csomag látható része. Itt vannak deklarálva a felsorolt programozási eszközök. Tehát itt változó, nevesített konstans, típus, alprogram deklaráció vagy újabb csomag deklaráció állhat. Minden itt deklarált eszközre hivatkozhatunk. A hivatkozás minősítéssel történik. A private rész kívülről nem elérhető.
95
CSOMAG
Ezután lehet megadni a csomag törzsét. Ha szerepel a specifikációs rész deklarációs részében alprogram, akkor ott az alprogramnak csak a specifikációja szerepelhet. Ekkor kötelező a törzs, és a csomag törzsében kötelezően szerepelnie kell az adott alprogram implementációjának. Az implementáció el van rejtve.
-- törzs: [PACKAGE BODY név IS deklarációs rész
-- kívülről nem látszik
[BEGIN végrehajtható utasítások [kivételkezelő] ] END [név] ]; Kivételkezelő a törzs végén helyezhető el, ez a csomag kivételkezelője. A csomag kívülről nem látható privát deklarációs részében lehetővé teszi a nyelv, hogy reprezentációelőírás szerepeljen. (Értékek tárolásánál a bitkombináció megjelenése.) Az Adában csomag lehet önállóan fordítható programegység, vagy szerepelhet egy másik programegység deklarációs részében. Az utóbbi esetben statikus a hatáskör kezelés. E hatáskörön belül bárhonnan hivatkozhatok a látható részben deklarált objektumokra. A csomagok egymásba skatulyázása megengedett. Ha önállóan fordítom a csomagot, akkor a program más részei számára láthatóvá kell tenni explicit módon. A csomag más nyelvekben is megjelenik: −
Turbo Pascalban neve: UNIT . A UNIT szerepköre jóval szűkebb, mint az Ada csomagjának szerepköre. A Standard Pascalban nincs csomag.
Más nyelvek modulnak hívják.
PRIVATE TÍPUS Az Ada típusrendszerének felsorolásánál beszéltünk egy PRIVATE típusról, ugyanis a csomag specifikációs részének deklarációs részében, a látható részben szerepelhet a PRIVATE típusmegjelölés. Így az adott típusú objektum reprezentációját elrejtem, és a PRIVATE részben adom meg, hogy új reprezentációelőírást szerepeltetek. A PRIVATE típus szolgál az absztrakt adattípus megvalósítására az Adában, bizonyos objektumokhoz való hozzáférést korlátozza. A PRIVATE típusúnak deklarált objektumokra a nyelvbe beépített műveletek közül csak az egyenlőségvizsgálat (=) relációs művelet, és az értékadás (:=) alkalmazható. Tehát az így deklarált objektumok kezelő műveleteit a programozónak kell implementálnia. Ha van a látható részben privát típussal deklarált objektum, akkor kötelező a nem látható részben olyan eljárás
96
CSOMAG
deklaráció, amely kezeli a típust. Műveleteket elsősorban függvénnyel (esetleg eljárással) adunk meg. Így a csomagba be van építve a viselkedésmód. A műveletek implementációja nem látható. Úgy kell megírni a műveleteket, hogy a viselkedésmódot tükrözze. Ha azt akarjuk, hogy az egyenlőségvizsgálat és az értékadás se legyen megengedett, akkor alkalmazhatjuk a LIMITED
PRIVATE korlátozott privát típust. Ekkor csak a specifikált műveletek működnek, az
egyenlőségvizsgálat és az értékadás már nem. Információrejtés
(Information
Hiding):
a
specifikációt
megmutatom,
az
implementációt
nem.
Az
információrejtéshez hasonló a bezárás: bizonyos információkat bezárunk a külvilág elől, hogy mi lesz látható, azt a csomag írója dönti el. A csomag alapvető eszköze az Adának. Az Adában kilenc db. beépített csomag van. A csomag látható részében deklarált eszközeire, objektumaira hatáskörükön belül minősítéssel hivatkozunk. Alakja: csomagnév.objektum_név; A minősítés szükségessége feloldható a USE utasítással. Alakja: USE csomag_név; Ha egy csomagban a látható részben változókat deklaráltunk, akkor azok a változók (amennyiben felhasználjuk őket a csomagban deklarált alprogramban) OWN típusú változók lesznek, amelyeknek tulajdonságuk, hogy két alprogramhívás közt megtartják értéküket. (A változó értékét felhasználhatom a következő alprogramhíváskor.) Ezzel további kommunikációs lehetőséget biztosít az Ada az alprogramok között.
97
CSOMAG Példa: package RACIONALIS_SZAM is type RACIONALIS is
–- látható
record SZAMLALO : integer;
–- látható
NEVEZO : integer range 1..MAX_INTEGER;
–- látható
end record;
function ”=” ( x,y : RACIONALIS) return boolean;
-- látható
function ”+” ( x,y : RACIONALIS) return RACIONALIS; -- látható function ”*” ( x,y : RACIONALIS) return RACIONALIS; -- látható end; ---------------------------------------------------------------package body RACIONALIS_SZAM is
-- nem látható
procedure KOZOS_NEVEZO (x,y: in out RACIONALIS) is M
function ”=” M
function ”+” M
function ”*” M
end RACIONALIS_SZAM; A racionális aritmetikát valósítja meg ez a csomag. E csomag specifikációjának csak látható része van: −
Van benne típus definició: RACIONALIS. A racionális számok reprezentációja egy rekord segítségével történik, látszik kívülről, tehát nem absztrakt adattípus (bármikor büntetlenül hozzáférhetek a SZAMLALOhoz és a NEVEZO-hoz, nem zárja be).
−
Van három függvényspecifikáció.
−
Lennie kell törzsnek, ahol implementáljuk ezeket.
Az Ada lehetővé teszi, hogy a függvény neve ne csak azonosító legyen. A műveleti jelek túlterheltek (pl. Pascalban a + műveleti jellel jelöljük az összeadást, a konkatenációt, uniót), a fordítóprogram dolga, hogy a megfelelő gépi kódú utasítást generálja. Az Adában ez még bővíthető, ugyanis az ” ” idézőjelek közé tett speciális karaktereket is használhatunk függvénynév megadásához, így még túlterheltebbek lesznek a műveleti jelek.
98
CSOMAG
A fenti példa megmutatja a reprezentációt ezért nem absztrakt típus. A RACIONALIS reprezentációja látszik. A SZAMLALO-t össze tudom adni egy másik számmal (a sztenderd összeadási művelet segítségével). Mit kell tenni, hogy ebből absztrakt adatszerkezet legyen? Módosítani kell a specifikációt a következő módon:
package RACIONALIS_SZAM is type RACIONALIS private; function ”=” ( x,y:RACIONALIS) return boolean ; function ”+” ( x,y:RACIONALIS) return RACIONALIS ; function ”*” ( x,y:RACIONALIS) return RACIONALIS ;
private type RACIONALIS is record M
end record; M
end; A reprezentáció átkerül a nem látható részbe. Most már nem férek hozzá a SZAMLALO-hoz és a NEVEZO-höz bárhonnan szabadon. Privát típusnál az ”=” egyenlőség vizsgálatot felülírtuk ebben az esetben. Formálisan prefix alakú kifejezésre adunk lehetőséget. Az értékadást nem írtuk felül. Ha ezt is le akarjuk tiltani :
package RACIONALIS_SZAM is type RACIONALIS limited private; function ”:=” ...... function ”=” ...... M
end;
99
AZ ADA FORDÍTÁSI FILOZÓFIÁJA
AZ ADA FORDÍTÁSI FILOZÓFIÁJA, A FORDÍTÓPROGRAM MŰKÖDÉSE PRAGMÁK A program szövegében elhelyezhetők olyan utasítások, amelyek a fordító működését (üzemmódját) befolyásolják. A fordítóprogramnak szólnak, nem áll mögöttük közvetlen kód, de befolyásolhatják a kódot. Ezek a pragmák: −
Egy részük a program szövegének bármely pontján elhelyezhető.
−
Másik része csak kötött helyen használható.
A Turbo Pascalban is van pragma fogalom, ld.: fordítási direktívák.
.A C++ −ban és a Javaban van-e pragma?A pragma szerkezete a következő: PRAGMA név [(argumentum_lista)]; Az argumentum_lista szerkezete:
ìazonosító ü ý; kifejezés î þ
[név =>] í
A pragmák azon eszközrendszerek közé tartoznak, amelyeket az Ada hivatkozási nyelv szinten nem szabályoz. Az implementációk eltérnek egymástól. Az Ada rendszerekben általában mintegy 50 féle pragma van. Lássunk közülük néhányat: −
SUPPRESS : a kivétel figyelés letiltása. Bárhol elhelyezhető. Az adott programegységre vonatkozik. Lásd: kivételkezelés.
−
INTERFACE: az adott alprogram deklarációjánál kell megadni ezt pragmát a specifikáció után, és azt jelzi, hogy az adott alprogram az adott nyelven van megírva. Alakja: INTERFACE (programnyelv_neve, alprogram_név);
100
−
AZ ADA FORDÍTÁSI FILOZÓFIÁJA
LIST: fordítás közben a programszövegről forrásnyelvű lista készül. Előírja, hogy a program mely részéből készüljön lista. Alapértelmezés szerint listáz. Bárhol elhelyezhető. Alakja: æ
ö
ìON ü LIST çç í ý ÷÷ OFF èî
þø
FORDÍTÁSI EGYSÉGEK Az Ada program nem más, mint fordítási egységek együttese, így egy program fordítható egyben, vagy széttagolható önállóan fordítható egységekre. Fordítási egység az a szövegrész, amely a program többi részétől függetlenül fordítható. A Pascalban a program a fordítási egység. A fordítási egységek időben és térben egymástól függetlenül lefordíthatóak. A szerkesztő kapcsolja össze őket, és hozza létre ezekből a programot. A “hamis” hivatkozások sok nyelvben csak szerkesztésnél derülnek ki. Az Adában a kereszthivatkozások fordítási idejűek, fordítás közben végez az Ada konzisztenciaellenőrzést. A konzisztenciaellenőrzés tehát feljön fordítási szintre. Fordítási egység lehet: −
alprogram specifikáció
−
alprogram törzs
−
csomag specifikáció
−
csomag törzs
−
fordítási alegység
−
valamint ezek tetszőleges kombinációja
KÖNYVTÁRI EGYSÉGEK Azokat a fordítási egységeket, amelyek nem függnek más fordítási egységtől (nem alegységei más fordítási egységnek), könyvtári egységnek hívja az Ada. Ilyen könyvtári egységet a programozó tetszőleges számban hozhat létre. A könyvtári egységeknek külön egyedi nevük van. Egy könyvtári egységben lehetnek olyan alprogram hívások, amelyek nem az adott könyvtári egységben vannak megírva: kereszthivatkozás. Ha valamely fordítási egységben használni akarok egy olyan elemet, amely egy másik fordítási egységben van benne, akkor az adott elem specifikációjának lefordítva kell lennie, tehát előbb kell lefordítani, mint azt, amiben hivatkozunk rá. Itt a konzisztencia-ellenőrzés! Tehát a főprogramot kell utoljára megírni. Függvény esetén is elég, ha csak a specifikációja van lefordítva. Ez további lépés a procedurális absztrakció felé.
101
AZ ADA FORDÍTÁSI FILOZÓFIÁJA
Ha a specifikáció módosul (pl. mert plusz egy paramétert hozzáteszek), újra kell fordítani azt a fordítási egységet, amelyben a specifikáció van, és azokat, amelyek hivatkoznak erre a módosult specifikációra, és csak azokat. Ha csak az implementáció változik, csak azt kell újrafordítanunk. Ennek segítségével nagy programok fejlesztése mehet párhuzamosan. Minden fordítási egység kezdete előtt meg kell adni egy ún. környezetelőírást. Ennek megadása a WITH utasítással történik, és ezután azon könyvtári egységeket soroljuk fel vesszővel elválasztva, amelyekre az adott fordítási egységben hivatkozunk, amelynek eszközeit felhasználom a fordítási egységben. (Hasonlóan, mint a Pascalban a USES unitnév.) A saját könyvtári egységeket is fel kell sorolni. Alakja: WITH könyvtári_egység[,könyvtári_egység]...; Ezen könyvtári egységek alkotják az adott fordítási egység környezetét. Ez a fordítási egység ezen könyvtári egységek elemeit látja, ha nincsenek bezárva. Az Adában 9 db. szabvány könyvtári egység van. A könyvtári egységek jó része csomag. Az Adában is van olyan könyvtári egység, amelyet nem kell szerepeltetni a felsorolásban. Ez a STANDARD nevű könyvtári egység, amelyet a fordítóprogram minden fordítási egységhez automatikusan hozzáfordít, ezt nem kell külön megadni, alapértelmezés. Innen veszi a szemléletet a Turbo Pascal. Ez a sztenderd csomag tartalmazza a karakterkészletet, beépített típusokat, műveleteket és a beépített kivételeket.
FORDÍTÁSI ALEGYSÉGEK Azokat a fordítási egységeket hívjuk fordítási alegységnek, amelyek önállóan nem léteznek, hanem egy másik fordítási egységhez kapcsolódnak. A program fordítási egységekből áll. Egy fordítási egységnek tetszőlegesen mély struktúrája van. A fordítási egység akármelyik szintjén megtehetem, hogy az implementációt nem adom meg, hanem jelzem, hogy a törzs (implementáció) máshol, külön lesz megadva. Egy fordítási alegység környezetét a csonk adja meg. Egy másik fordítási egységben megadom a törzset, ami a csonkhoz tartozik. A csonkot tartalmazó fordítási egységet kell előbb lefordítani. Tetszőleges csonksorozatot lehet létrehozni.
102
AZ ADA FORDÍTÁSI FILOZÓFIÁJA
Példa: procedure FELDOLGOZO is //+++++++++++++++++++++++++++++++++++++ package D is HATAR : constant:=1000; TABLA : array (1..HATAR) of integer; procedure RESTART; end; //+++++++++++++++++++++++++++++++++++++ package body D is //--------------------------------procedure RESTART is begin for N in 1..HATAR loop TABLA(N):=N; end loop; end; //--------------------------------begin RESTART; procedure Q(x:integer) is begin M
D.TABLA(x):= D.TABLA(x)+1; M
end Q; end D; //+++++++++++++++++++++++++++++++++++++ begin
//feldolgozo
... D.RESTART; ... end FELDOLGOZO; Itt egy eljárást látunk, ezen belül van egy csomag és egy másik eljárás. Fordítás után egy Feldolgozo nevű könyvtári egységet képez. Ez a programszöveg önállóan alkot egy fordítási egységet, de feldarabolható három külön fordítható egységre a következőképpen: első rész a csomag-specifikáció, második rész a csomag törzse és
AZ ADA FORDÍTÁSI FILOZÓFIÁJA
103
a harmadik rész egy eljárás:
procedure FELDOLGOZO is package D is HATAR : constant:=1000; TABLA : array (1..HATAR) of integer; procedure RESTART; end; begin ... D.RESTART; ... end FELDOLGOZO; --------------------------------------------------------------
package body D is procedure RESTART is begin for N in 1..HATAR loop TABLA(N):=N; end loop; end; begin RESTART; End D; --------------------------------------------------------------
With D; procedure Q(x:integer) is begin M
D.TABLA(x):= D.TABLA(x)+1; M
end Q; Ha valamelyik fordítási egységben hivatkozunk a másikban lévő objektumra, annak a specifikációját mindenképp előbb kell lefordítani, tehát tudni kell mire hivatkozunk. Ezért először fordítandó az első rész, majd a második- és harmadik tetszőleges sorrendben. A lényeg, hogy az első rész fordítása megelőzze a másik kettőét. Hogyan néz ki egy fordítási alegység?
104
AZ ADA FORDÍTÁSI FILOZÓFIÁJA
Példa: procedure T is R, S : real:=1.0; package D is pi.constant:=3.186; function F(X : real) return real; procedure G(X, Z : real); end;
package body D is separate;
-- csonkképzés
procedure Q(u : in out real) is separate;
-- csonkképzés
begin M
Q(R); ... D.G(R,S); ... end T; -----------------------------------------------------separate(T)
--fordítási alegység
procedure Q (u : in out real) is begin M
end Q; separate(T) package body D is M
function F(x:real) return real is separate;
--csokképzés
procedure G(X, Z : real) is separate; end D; -----------------------------------------------------separate (T.D) function F(x:real) return real is M
end F; procedure G(X, Z : real) is M
end G;
--fordítási alegység
AZ ADA FORDÍTÁSI FILOZÓFIÁJA
105
Fordítási alegységeket mutat ez a kódrészlet. A programegységek egymásba skatulyázhatók. Csonkokat képezek azon a helyen, ahonnan a fordítási alegységet kiemeltem. Csonkképzés SEPARATE-val: jelezzük, hogy itt nem található meg a törzs, hanem valahol máshol. Fordítási alegység separate-val kezdődik, és ( )-ben megadjuk azt a programegységet, amelyen belül a csonk van, megadjuk, hogy hová kapcsolódik. Ha kell, minősítünk. Időben elválhat a csonk és az alegység fordítása, de fontos a sorrend: előbb a csonkot tertalmazó fordítási egységet kell lefordítani, utána az alegységet. Az implementáció változhat, és csak azt kell újrafordítani. Előnye: nagy rendszer fejlesztésénél a specifikációt elkészítjük, az implementáció később következik. A procedurális absztrakció útján továbblépve: −
Az alprogram attól procedurális eszköz, hogy paraméterezzük. A specifikáció és az implementáció elválik. De típus nem adható meg paraméterként.
−
Következő procedurális eszköz a csomag. Összegyűjtök programegységeket, szolgáltatásként használható.
−
Következő procedurális eszköz, a generikus továbblép az újrafelhasználhatóság szintjén. Az általános megnevezése: makró. Az Ada generikus forrásszövegmintát ad alprogramok és csomagok generálásához. A generikus egy forrásszövegrész, amely szöveg paraméterezhető. Ezt a szöveget a fordító kezeli. Fordítási időben a fordító a megadott aktuális paraméterek segítségével a forrásszöveg mitából előállít egy konkrét alprogram vagy csomag szöveget, amit a fordító lefordít. Ott a procedurális absztrakció, hogy tetszőleges számú alprogramszöveg generálható a szövegből. Egy szöveggel különböző forrásszöveget tudok generáltatni. Fordítási időben történik a generálás.
106
GENERIKUS
GENERIKUS “Amit egyszer megírtunk, azt ne kelljen mégegyszer megírni.” Ezen elv alapján eljárás- és csomag forrásszöveg generálható. Az újrafelhasználhatóság eszköze. A generikus egy minta forrásszöveg, amelyet paraméterezünk. A generikust meg lehet hívni. A hívást a fordítóprogram végzi el. A meghívás eredményeként a fordító előállít egy konkrét forrásszövegetaz aktuális paraméterek segítségével, amelyet azután lefordít. Az Ada generikus tehát egy olyan eszköz, amellyel eljárás, illetve csomag szövegmintát tudunk előállítani, fordítóval konkrét szöveget generáltatni, és majd azt fogja a fordító lefordítani. Csomag és alprogram generálható. Típus lehet paramétere a generikusnak! Alprogramnak viszont nem, egyetlen programnyelvben sem. Nézzük meg, hogyan néz ki az Ada generikus:
GENERIC [formális_paraméter_lista] és ez után egy teljes alprogram vagy csomag, ami a generikus törzsét jelenti.
A formális_paraméter_listán szerepelhet: −
változó deklaráció
−
típus deklaráció a következő módon: TYPE név IS (< >)
-- felsorolásos
RANGE < >
-- egész
DELTA < >
-- fixpontos
DIGITS < >
-- lebegőpontos
tömb mutató private
-- A csomag specifikációs részén kívül a generikus formális paramétereként szerepelhet a privát típus.
−
szerepelhet alprogram specifikáció a következő módon: WITH alprogram_specifikáció [IS név]; WITH alprogram_specifikáció IS < >;
107
GENERIKUS
Akárhány fajta paraméter és egy fajtából tetszőleges számú szerepelhet a formális paraméterlistán, a felsorolás sorrendje tetszőleges. Ez a minta. (Csomag vagy alprogram minta.) Konkrét alprogramot vagy csomagot ebből a mintából a következő utasítás segítségével lehet generáltatni attól függően, hogy mi volt a generikus:
ì PROCEDURE ü ï ï í FUNCTION ý név IS NEW generikus_név [(aktuális_paraméter_lista)]; ï PACKAGE ï î þ "A generikust így hívom meg." Ennek során lejátszódik egy paraméterkiértékelés és paraméterátadás. A generikus fix paraméterszámú. Paraméterkiértékelés: −
Sorrendi kötés az alapértelmezés.
−
De alkalmazható név szerinti kötés is.
−
Számbeli egyeztetésnél az alprogram formális paraméterekhez nem kötelező aktuális paramétert megadni akkor, ha az opcionális rész (az IS név vagy az IS < >) szerepel.
−
Típusegyeztetés helyett fajtaegyeztetés van: −
változóhoz
→ kifejezés: változó típusa a kifejezés típusával egyezik meg
−
típushoz
→ konkrét (már ismert) típusnév
−
privát esetén → teszőleges típusnevet megadhatunk
−
Ha van alprogram specifikáció, megfelelő eljárás- vagy függvénynevet adhatok meg aktuális paraméternek.
Az aktuális_paraméter_lista: −
Ha kifejezés az aktuális paraméter, a deklarált változó kezdőértéket kap. Ha a változó értékét nem módosítom a törzsben, akkor ez megfelel egy nevesített konstansnak.
−
A típusnév átadása névszerint történik (fölülírja).
−
Ha alprogramnév az aktuális paraméter, akkor nem kötelező megadni paramétert: − Ha megadunk alprogram nevet, a generált szövegben ez a név jelenik meg. − Ha nem adunk meg alprogram nevet, akkor a generált név: − − Vagy a WITH alprogram_specifikáció IS név -ben megadott név lesz. − − Vagy, ha WITH alprogram_specifikáció IS < > van, akkor nem történik helyettesítés, az eredeti név marad.
108
GENERIKUS
Példa:
generic type ELEM IS private;
-- generikus rész
with function F(U, V : ELEM) return ELEM is < >;
procedure MUVELET(U, V : in out ELEM); -- eljárás deklaráció M
begin M
end;
procedure SZOROZ is new MUVELET(integer); -- sorrendi kötés procedure OSSZEAD is new MUVELET(F=>″+″, ELEM=>integer); -- név szerinti kötés
109
GENERIKUS Példa:
Az Adára vonatkozó majdnem minden ismereteinket összegző példa: csomag, amely vermet (absztrakt adattípust) implementál, némi kivételkezeléssel szinesítve.
generic MERET : integer; type ELEM is private;
package VERMEK is type VEREM is limited private; procedure PUSH(S:in out VEREM; E:in ELEM); procedure POP(S:in out VEREM; E:out ELEM); OVERFLOW, UNDERFLOW:exception; private type VEREM is record HELY:array(1..MERET) of ELEM; INDEX:integer range 0..MERET:=0; end record; end;
package body VERMEK is procedure PUSH(S:in out VEREM; E:in ELEM) is begin if S.INDEX=MERET then raise OVERFLOW; endif; S.INDEX:= S.INDEX+1; S.HELY(S.INDEX):=E; end PUSH;
procedure POP(S:inout VEREM; E:out ELEM) is begin if S.INDEX=0 then raise UNDERFLOW; endif; E:=S.HELY(S.INDEX); S.INDEX:= S.INDEX-1; end POP; end VERMEK;
package EGESZ_VEREM is new VERMEK(ELEM=>integer;MERET=>1024); --n.sz.k. package LOGIKAI_VEREM is new VERMEK(100, boolean);
--sorrendi kötés
110
GENERIKUS
Itt egy generikus csomag látható, melynek két paramétere van: −
elemek típusa
−
kezelt elemek száma
Tetszőleges típusú elemeket tartalmazó, tetszőleges méretű verem kezelését megvalósító csomag. Absztrakt adattípust valósít meg (bezárja az implementációt) private típussal (csak a leírtak megengedettek). Kívülről nem látszik, hogy hogyan van reprezentálva. A vermet egydimenziós tömbbel ábrázoljuk a szokásos módon. Az absztrakt adattípus viselkedését tükröznie kell: LIFO veremként működik. Két alapművelet van implementálva: a PUSH és a POP. Kivételkezelés: a csomagban nem kezelem azokat a szituációkat, hogy: −
a verem üres Þ nem tudok olvasni
−
a verem tele van Þ nem tudok beleírni
hanem abban a programegységben, amely használni akarja a csomagot. Nincs kivételkezelő, így a kivétel átadódik a hívás helyére.
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE
111
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE, KONKURRENS PROGRAMOZÁS ILLETVE EZEK MAGASSZINTŰ PROGRAMOZÁSI ESZKÖZEI
A Neumann architektúrán felépülő gépek szekvenciálisak: a processzor a programnak megfelelő utasítási sorrendben hajtja végre az utasításokat elemi lépésenként. De itt is megjelenik a párhuzamosság.
FOLYAMAT (processz) ÉS SZÁL (thread) Megírok egy programot valamilyen programozási nyelven, ezt a fordító lefordítja gépi kódra. Ha a gépi kódú program működik, akkor folyamatról, vagy szálról beszélünk. Ha ezek a működő kódok az erőforrásokat kizárólagosan birtokolják, akkor folyamatról beszélünk, ha bizonyos erőforrásokat közösen birtokolhatnak, akkor szálakról beszélünk. Az 50-es évek végén 60-as évek elején megjelennek a multiprogramozott operációs rendszerek. Így a programok szintjére jön fel a párhuzamos programozás. Szűk keresztmetszet: a CPU ideje. Egy processzor segítségével több feladatot lát el a számítógép aszinkron módon, de mégiscsak párhuzamosan. A másik irány: nagyon hamar megjelenik a szinkron párhuzamos működés. Az I/O eszközöket nem közvetlenül a processzor vezérli, hanem segédprocesszorok vannak beépítve. Később megjelennek a többprocesszoros rendszerek. Egy időben annyi folyamat működhet, ahány processzor van. Kérdés, hogy hol vannak azok a magasszintű programozási eszközök, amelyek kihasználják a többprocesszoros rendszer párhuzamosságát? A párhuzamos programozási filozófián felépülő nyelvek: Occam, Paralel C, Concurrent Pascal, Ada. A folyamatok illetve a szálak maguk szekvenciálisan működnek, a folyamatok viszont párhuzamosak. A folyamat önmagában szekvenciális, de egymáshoz viszonyítva párhuzamosan működnek. KOMMUNIKÁCIÓ A folyamatok kommunikálnak egymással, adatcserét folytatnak. (Az Adában folyamatok vannak.) Van két folyamat, ezek egymástól függetlenül önmagukban szekvenciálisan futnak. (ld. Programozáselmélet) SZINKRONIZÁCIÓ A párhuzamosan futó folyamatoknak bizonyos időpillanatokban találkozniuk kell. Előfordul, hogy a szinkronizációs ponton keresztül történik adatcsere, a szinkronizációs ponton keresztül zajlik a kommunikáció. Például olyan információt vár az egyik a másiktól, ami nélkül nem tud továbbhaladni.
112
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE
KONKURENCIA A folyamatok vetélkednek az erőforrásokért. Kérdés, hogy hogyan oldható meg az erőforrások felosztása. KÖLCSÖNÖS KIZÁRÁS Mivel a folyamatok kizárólagosan birtokolják az erőforrásokat: például ha egy adatot több folyamat is használ, biztosítani kell, hogy amíg az egyik folyamat módosítja az adatot, addig a másik folyamat ne használhassa fel. HOLTPONT FOGALMA Két folyamat párhuzamos működése esetén ha a két folyamat úgy foglal le erőforrásolat, hogy mindkettőnek szüksége lenne a másik által lefoglalt erőforrásra, és egyik sem engedi el azt az erőforrást, amit ő lefoglalt, akkor nem tudnak továbbmenni, holtpont alakul ki. (Megoldása: ld. Programozáselmélet)
Párhuzamos programozási eszközrendszer először a PL/1-ben jelent meg. Létezik a Pascalnak és a C-nek is olyan változata, amely ebben az irányban bővíti tovább a nyelvet. Azok az algoritmusok, amelyeket ismerünk, mind szekvenciális algoritmusok, de léteznek párhuzamos algoritmusok a problémák megoldására. Az algoritmusokon belül az egyszerre elvégezhető műveleteket egyszerre végezzük el. Az ember kultúrája olyan, hogy szekvenciálisan látja a világot, holott az agy párhuzamosan dolgozik, tehát a hardverünk tökéletesen alkalmas lenne a párhuzamos látásmódra. A programozási nyelvekben szükséges olyan eszközrendszer, amelyekkel biztosítani tudjuk a kommunikációt, a szinkronoizációt, a kölcsönös kizárást, a konkurenciát, azaz kezelni tudja a folyamatokat, amelyekkel megvalósíthatjuk a párhuzamos programozást.
TASZK Az Adában a taszk, mint programozási eszköz szolgál a párhuzamos programozás megvalósítására. Általában a nyelvek taszkoknak hívják azokat az eszközöket, amelyekkel párhuzamos programozás valósítható meg. A taszk olyan nyelvi eszköz, amely mögött folyamat áll. A taszk olyan programegység, amely önállóan nem létezik, csak egy másik programegységbe beágyazva. SZÜLŐEGYSÉG A taszkot tartalmazó programegységet szülőegységnek hívjuk. TESTVÉRTASZK Egy szülőegységen belül akárhány testvértaszk elhelyezhető. Ezek azonos szinten deklarált taszkok.
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE
113
A taszkok tetszőleges mélységben egymásba ágyazhatók. A szülőegység és a testvértaszkok törzse mögötti folyamatok működnek egymással párhuzamosan.
Többprocesszoros rendszerek esetén egy processzornak egy taszk felel meg. Egyprocesszoros rendszerek is programozhatók párhuzamos módon, a rendszer szimulálja a processzoron a párhuzamos programozást. A PC-s Ada verziók általában nem tudják szimulálni a párhuzamos programozást. Egy taszk akkor kezdi el a működését, amikor elindul a szülőegysége. Nem lehet külön aktivizálni, ez egy szinkronizációs pont, minden benne lévő folyamat ekkor indul. Egy taszk befejezi a működését: −
ha elérte a végét (vagyis elfogytak az utasításai)
−
vagy ha a szülőegysége vagy a testvértaszk kivülről befejezteti a működését a következő utasítással:
abort név; −
de a saját működését is befejezheti
A szülőegység (akkor is, ha taszk) akkor fejeződik be, ha −
Ő, mint programegység befejeződött.
−
És ha az összes általa tartalmazott testvértaszkja befejeződött.
Ez megint egyfajta szinkronizációs pont a szülőegység számára. Képzeljük el, hogy a taszk egy blokk, amely a végére ért, de a testvértaszkjai még működnek. A blokk várakozik, elért egy szinkronizációs pontra, nem dolgozik, de még nem inaktív. A taszk két részből áll: −
specifikációs rész
−
törzs
114
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE
Alakja:
-- specifikáció: TASK [TYPE] név IS [entry_deklaráció(k) END [név]];
-- törzs: TASK BODY név IS [deklarációk] BEGIN végrehajtható_utasítások;
[kivételkezelő] END [név]; A specifikációs rész második sora olyan, mint egy eljárás specifikáció, csak az alapszó entry, nem
procedure. Ezek a szinkronizáció eszközei az Adában. Létrehozható taszk típus, ez egy korlátozott privát típusnak tekinthető. Deklarálható tehát taszk típusú (taszkokat tartalmazó) tömb. Ezzel nem foglalkozunk részletesen. AKTÍV- ÉS PASSZÍV TASZKOK Passzív taszkok azok a taszkok, amelyek valamilyen szolgáltatást nyújtanak. Formálisan a deklarációs részében az entry deklaráció, amely a szolgáltatás leírását szolgálja. Aktív taszkok azok a taszkok, amelyek igénybeveszik ezeket a szolgáltatásokat. A passzív taszk tálcán kínálja a szolgáltatásokat, nem tudja, ki fogja igénybevenni. (Az aktív taszk viszont tudja, mit akar.) Az entry a szolgáltatás. A passzív taszkon belül (amely tartalmazza az entry_deklarációt) szinkronizációs pontot a következőképpen tudunk létrehozni:
ACCEPT entry_név[(formális_paraméter_lista)] [DO utasítás(ok) END [név]];
-- ezek az utasítások a szolgáltatások
Ahány entry deklaráció van a passzív taszkban, a törzsben legalább annyi elfogadó utasítás szükséges. Egy entry deklarációhoz a törzsben akárhány ACCEPT utasítás tartozhat. Ez egy szinkronizációs pont. Az aktív taszk oldaláról az ennek megfelelő szinkronizációs pontot egy entry hívás jelenti. Az entry hívás egy szabályos eljáráshívás:
név (aktuális_paraméter_lista);
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE
115
SZINKRONIZÁCIÓ - RANDEVÚ A szinkronizációt az Ada úgy hívja, hogy randevú. Az aktív taszk egy entry hívással képez egy randevú pontot. Elindul a két taszk, szekvenciálisan hajtja végre az utasításokat, amíg egy randevú ponthoz nem ér az egyik taszk. Amelyik odaér a randevú ponthoz, megvárja a másikat. A randevú elsősorban a szinkronizáció eszköze az Adában, de van lehetőség a randevúban történő adatcserére is, erre szolgálnak az entry opcionális formális paraméterei. Lehetséges, hogy a kommunikáción túl csinálni is akarnak valamit a randevún: [DO utasítások
END név;] Sem a kommunikáció, sem a tevékenység nem szükséges. Ha van megadva paraméter, akkor a randevú kezdetén in és in out paraméterek esetén információ áramlik az aktív taszktól a passzív felé; a randevú végén pedig out és inoutnál a passzív taszk felől halad az információ az aktív taszk felé. Van egy másik kommunikációs lehetőség is: minden taszknak van szülőegysége. A taszkok kommunikálhatnak globális változókkal is. Az összes taszk láthatja ugyanazt a változót. Biztosítandó a kölcsönös kizárás! Külön nyelvi eszköz van arra, hogy a közösen használt változóknak a rendszer biztosítsa a kölcsönös kizárást. Ez pragma segítségével történik. Alakja: SHARED VÁLTOZÓ_NÉV; A szülőegység deklarációjában kell megadni (a testvértaszkok használják). Ha valamely passzív taszknak nagyon népszerű a szolgáltatása, sokan akarnak vele randevúzni. Az aktív taszkok sorban állnak (időbeli sorrend), hogy igénybevehessék a szolgáltatását. (Ciklus használata.) A sorbanállás befolyásolható, taszkhoz prioritás rendelhető egy pragma segítségével (ezt a taszk specifikációjánál kell elhelyezni):
PRIORITY(kifejezés); Vannak prioritással rendelkező és prioritás nélküli taszkok. Ezek a randevúra várakozás időbeliségét határozzák meg. Alacsonyabb, vagy prioritás nélküli taszk nem akadályozhatja egy magasabb prioritású taszk munkáját. Beszélünk relatív- és abszolút prioritásról: −
relatív prioritás: az éppen folyó randevú befejezéséig vár.
−
abszolút prioritás: az éppen folyó randevút félbeszakítja.
Van egy úgynevezett késleltető utasítás:
delay kifejezés; A kifejezés nemnegatív, egész értékű, decimális számrendszerben értendő szám, amely a késleltetést adja meg másodpercben.
116
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE
AZ AKTÍV TASZK RÉSZÉRŐL A RANDEVÚ BEFOLYÁSOLÁSA A KÖVETKEZŐ MÓDON TÖRTÉNIK FELTÉTELES RANDEVÚ Csak taszkban alkalmazható. A randevú végbemenetele szabályozható a select utasítással, ami csak taszkban fordulhat elő.
SELECT entry_hívás [utasítás(ok)] ELSE utasítás(ok); END SELECT; Az entry_hívás a randevúpont. Az aktív taszk megnézi, hogy létrejöhet-e azonnal a randevú. Ha igen, akkor létrejön a randevú, az esetleges utasítások végrehajtódnak. Ha nem tud randevúzni, akkor nem vár, hanem “póttevékenységet” végez: az ELSE ágban lévő utasításokat hajtja végre. IDŐZÍTETT RANDEVÚ
SELECT entry_hívás [utasítás(ok)] ELSE delay kifejezés; utasítás(ok); END SELECT; Ha azonnal megtörténhet a randevú, akkor randevúznak, majd annak befejezése után az entry_hívás utáni utasítás(ok) hajtódik, illetve hajtódnak végre. Ha nem (de nagyon fontos a randevú, képesek vagyunk várni is rá), akkor a késleltetési időn belül meghatározott ideig próbálkozik. Ha ezidő alatt végrehajtható a randevú, akkor végrehajtja, különben “pótcselekvés”.
A PASSZÍV TASZK OLDALÁRÓL BONYOLULTABB
SELECT [WHEN feltétel =>] alternatíva [OR [WHEN feltétel =>] alternatíva]... [ELSE utasítás(ok)] END SELECT;
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE
117
Egy alternatíva az alábbi három szerkezet valamelyike: −
accept_utasítás [utasítás(ok)]: elfogadó alternatíva
−
delay_utasítás [utasítás(ok)] : késleltető alternatíva
−
terminate_utasítás
Korlátozások: −
Legalább egy egy WHEN ág szükséges, és lehet egy ELSE ág.
−
Legalább egy elfogadó alternatíva szükséges, de akármennyi lehet.
−
A delay_utasításból bármennyi lehet.
−
A terminate_utasításból maximum egy.
−
A delay_utasítás és a terminate_utasítás kizárja egymást.
−
Ugyanarra az entry_utasításra akárhány elfogadó utasítás vonatkozhat.
NYÍLT ÉS ZÁRT ALTERNATÍVÁK Egy alternatívát nyíltnak nevzünk, ha: −
vagy nem szerepel előtte WHEN feltétel
−
vagy szerepel, de a feltétel igaz.
Egyébként az alternatíva zárt. Szemantikája a következő: amikor egy ilyen select utasításhoz ér a passzív taszk: −
Kiértékelődnek a feltételek.
−
Eldől, hogy mely alternatívák nyíltak, és mely alternatívák zártak.
−
Azon nyílt alternatívákban, melyekben delay utasítás van, kiértékelődik a delayben megadott kifejezés is.
−
Egy nyílt accept alternatíva kiválasztható, ha létezik olyan aktív taszk, amely ezzel a ponttal randevúzni akar (meghívta ezt az entryt). Ekkor a randevú végbemegy, és kilép a selectből.
−
Ha egyszerre több kiválasztható nyílt alternatíva van, bármelyik végrehajtódhat. Nem determinisztikus, hogy melyik randevú hajtódik végre, de mindig csak egyetlen randevú mehet végbe.
−
Egy nyílt késleltető alternatíva kiválasztható, ha nincs kiválasztható nyílt accept alternatíva. Ha több kiválasztható delay utasítás van, akkor a legkisebb várakozási idejűt választja ki. Ekkor a passzív taszk a megadott ideig várakozik, és nézi, hogy nem futott-e be valamely nyílt elfogadó alternatívához randevú kérés. Ha igen végrehajtja, ha nem akkor várakozott egy jót, és kilép a select-ből.
−
Nyílt terminate alternatíva akkor választható ki, ha a testvér taszkok és a szülő egység befejezte a működését, vagy a szülő egység legalábbis várakozó állapotban van. Ha a szülőegység taszk, akkor kükönösen érdekes ez a szituáció. (Ezért nincs PC-s környezetben Ada fordító. Ehhez operációsrendszer kell!) Ekkor a taszk befejezi a működését.
118
−
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE
Ha nincs kiválasztható nyílt alternatíva, és van ELSE rész, akkor végrehajtódnak az ELSE ágban lévő utasítások, és kilép a SELECT-ből. Ha nincsen ELSE rész, akkor a SELECT belefut egy végtelen várakozásbsa, amelyben nézi, hogy egy nyílt alternatíva kiválaszthatóvá, vagy egy zárt nyílttá és kiválaszthatóvá válik.
−
Ha minden alternatíva zárt, és van ELSE ág, akkor végrehajtódnak az ELSE utáni utasítás(ok), és kilépünk a SELECT-ből. Ha nincs ELSE ág, akkor bekövetkezik a SELECT_ERROR kivétel. Ez a kivétel csak taszkokban következhet be.
Aktív taszk csak olyan passzív taszkkal tud randevúzni, amelyik még működik. Ha egy olyan taszkra történik hivatkozás amely már inaktív, akkor kiváltódik a TASKING_ERROR kivétel. Ez a kivétel is csak taszkokban következik be.
KIVÉTELKEZELÉS A TASZKOK VONATKOZÁSÁBAN
-
Ha a kivétel egy randevúban következik be, akkor a randevúban résztvevő mindkét taszkban bekövetkezik a kivétel.
-
A kivételkezelés szabályai a szokásosak, azzal a specialitással, hogy vannak olyan kivételek amelyek csak taszkban következhetnek be.
-
A taszknak, mint programegységnek a végén helyezhatő el kivételkezelő, de ha a kivételt egy taszkban nem kezeljük, vagy explicit továbbadjuk, akkor az továbbadódik a hívó programegységnek, azaz a szülőegységben váltódik ki a kivétel, vagy ha ott nincs lekezelve, akkor továbbadódik, de legfeljebb addig adódik tovább, amíg a szülőegység taszk. Ha a legkülsőbb taszkban sincs kivételkezelő, akkor a kivétel elhal, nem megy tovább abba a programegységbe, ami a taszkot tartalmazza (hiszen egy blokk, ami nem taszk, nem tud mit kezdeni a csak taszkban bekövetkezhető kivételekkel). Ezért a taszkokban kezelni kell a kivételeket.
Ha az Ada programokat többprocesszoros rendszerekben futtatjuk, akkor valódi párhuzamos programozást valósítottunk meg. Egyprocesszoros rendszeren is működik olyan Ada program, amely taszkokat tartalmaz. Az operációs rendszer feladata, hogy a párhuzamosságot szimulálja. A párhuzamos programozás nem determinisztikus programozás, így van olyan szituáció, amiről a nyelv nem rendelkezik. Ada fordítók megítélése: −
determinisztikusan valósítja meg a taszkokat, vagy
−
nem determinisztikusan valósítja meg a taszkokat.
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE
119
Klasszikus példa: A következő szituációt valósítja meg: Van egy taszk, ami adatokat állít elő a saját ütemének megfelelően. Van egy másik taszk, ami felhasználja a termelt adatokat szintén a saját ütemének megfelelően. Nincsenek szinkronban, párhuzamosan dolgoznak. Produkáljunk egy passzív taszkot, amely fogadja illetve adja az adatokat, például egy sor segítségével: puffer. Kell egy szülőegység, bele kell foglalni a taszkokat egy szülőegységbe, mivel taszkok nem szerepelhetnek önállóan. A szülőegységen belül tehát van három taszk. A szülőegység törzse üres. Feladata: figyelni a testvértaszkokat. 1.
2.
P.
begin null; end; Az első taszk törzsében legyen egy végtelen ciklus:
loop M
BUFFER.WRITE(CHAR); exit when CHAR=END_OF_CHAR; end loop; M
A második taszk felépítése: M
loop BUFFER.READ(CHAR); M
feldolgozás;
exit when CHAR=END_OF_CHAR; end loop; M
120
A PÁRHUZAMOS PROGRAMOZÁS KÉRDÉSE
A PASSZÍV TASZK FELÉPTÉSE:
task BUFFER is entry READ (C : out CHARACTER); entry WRITE (C : in CHARACTER); end;
TASK BODY
BUFFER IS
POOL_SIZE : constant INTEGER:=100; POOL
: array (1..POOL_SIZE) of CHARACTER;
COUNT
: INTEGER RANGE 0..POOL_SIZE:=0;
IN_INDEX, OUT_INDEX : INTEGER RANGE 1..POOL_SIZE:=1;
begin loop select when COUNT < POOL_SIZE => accept -- kezdetben nyílt, van hely WRITE (C : in CHARACTER) do POOL(IN_INDEX:=C); END; IN_INDEX:=IN_INDEX mod POOL_SIZE+1; COUNT:=COUNT+1; or when COUNT > 0 => accept
--kezdetben zárt, nincs elem
READ(C : out CHARACTER) do C:=POOL(OUT_INDEX); end; OUT_INDEX:= OUT_INDEX mod POOL_SIZE-1; COUNT:=COUNT-1; or terminate;
--nyílt
end select; end loop; end BUFFER; A törzs egy végtelenciklus, amelyen belül egy SELECT utasítás van. A SELECT feladata a szinkronizáció. Három ág van. A terminate mindig nyílt. Elindul a programrész, a három taszk belefut a végtelenciklusba. Az első randevút az első aktív taszk hajtja végre. Majd kilép a SELECT-ből. Mivel végtelenciklusról van szó, újból a SELECT-re kerül a vezérlés. A két aktív taszk előbb-utóbb kilép a végtelenciklusból, és elér a törzsének a végére. A szülőegység várakozik. A passzív taszk a terminate-nál fejezi be a működését.
MÓDSZERTANOK ÉS TECHNOLÓGIÁK
121
MÓDSZERTANOK ÉS TECHNOLÓGIÁK A ’60-as években a számítástechnika kilépett a művészetek világából, és átment az iparba a szoftverek terén is (sorozatgyártás). Felvetődött a kérdés, hogy hogyan lehet sorozatban gyártani szoftvereket. Ez volt a szoftverkrízis időszaka. A következő kérdés vetődött fel: Hogyan lehet a probléma ismeretében bármilyen programnyelven jó programot írni? Mi az, hogy jó program? A jónak van egy helyes értelmezése. Ha van egy program, akkor tudjam, hogy mit csinál. Olyan programot írjak, ami azt csinálja, amit akarok: programhelyesség-bizonyítás. Mitől nem jó egy program? A szoftverkrízis környékén a programozás jellemzői: −
Monolitikus programozás: a programot egy ember készíti, fejleszti, tervezi, stb.
−
A programnak nincs belső struktúrája, így a program módosítása vagy nehéz, vagy lehetetlen.
−
A programok sosincsenek készen, csak 90%-ban.
−
A program megbízhatatlan: − − Az input nem minden elemére működik, így nem algoritmust realizál. A szélsőséges esteket általában nem kezeli. Pl. a rendezési algoritmusnak egy elemre is működnie kell. − − A program nem mindig áll le. − − A program lefagy, a rendszer is elszáll. − − A program működik, de nem lehet tudni, hogy milyen eredményt ad. Hibás adatokat produkál. − − A program nem védi a saját adatait.
−
Alkalmazkodóképesség hiánya az egyik legnagyobb probléma: − − Nem gépfüggetlen. − − Nem operációsrendszerfüggetlen. − − Növekvő terhelés mellett a teljesítmény, a hatékonyság rohamosan (exponenciálisan) csökken. (Programot írni sokan tudnak, jó programot is elegen, de jól működőt nagyon kevesen. És rossz programból jól működőt írni már művészet kategória. Hangolás: a szoftvert az adott környezethez igazítjuk.)
−
A program udvariatlan (nem felhasználóbarát.): − − A program csak egyféleképpen kezelhető, de azt nem tudja senki, hogy hogyan. − − A program hibaüzenetei semmitmondóak. − − Az outputja használhatatlan. − − A programok nincsenek dokumentálva.
−
Nem lehet beleavatkozni kívülről a program menetébe.
Kérdés, hogy hogyan lehet olyan elveket, gyakorlatokat találni, amelyek ezen hibákat kiküszöbölik? Hol vannak
122
MÓDSZERTANOK ÉS TECHNOLÓGIÁK
azok az eszközök (programozásmódszertan), amelyekkel jó programot lehet írni, abban az értelemben, hogy: −
Egy adott programról meg tudjam mondani, hogy mit csinál.
−
Úgy tudjak megírni egy programot, hogy az azt csinálja, amit kell. (Programhelyesség bizonyítás)
Válasz a hogyanra: 1.
Moduláris programozás módszertana (IBM).
2.
Megszületnek az objektumorientált nyelvek.
3.
Elkészítenek egy matematikai absztrakt elméletet. Megszületik a struktúrált programozási módszertan a 60as évek végén 70-es évek elején. Ebből az elméletből születnek meg a feltett kérdésekre a válaszok.
4.
A 70-es években megjelennek az újelvű programozási nyelvek és az újelvű programozás.
5.
Szintén a 70-es években születik meg a logikai programozás.
MÓDSZER (METHOD): Tevékenységek egy olyan szisztematikus együttese, amelyek megvalósítanak egy feladatot, megoldanak egy problémát (ld. algoritmus, pl. a gyorsrendezés egy módszer). TECHNIKA (TECHNIQUE): A módszerek által javasolt tevékenységek végrehajtása, kivitelezése. Például egy rendezési algoritmus végrehajtása manuálisan vagy számítógéppel. (pl. recept) MÓDSZERTAN (METHODOLOGY): Módszerek és technikák együttese, segítségével egy adott cél elérhető, egy adott probléma megoldható. ESZKÖZ (TOOL): Automatikus, vagy kézi erőforrás, módszer vagy módszertan alkalmazását segíti elő. Pl. egy C-ben megírt gyorsrendezést megvalósító függvény. KÖRNYEZET (ENVIRONMENT): Eszközök együttese. TECHNOLÓGIA (TECHNOLOGY): A módszertanok és környezetek együttese.
123
PROGRAMOZÁSI MÓDSZERTANOK
PROGRAMOZÁSI MÓDSZERTANOK MODULÁRIS PROGRAMOZÁS A moduláris programozás programozási módszertan. (Kialakulása: kitaláltak valamit, és később elnevezték moduláris programozásnak.) Elve: “Oszd meg, és uralkodj” elve átvitele a programfejlesztésre. Jellemzője: A moduláris programozást a top-down dekompozíció és a bottom up kompozíció jellemzi. TOP-DOWN DEKOMPOZÍCIÓ A megoldandófeladat komplexitását kell csökkenteni, osszuk fel a feladatot részfeladatokra. Ha a részfeladatok is túl bonyolultak, osszuk azokat is részfeladatokra, és tegyük mindaddig, amíg a részek átláthatóak nem lesznek. Ha ezeket leprogramozom, ezek lesznek a modulok. Ettől moduláris a programozás. A részfeladatokra bontásra vannak eszközök és technikák. Fel kell deríteni, hogy mit kell csinálni. Ezeket a “mit”-eket darabolom tovább. Funkciók mentén bontunk fel. A részfeladatok közötti kapcsolatokat jól kell specifikálni. Erre is van recept, de nem automatizálható. Ez a top-down dekompozíció, amikor felülről lefelé darabolok. A modul egyfajta logikai egység, amely: −
Önálló névvel rendelkezik.
−
Önállóan kódolható.
−
Önállóan tesztelhető.
−
A modulok más modulok környezetében vannak, és paramétereken vagy globális változókon keresztül kommunikálnak egymással.
A funkció menti szétbontásnál a modulok fajtái a következők: −
Adatmodulok: megadom, hogy mikkel akarok dolgozni.
−
Funkcionális modulok: leírom, hogy hogyan.
−
I/O modulok: kommunikáció.
−
Vezérlő modulok: öszefűzés.
Egymástól teljesen függetlenek a fejlesztés során az adatmodulok és az funkcionális modulok.
124
PROGRAMOZÁSI MÓDSZERTANOK
A moduláris programtervezés lépései, módszertana: −
A feladat specifikációjától indulunk, és a top-down segítségével megadjuk a feladatmodul szerkezetét úgy, hogy a funkciók (tevékenység) mentén darabolunk.
−
Modulok specifikációja, kapcsolata más modulokkal, kommunikációjuk leírása.
−
Megtervezzük azokat az algoritmusokat, amelyek ezeket a részproblémákat megoldják.
−
Lekódoljuk.
−
Teszteljük.
Az utóbbi négy lépés megvalósításához vannak módszerek, technológiák.
BOTTOM UP KOMPOZÍCIÓ Lentről fölfelé felépítjük a programot. Nincs módszer és nincs technika arra, hogy az önmagában tökéletesen jól működő modulokból hogyan lehet jó programot összeállítani. Kézi munka. (A válasz általában: ha van szerencsém.) A programhelyességre nem tud pozitív választ adni. A moduláris programozás előnyei: −
A programnak van egy áttekinthető szerkezete, tiszták a kapcsolatok a modulok között.
−
Ha módosítanom kell a programomat, ez általában egy vagy csak néhány modul módosítását jelenti. A javítás is csak részfeldatokra korlátozódik.
−
Párhuzamos fejlesztést, tesztelést tesz lehetővé. Felgyorsul a programozás. Egyszerre több ember tud dolgozni 1 adott feladattal.
−
Újrafelhasználhatóság: bizonyos modulok szabványosíthatók. Pl. I/O. A szabványmodulok egy könyvtárban tárolhatók, és újrafelhasználhatók.
A moduláris programozás hátránya: −
Nincs módszer arra, hogy tökéletesen jól működő modulokból hogyan lehet jó programot összeállítani.
125
PROGRAMOZÁSI MÓDSZERTANOK
STRUKTURÁLT PROGRAMOZÁS A ’60-as évek végén ’70-es évek elején alakul ki részben a moduláris programozás hatására a strukturált programozási módszertan. Érdekes, hogy három különböző helyen jelenik meg egyszerre valamilyen módszertan, amelyekről kiderül, hogy ugyanarról szólnak. A harmadik egy absztrakt matematikai módszertan, amely alapján helyes programokat lehet írni.
1. DIJKSTRA: HIERARCHIKUS PROGRAMOZÁS A STRUKTURÁLT MÓDSZERTAN LEGABSZTRAKTABB VÁLTOZATÁT DIJKSTRÁÉK (HOLLAND) DOLGOZTÁK KI. HIERARCHIKUS PROGRAMOZÁSNAK HÍVTÁK EZT A MÓDSZERT. A struktúrált programozás átveszi a moduláris programozás top-down módszerét. A megoldandó feladathoz a program egy absztrakt programsorozat határértékeként alakul ki. Ebben a sorozatban egy későbbi absztrakt program egy őt megelőző program egy változtatásával áll elő úgy, hogy tekintjük valamely tevékenységét, és fokozatosan finomítjuk. A megelőző program valamely tevékenységét kifejtjük (részfeladat). A sorozat minden absztrakt programja mellett ott van egy absztrakt számítógép, amelynek utasításkészlete megegyezik a programban használt utasításokkal. Végül egy konkrét gép konkrét utasításkészletére készül el a program. Pl. egy tanulmányi nyilvántartási rendszer. A sorozat minden programja bizonyítottan helyes (azt a feladatot oldja meg, amit kell). Nincsen bottom-up. Léteznek konkrét matematikai módszerek, vannak automatizálható eszközök, de bizonyos feltételek, leképezések nem automatizálhatóak, ilyenkor a programhelyesség bizonyításához kézi vezérlés szükséges. (ld. még Programozáselmélet)
2. MILLS: FUNKCIONÁLIS PROGRAMOZÁS Ugyanaz az elve, mint a Dijkstra féle programozásnak, csak itt az eljárásmodell az elsődleges, az adatmodell másodlagos. Tevékenység mellett osztja szét a problémát részproblémákra. A feladat határozza meg a program szerkezetét.
3. JACKSON ÉS WARNIER: ADATORIENTÁLT PROGRAMOZÁSI MÓDSZERTAN
A harmadik irányzat, amely jóval kevésbé matematizál. A top-down módszert vallja, de az adatmodell az elsődleges. Ha van egy probléma, fel kell derítenem az általa érintett adatok szerkezetét. A program szerkezetét az adatok szerkezete határozza meg. Hátránya: csak adatfeldolgozási területen alkalmazható.
126
PROGRAMOZÁSI MÓDSZERTANOK
BOEHM ÉS JACOPINI
’66-ban publikálnak egy cikket, amelyben leírják, hogy minden algoritmus felépíthető a következő három vezérlési szerkezet segítségével: Szekvencia:
Szelekció: i
Iteráció:
h h i
Ezt még nem tudták bizonyítani, de Mills ’68-ban bebebizonyítja, és a maga filozófiájának megfelelően a következőket mondja: −
Egy program akkor jó, ha a szerkezete leírható egy szekvenciaként, amely szekvencián belül a fenti három vezérlési szerkezet megengedett.
−
Egy szekvenciaelembe a külvilágból egy ponton lehet belépni és egy ponton kilépni.
−
Ha egy program ilyen, akkor az struktúrált. Kellemesen dokumentálható, módosítható.
A Jackson féle elv azon alapul, hogy az adatszerkezetek szintén leírhatók e három vezérlési szerkezet segítségével, ugyanis egy állomány nem más, mint rekordok iterációja, egy fix rekord pedig mezők szekvenciája. Az adatszerkezetek meghatározzák a program szerkezetét. Kérdés, hogy ebbe a szemléletbe belefér-e a GOTO utasítás. A GOTO általában arra csábít, hogy felborítsuk ezt a szerkezetet. A Fortranban például nem lehet GOTO nélkül programozni. De a normális használata belefér. A visszaugrás (hurok) nem engedélyezett. Tehát a GOTO nem űzetik ki a struktúrált programozás világából, legfeljebb nagymértékben háttérbeszorul, és használata korlátozódik. Hagyománytiszteltből a legtöbb nyelvben meghagyják a GOTO-t. A struktúrált programozás módszertana a ’70-es évek elején analízis módszertanná, rendszerfejlesztési, tervezési technológiává válik. A 1970 - 1980-as évek uralkodó módszertana.
127
OBJEKTUM - ORIENTÁLTSÁG
OBJEKTUM-ORIENTÁLTSÁG Meg fogunk állni a programozási nyelvek szintjén. Az objektum-orientált programozási módszertan filozófiáját – amelynek alapgondolata, hogy az adat és a funkcionális modell egymástól elválaszthatatlan, lényegében egyetlen modell − követik az objektum-orientált programozási nyelvek. Az OO programozási nyelvek imperatív jellegűek: algoritmikus szemléletet tükröznek. Az OO paradigma bevonul minden más nyelvi osztályba is. −
A ’60-as évek második felében jelenik meg a SIMULA67-ben az OO programozási eszközrendszer. A SIMULA67: Algol verzió, szimulációs, absztrakciós nyelv, minden objektum-orientált eszköz megvan benne. Észak-Európában születik meg.
−
Alan Kay amerikai egyetemista 1969-ben szakdolgozatában egy új világot vázol föl: az OO világot. A Xerox-nál próbálja megvalósítani. Egy projektet szervez, melynek célkitűzése egy személyi számítógép megtervezése (hardver, architektúra, szoftver). Ő használja az objektum-orientált elnevezést, fogalmakat. (A grafikus interfész, ablak és egér fogalma is ekkor jelent meg először.) Felvázol egy projektet: −
csinálni kell személyi számítógépet
−
Windows típusú operációs rendszert, grafikus felhasználói felületet (ekkor még csak az elektromos írógép az input periféria)
− −
mindezt objektum-orientált programozási környezetben
1970-es évek elején megszületik a Xerox-nál a Smalltalk programozási nyelv, melyet objektum-orientáltnak terveznek, tiszta objektum-orientált nyelv, (SIMULA elemekkel). A világot csak objektum-orientáltnak hagyja láttatni, másnak nem. Ekkor még a strukturált elv a döntő.A Smalltatkkal együtt megjelenik egy olyan paradigma, amelyet a szakma még nem tud befogadni, másrészt olyan hardver kell alá, ami még nem létezik. A ‘80-as évek második felétől jelenik meg ilyen hardver. Ezután őrültmód elkezd terjedni az OO. Divattá válik. A Smalltalk napjainkban is él.
−
Megszületik a ‘80-as években a C++, jelenleg divatnyelv.
−
1985-ben megjelenik a Meyer által kifejlesztett Eiffel nyelv, ami az OO területén azt a szerepet játsza, amit az eljárásorientált nyelvek területén a Algol. Nincs gyakorlati jelentősége.
−
1989: Turbo Pascal 5.5
−
1990: Turbo Pascal 6.0: OO eszközrendszerrel rendelkezik. A ‘80-as évek második felében ‘90-es évek első felében minden magára valamit is adó programozási nyelvnek van olyan változata, amely már OO eszközrendszerrel rendelkezik valamilyen szinten.
−
Java: az OO vallásának istene. A C++ óta egyetlen, aminek gyakorlati jelentősége is van.
128
OBJEKTUM - ORIENTÁLTSÁG
Objektum-orientáltság jellemzői: −
az adatmodell és az eljárásmodell elválaszthatatlan (így szemléli a világot)
−
absztrakt eszköz és fogalomrendszer: Az újrafelhasználhatóságot olyan magas szintre elviszi, ameddig lehetséges, a valós világot nagyon megközelíti.
−
szemlélete: imperatív (algoritmus – kódolni kell) eszközrendszer
Jelen pillanatban az OO területén többféle iskola létezik, amelyek bizonyos pontokon élesen vitatkoznak egymással, nem csak nüansznyi különbségek vannak köztük. Jelen pillanatban folyik az OO matematikai hátterének elkészítése, kifejlesztése.
AZ OBJEKTUM-ORIENTÁLT PROGRAMNYELVEK FOGALOMRENDSZERE
OBJEKTUM (OBJECT): Az eljárásorientált nyelvek változó fogalmának kiterjesztése (általánosítása), olyan konkrét programozási eszköz melynek vannak: −
Attribútumai (attribute): ez az adatrész, a struktúra, tetszőleges bonyolultságú adatszerkezet. Szokás ezt az objektum statikus részének is nevezni. Minden objektum mögött van egy jól definiált tárterület, ezen vannak az attribútumok értékeit reprezentáló bitsorozatok. Terminológia: az obektumok állapotairól (state) beszélünk, ahol minden egyes állapotot egy-egy bitkombináció ír le, ami egy jóldefiniált címen van.
−
Módszerei (method): a viselkedés leírására szolgál (eljárásmodell leírására) az eljárásorientált nyelvek eljárásai és függvényei. A módszerek adják meg nyelvi szinten az objektum viselkedésmódját (behavior).
−
Azonossággal rendelkezik (van azonosság tudata): bármely objektum csak és kizárólag önmagával azonos, minden mástól megkülönböztetett. Minden objektumnak van azonosítója (OID: object identifier). Nyelvi szinten ezzel nem foglakozunk. Analógia: változó – név objektum – OID (nem egy név!) A változó neve igazából soha nem azonosító csak hatáskörön belül egyértelmű a névhivatkozás. Az OID viszont tényleg egyedi, még programok között is!
OBJEKTUM - ORIENTÁLTSÁG
129
OBJEKTUM VISELKEDÉSE: Az objektum állapota időben módosul(hat). Módszerek csoportjai: −
le tudja kérdezni az objektum állapotát
−
meg tudja változtatni az objektum állapotát
OBJEKTUMOK ÉLETTARTAMA: Az objektumot létre kell hozni, és addig él, amíg meg nem szűnik. A megszüntetés lehet a nyelvi rendszer feladata, vagy a programozóé. Az objektumazonosító minden szinten él, mindig léteznie kell.
OSZTÁLY (CLASS): Absztrakt eszköz, az eljárásorientált nyelvek típusfogalmának általánosítása (gyakran itt is típusként említjük szinonimák). Az osztály absztrakt adattípus abban az értelemben, aholy az Adában a korlátozott privát típust használjuk. Az osztály azonos attribútumú és módszerű objektumok együttese. Az osztályhoz köthetőek az objektumok; az osztályból származtathatóak az objektumok.
PÉLDÁNY (INSTANCE): Az osztályon belül létrehozok egy objektumot: példányosítás (instantiation). −
Az adott objektum adott osztály példánya. Minden objektum tudja, hogy melyik osztálynak példánya.
−
Adott osztályhoz tartozó minden példány ugyanolyan attribútumokkal és módszerekkel rendelkezik. Minden példány tudja, hogy milyen módszerekkel rendelkezik.
−
A módszereket mindig konkrét példányon futtathatom le, ezen értelmezhetők: az aktuális példányon.
−
Példány létrehozása: ugyanaz az adatszerkezet újra és újra megjelenik a tárban. A módszereket nem többszörözi !
−
Létezhetnek olyan attribútumok és olyan módszerek, amelyek nem arra szolgálnak, hogy az egyes példányok állapotait és viselkedését vizsgáljuk velük, hanem magához az osztályhoz tartoznak. (Példányattribútum, példánymódszer; osztályattribútum, osztálymódszer) −
Osztályattribútum: hány darab példánya van (az osztály kiterjedése).
−
Az osztályattribútumok nem többszöröződnek.
Az OO szemlélet szerint először létre kell hozni egy osztályt, leírni, hogy a hozzá tartozó objektumoknak milyen attribútumai és módszerei legyenek. És ezek után az osztályhoz kapcsolódóan és osztályon belül létre lehet hozni objektumokat. Példányosítás után az osztály példányairól beszélünk.
130
OBJEKTUM - ORIENTÁLTSÁG
ÖRÖKLŐDÉS (INHERITANCE): Az újrafelhasználhatóság eddig legteljesebb válasza: objektum-orientált programozási elv: az osztályok nem függetlenek egymástól, speciális viszony értelmezhető közöttük, ez az öröklődés. Ez a viszony aszimmetrikus. (Az absztrakciót a lehető legmesszebb elviszi, viszont a párhuzamosságra nem ad választ, bár az objektumok párhuzamosan léteznek. Nyelvi szinten nem mindenhol jelenik meg ez explicit módon. Az adatfolyamnyelvek adják a párhuzamosságra a legpozitívabb választ.) Az öröklődés osztályokhoz kötött fogalom: két vagy több osztály között értelmezhető.A szuperosztályhoz kapcsolódóan tudunk létrehozni alosztályokat. szuperosztály (superclass) / szülőosztály / ősosztály / alaposztály
alosztály (subclass) / gyerekosztály / származtatott osztály Az alosztály átveszi, örökli a szuperosztály attribútumait és módszereit (azokat, amelyeket a láthatóság módszerével nem tiltottunk le). Öröklésnél azonnal megvan az újrafelhasználhatóság, rendelkezésre áll az összes eszköz. Az alosztály ezen túlmenően: −
új attribútumokat vezethet be
−
új módszereket vezethet be
−
újraimplementálhatja a módszereket
−
törölhet attribútumokat
−
törölhet módszereket
−
a láthatósági szabályokat újraértelmezheti, hatásukat felfüggesztheti
−
átnevezhet attribútumokat
−
duplikálhat attribútumokat
−
duplikálhat módszereket
Öröklés: valamit egy az egyben átvehetek, ha akarom, módosíthatom. Aszimmetria: a szuperosztály nem látja, nem manipulálhatja alosztályait, de fordítva igen. A szuperosztályt teljes mértékben látja az alosztály. Az alosztály minden objektuma objektuma a szuperosztálynak is! Viszont fordítva ez nem áll fenn. Így minden rendszerben: mindenütt, ahol egy szuperosztály egy példánya szerepel, szerepelhet az alosztály egy példánya is és fordítva nem igaz. Egy osztályból tetszőleges számú alosztály származtatható minden nyelvben. Az egyes rendszerekben kérdés, hogy: az alosztálynak hány szuperosztálya lehet? −
egy: egyszeres öröklődés (single)
−
akárhány: többszörös öröklődés (multiple). Problémák: azonos nevű attribútumok, módszerek esetén: névütközés; ezt a rendszernek kezelnie kell. Rendszerfüggő, hogy hogyan teszi.
Alosztályból másik alosztály származtatható: öröklési hierarchia. Ez egyszeres öröklődés esetén fa., többszörös öröklődés esetén aciklikus gráf.
131
OBJEKTUM - ORIENTÁLTSÁG Öröklési fa:
Alakzat
Nyílt alakzat
Zárt alakzat
Poligon
Háromszög
Négyszög
Ellipszis
Ötszög
Kör
Például az Alakzat osztály −
attributumai lehetnek: vonalvastagság, nagyság, szín, háttér, kitöltöttség ...
−
módszerei lehetnek: kirajzol( ), elforgat( ) …
Az Alakzatot elkezdem specializálni, ekkor a Zárt alakzatnál jöhetnek mégújabb −
jellemzők: terület, kerület …
−
módszerek: területszámítás( ), kerületszámítás( ) …
A Zárt alakzat Alakzat is egyben, így a Zárt alakzat minden példánya az Alakzatnak is példánya (is_a). A Háromszögnek is lehet egy terület( ) módszere: átveszem a Zárt alakzattól, de ezt újraimplementálom, hiszen a Zárt alakzat területét csupán közelítőleg tudom megadni, míg a Háromszögét pontosan. Terminológia: −
Fa gyökéreleme: ősosztály, amiből az összes többi származik.
−
Előd:
pl. a Kör elődjei: Ellipszis, Zárt alakzat, Alakzat.
−
Leszármazott:
pl. a Zárt alakzat leszármazottjai: Ellipszis, Kör.
−
Kliens osztályok: azok az osztályok, amelyek között nincsen öröklődési kapcsolat. Pl. Kör − Ötszög.
132
OBJEKTUM - ORIENTÁLTSÁG
BEZÁRÁS (ENCAPSULATION): Az OO nyelvek legkényesebb fogalma: általában e fogalom mentén válnak el az iskolák, attól függően, hogy melyik mit vall róla. Az eljárásorientált nyelvek hatáskör fogalmának, a láthatóságnak a kiterjesztése. A legtöbbet félreértelmezett fogalom. Nem objektumhoz kapcsolódik. −
Bezárás_1: Nem objektumhoz kötődik. Az osztály egy absztrakt adattípus. Az osztály rendelkezik egy interfész és implementációs résszel. Az osztály objektumaihoz csak az interfész részen keresztül férhetünk hozzá, az implementációhoz egyáltalán nem, korlátozott hozáférést jelent. Ez az információrejtés elve (Information hiding). Egy osztály objektumai egy az osztály által definiált interfészen keresztül érhetők el, és csak így! A nyelv a benne definiált attributumokat és metódusokat két részre osztja: − Nyilvános rész: amelybe tartozó eszközöket minden kliens osztály lát. − Privát rész: kívülről nem látható.
−
Bezárás_2: A bezárás eljárásorientált nyelvek hatáskör fogalmának általánosítása OO körökben, ahol garantáltan létezik egy olyan eszközrendszer, mellyel a programozó tudja szabályozni, hogy az osztályból mi látható és ki számára.
POLIMORFIZMUS (POLIMORPHISM): vagyis többalakúság. Kétfajta polimorfizmus van: −
Objektum polimorfizmus: Minden objektum tudja saját magáról, hogy melyik osztály példányaként jött létre. Egy objektum objektuma saját osztályának, de az öröklődési hierarchiában objektuma valamennyi elődosztálynak is egyben. Így minden egyes objektum szerepelhet minden olyan szituációban, ahol az ősosztály objektuma szerepelhet, nem csak a saját osztálya példányaként használható.
−
Módszerpolimorfizmus (overriding): Egy leszármazott osztály egy örökölt módszert újraimplementálhat: a módszer specifikációja változatlan marad, de az implementáció más lehet az öröklődéi vonalon. Ld.: −
Zárt alakzat: terület( ), kerület( ) módszer
−
Háromszög: terület( ), kerület( ) módszer más! (új implementáció)
KÖTÉS (BINDING): A módszerpolimorfizmushoz kapcsolódik. Ha van egy függvény és több implementáció hozzá, kérdés, hogy mikor melyik kód kapcsolódik a specifikációhoz. Eszerint beszélünk: −
Statikus (static) más néven korai (early) kötésről: a névhez a kód hozzárendelődik fordítási időben. Az OO rendszerek többsége fordítóprogram orientált.
−
Dinamikus (dynamic) vagy késői (late) kötésről: kötés futási időben történik, így ugyanahhoz a névhez más-más kód tartozhat, attól függően, hogy melyik osztálykörnyezetben dolgozunk: az aktuális példány osztályában definiált kód, vagy (ha nincs) a hierarchián felfele a legközelebbi kód kötődik.
A nyelvek többsége mindkét kötést ismeri, kérdés, hogy melyik az alapértelmezett.
133
OBJEKTUM - ORIENTÁLTSÁG ÜZENET (MESSAGE):
Tipikusan Smalltalk fogalom. A Smalltalk filozófia szerint az objektum üzenetek segítségével kezelhető. Ha az objektumtól kérni akarok valamit, akkor üzenetet küldök. Az objektum veszi az üzenetet, én nem tudom, mi történik közben, nem tudom, hogy az objektum hogyan találja ki a választ, és az objektum válaszol.
ABSZTRAKT OSZTÁLYOK Absztrakt osztályoknak hívjuk azokat az osztályokat, amelyeknek nincsenek példányaik, amelyek nem példányosíthatók. Csak örököltetésre való. Beszélnek nyelvek absztrakt módszerekről. Ezek azok a módszerek, amelyeknek csak a specifikációjuk van megadva implementáció nélkül. Az abszrakt osztályokból konkrét, példányosítható
osztályok származtathatók.
Az
egész
eszközrendszer az absztrakciót szolgálja. A
rendszerfejlesztési ciklusban és a programfejlesztésnél lesz érdekes.
KONTÉNER OSZTÁLYOK (CONTAINER) Olyan adatszerkezet, amely objektumokat tartalmaz. Alapvető a tömb, láncolt lista, verem, sor, stb. Nem minden nyelvben vannak realizálva a konténer osztályok, a programozónak kell megvalósítania. Alapvető szerepük az adatbáziskezelőknél van.
KOLLEKCIÓK (KOLLECTION) Objektum-orientált adatbázisok esetén a konténerosztályok helyett a terminológia: kollekció (Collection). Ezen kollekcióval kapcsolatos az iterátor fogalma.
ITERÁTOR Általában ez is egy osztály, típus, ennek példányaihoz tartozó objektumokat be tudjuk járni. A bejárás az adatszerkezeteknek megfelelően történik.
PARAMÉTEREZETT OSZTÁLYOK: Egyes objektum-orientált nyelvekben vannak ún. paraméterezett osztályok, a C++ terminológia szerint templatek. Lényegében megfelelnek osztályszinten az Ada generikusnak.
134
OBJEKTUM - ORIENTÁLTSÁG
OBJEKTUMOK ÉLETTARTAMA: A példányosítás mindig egy explicit tevékenység eredménye, minden objektumot minden nyelvben a programozó hoz létre. Meddig él? -
A nyelvek egy részénél az objektumot megszüntetni is explicit módon kell, az objektumok törlése is a programozó feladata. A nem tisztán objektum-orientált nyelvek egy része vallja ezt az elvet. Ld. C++.
-
A nyelvek másik része (nagyobb része) alkalmaz egy automatikus objektum törlési mechanizmust (garbage collection), amelynek a feladata az objektumok megszüntetése aszinkron módon úgy, hogy azzal a programozónak ne kelljen foglalkoznia, és úgy, hogy a törölt objektumok tárhelye ismét felhasználható legyen. Ez az automatikus tárfelszabadítás nem csak az objektum-orientált nyelvek sajátja, hanem egy tárkezelési technika. Többféle algoritmus van arra, hogy a rendszer hogyan dönti el, hogy mely objektum törölhető. Nyilvánvaló, hogy garbage collection algoritmus sokkal kényelmesebbé teszi a programozást.
EGYSÉGESSÉG: A nyelvben létezik-e más eszközrendszer, mint az objektum? Minden objektum, vagy van olyan eszköz, ami nem az? Ezek alapján az OO nyelveknek két nagy csoportja van: −
A tisztán OO nyelvek azt vallják, hogy minden objektum (osztály, attribútum, módszer, objektum). Csak olyan eszközöket tartalmaznak, amelyek obektumorientáltak, és nincs más eszköz. Pl.: Smalltalk, Eiffel csak OO elvek alapján működik. A tisztán OO nyelvek esetén e nyelvi rendszer egyetlen osztályhierarchiából áll. Például a Smalltalk egy osztályhierarchia. A programozás pedig nem más, mint definiáljuk a saját osztályainkat, és azokat elhelyezem az osztályhierarchiában: az adott osztályhierarchiát bővítük, és ezekből származtatunk objektumokat.
−
A hibrid nyelvek alapvetően eljárásorientált, logikai, funkcionális, stb. nyelvi eszközöket tartalmaznak, és ez a nyelvi szközrendszer bővül OO eszközrendszerrel.Van tehát objektum is, és van nem objektum is. Lehetnek bennük eljárásorientált, deklaratív, funkcionális, objektum-orientált eszközök. Programozhatunk benne objektumorientáltan is. A nem tisztán OO nyelvek nem tartalmaznak osztályokat, incs osztályhierarchia. Definiálhatunk önálló osztályokat, és egymástól fügetlen osztályhierarchiákat. Itt is vannak szabvány osztálykönyvtárak, csak ezek nem a nyelv részei, és ezektől fuggetlenül is lehet programozni. Majdnem minden nyelvnek van olyan kiterjesztése, amelyben szerepelnek OO eszközök. Ilyenek például az OO COBOL, Object Pascal, C++.
135
OBJEKTUM - ORIENTÁLTSÁG Terminológia: −
Objektum alapú nyelvek: (object-based) ha a nyelvben van objektum fogalom és bezárás, de nincs osztály és öröklés. (Pl. Ada)
−
Osztály alapú nyelvek: (class-based) van osztály, bezárás, objektum fogalom, de nincs öröklődés. (Pl.: CLU)
−
Objektum-orientált nyelvek: (object-orinted) minden létezik: bezárás, osztály, öröklődés fogalom. Ezek a nyelvek (imperatív nyelvként) fordítóprogramosak.
−
És végül létezik az OO-nak egy olyan speciális nyelve, amelyben nincs osztály fogalom, de minden más OO eszköz megvan benne. Programozási nyelvek hierarchiája: (nyíl: származik, hatás; aláhúzás: objektum-orientált)
ALGOL60
Pascal
OBJECT PASCAL
SIMULA 67
A DA
C ++
C
SMALLTALK 72
OBJECTIVE C
SMALLTALK 74
SMALLTALK 76 TURBO PASCAL SMALLTALK 80 A DA 9 5 TURBO PASCAL 6 .X M O D U LA 2
EIFFEL
JAVA
SMALLTALK / V
VISUALAGE SMALLTALK DELPHI
OBERON
136
A C++ -RÓL
A C++-RÓL -
Hibrid nyelv.
-
Alapvetően eljárás-orientált.
-
A C eljárásorientált nyelvet bővíti ki objektum-orientált eszközökkel. Átveszi a C-nek majdnem minden elemét, de nem kompatíbilis a C-vel: a C++ erősen típusos nyelv, a C nem az. Minden objektum-orientált eszköz meg van valósítva a C++-ban. Az eljárásorientáltságot mutatja, hogy a C++, mint nyelv nem bocsát rendelkezésre sztenderd osztályhierarchiát. Vannak hozzá osztálykönyvtárak, de nem a nyelvben vannak.
-
A többszörös öröklődés elvét vallja. Az öröklődésnél az attributumok és a módszerek mindegyike öröklődik. Létezik a módszerpolimorfizmus, az alosztály új attributumokat értelmezhet, és a láthatósági szabályokat meg tudja változtatni, mást nem.
-
E két utóbbiból következik, hogy osztályhierarchia gráfok vannak (a sztenderd könyvtárból származtatható).
-
A statikus kötés elvét vallja alapértelmezésként. Ha a programozó dinamikus kötést akar, akkor ezt külön kell előírni a VIRTUAL alapszóval. Ezek a virtuális függvények a C++ terminológia szerint (amelyek polimorfak).
-
A C++ terminológia szerint egy osztályon belül tagok (member) vannak. Háromfajta tagról beszél: -
Adattagok: attribútumoknak felek meg.
-
Tagfüggvények: módszereknek felelnek meg (C függvények)
-
Tagosztályokat is tartalmazhat az osztály.
-
Ismeri az absztrakt osztály fogalmát, tiszta virtuális függvényekről beszél.
-
A láthatóságot többszinten szabályozza (A leszármazott osztály módosíthatja a láthatósági viszonyokat.): -
A private-ként deklarált tagokat csak az adott osztály és a barátnak (friend) deklarált osztály tagjai láthatják.
-
Ha a tag protected láthatóságú, akkor az az adott- és a leszármazott osztályokban látszik.
-
Public esetben az osztályokon kívülről, mindenhonnan láthatóak. Nyilvános tagok deklarációja.
.Mit takar a “mindenhonnan” C++-ban? Mi a külvilág?−
Egy osztály definíciója a következőképpen néz ki:
CLASS név [:láthatósági_előírások szuperosztályok] {tagdefiníciók} Az opcionális részben definiálható át az öröklött láthatósági viszony. Két alapszó állhat itt: public és private. Szuperosztálynak léteznie kell. A szuperosztályból átveszi az újonnan definiált osztály a
public-nak és protected-nek deklarált tagokat. A private-tal levédhetem, hogy ezentúl ne láthassák az innen leszármazott osztályban ezen tagokat.
137
A C++ - RÓL Példa:
class X{ private int a;
/* a: csak X-en belül látható */
protected int b;
/* b: örökösök látják */
public int c;
/* c: mindenki látja */
};
class Y1 : public X {...}; class Y2 : private X {...}; Származtatunk az X-ből két osztályt: az Y1-t és az Y2-t. A további láthatóság szabályozása a következőképpen történik: −
Az Y1 nem látja az a-t, látja a b-t és c-t. Az Y1 örököseire is ez vonatkozik.
−
Az Y2 nem látja az a-t, látja a b-t és c-t. Az Y2 örökösei nem látják a-t, b-t és c-t sem.
Példa: Sor kezelése egyirányban láncolt listával:
class list{ class node{ friend class list; private node* linV; int contents; };
private: node* head; public: list(){head=0};
/*konstruktor*/
void insert_at_head(int);
/*itt csak módszerspecifikációk*/
void insert_at_tail(int); int empty(); ... };
.Mi a fordítási egység a C++-ban? .Megtehetem, hogy a specifikáció és az implementációt külön fordítási egységbe teszem?-
138
A C++ -RÓL
class queue: public list{ public: void enqueue(int value){ list :: insert_at_tail(int value); } ... }
.Mi a :: szerepe a C++-ban? KONSTRUKTOR A konstruktor olyan függvény, amely kötelezően meg kell jelenjen minden osztályban. Neve megegyezik az osztály nevével. Feladata az inicializálás a példányosításnál. DESTRUKTOROK A destruktorok szolgálnak az objektumok megszüntetésére.
.Mi a konstruktor és a destruktor C++-ban? -
139
JAVA
JAVA www.EckelObjects.com Thinking in Java Nyékiné Gaizler Judit (szerkesztő) : JAVA útikalauz programozóknak (Kalibán Bt.) A Javát a SUN fejlesztette 1994-től. A Java, mint nyelv nyílt, heterogén, tetszőleges platformokat egyesítő elosztott rendszerek nyelveként születik meg. A C++ tökéletesítéseként jön létre. A Java megszünteti a C++ néhány eljárásorientált nyűgjét és a mutatóorientáltságot. Meghagyja a kifejezésorientáltságot. Tisztább objektum-orientált nyelv, mint a C++, majdnem tiszta OO nyelv, kevésbé hibrid nyelv. A Java tervezési célkitűzése a hordozhatóság. Fordítóprogramos, de a Javaban megírt forrásprogramot a Java-fordító ún. bájtkód formátumra fordítja le (ez a bájtkód hordozható), és ezt a lefordított programot pedig az ún. Java Virtuális Gép (Java Virtual Machine - JVM) futtatja le. A JVM tehát egy interpreter, de készíthető hozzá célhardver, amelynek gépi kódja a bájtkód, illetve megadható olyan fordító, amely a bájtkódot valamely konkrét gépi kódra fordítja le. Egyrészt a JVM tekinthető egy: -
Absztrakt számítógépnek, amelynek a gépi kódja ez a bájtkód, és interpreteres, amelyet (általában szoftveresen) szimulálunk egy konkrét platformon. Ennek a koncepciónak a nagy előnye, hogy a Java programok hordozhatóak. Ugyanaz a bájtkód jön létre, ugyanaz a JVM van megírva minden platformon. Egységes. A JVM feloldja az egyes platformok közötti különbségeket.
-
Elképzelhető, hogy a JVM egy tényleges hardver, a bájtkód mögött egy célhardver van. És ezt a bájtkódot továbbfordítom egy adott processzor gépi kódjára.
A Java nyelv: -
A Java meglehetősen magas szintre elviszi az absztrakciót.
-
Hálózati környezetben biztosítja az újrafelhasználhatóságot.
-
Párhuzamos programozást is lehetővé tesz.
140
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
A JAVA FILOZÓFIÁJÁN KERESZTÜL BEMUTATVA AZ OBJEKTUM-ORIENTÁLTSÁGOT Egyesíti az eddig tárgyalt fogalmakat.
KARAKTERKÉSZLET Az UNICODE 16 bites kódtáblán alapul, ahol betű minden nyelv betűje lehet. Tartalmazza az összes nemzetközi abc-t. Egy baj van, hogy a különböző platformok, az operációs rendszerek egy része ezzel az UNICODE-dal nem tudnak mit kezdeni, nem tudja kezelni ezt, tehát konkrét platformon ez az előny nem érvényesül.
Ezért
maradunk az eddig megszokott ASCII valamely változatánál. A kis- és nagybetűt megkülönbözteti.
AZONOSÍTÓ Az azonosító fogalom a szokásos, azzal a megjegyzéssel, hogy betű nem csak az angol abc betűi, hanem a nemzetközi betűk, és az _ és $ jel is ide tartozik.
KULCSSZAVAK Eléggé C++ szerűek. A CONST és a GOTO alapszó, de nincs implementálva, nincs mögötte semmi.
STANDARD AZONOSÍTÓ Nincs.
MEGJEGYZÉS Háromféle megjegyzés formátummal dolgozik: -
// -től sorvégéig megjegyzés.
-
/* */ zárójelek között tetszőleges hosszan, ahol szóköz az elhatároló.
-
/** */ dokumentációs megjegyzés. (Ezzel nem foglalkozunk.)
CÍMKE A címke azonosító, utasítás előtt áll, kettősponttal elválasztva. Minden utasítás címkézhető.
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
141
TÍPUS: NEM OBJEKTUMOK Mivel léteznek a Javában olyan eszközök, amelyek nem objektumok, ezért majdnem tiszta OO nyelv. Ilyenek például: -
Beépített típusok:
-
boolean
-
char
-
byte
-
short
-
int
-
long
-
float
-
double Ezek az eljárásorientáltság értelmében típusok. Nincs mutató típus, de van logikai. Ezek tartományainak elemei jelenhetnek meg literálként. Szerepük: változókat lehet velük deklarálni, amelyek szintén nem objektumok. Létezik tehát a Javában a hagyományos változó fogalom.
-
Beépített osztályok: az egyes típusok osztályváltozata, amelyek példányosíthatók. Nevük ugyanolyan, mint a beépített típusoké, annyi különbséggel, hogy ezek mindig nagybetűvel kezdődnek. Pl.
Boolean, Char, Byte, stb. (Minden típusnak létezik az osztályváltozata.) A strukurált típusok közül létezik az egydimenziós tömb. Többdimenziós tömb nincs. Az index 0-tól indul. A név mögött [ ] zárójel jelzi, hogy tömbről van szó.
LITERÁL Léteznek C-szerű literálok, amelyek a beépített típusok értékkészletéből valók. -
’a’
-
”alma” : sztring literál
: karakteres literál
BEÉPÍTETT NEVESÍTETT KONSTANSOK Ilyen például:
-
true, false
-
null objektum Nevük foglalt szó.
142
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
VÁLTOZÓ Változó deklaráció C-szerű:
típus névlista; A deklarációban kifejezéssel kezdőérték adható a változóknak futási időben kiértékelődő kifejezéssel:
int x=0; Automatikus kezdőértékadás nincs. Az inicializálást a Java megköveteli.
KIFEJEZÉSEK A C kifejezés fogalmát veszi át a C++-on keresztül. C, C++ szerű a precedencia táblázat és kiértékelés, de a kiértékelést a hivatkozási nyelv szabályozza. Jóval kevesebb implementációfüggő rész van, mint a C-ben. Ha a C-ben meghívok két függvényt, és összeadom őket, akkor implementációfüggő a kiértékelés, de a Javában nem. Szigorúan típusos nyelv, azzal a megkötéssel, hogy a konverziót bizonyos esetekben megengedi (nem úgy, mint az Ada).
UTASÍTÁSOK 1. A deklarációs rész csak változó deklarációból áll. 2. A végrehajtható utasítások: -
Kifejezés utasítás. Ld. értékadó kifejezés.
-
Új objektum létrehozása. Példányosítás, ami a Java szempontjából egy művelet. Ld. precedencia táblázat.
-
Módszerhívás.
-
Vezérlő utasítások: ld. később. C, C++ szerű.
BLOKK: -
A blokk { } zárójelek között szerepel.
-
Cimkézhető.
-
Tetszőleges mélységben egymásba skatulyázható.
-
Változó blokk lokális változójaként deklarálható.
-
A blokkon belül tetszőleges a deklarációs- és végrehajtható utasítások sorrendje.
-
A lokális változók hatásköre statikus. Létezik lokális változó, de nincs lyuk a hatáskörben szituáció, mert a Java nem engedi meg az újradeklarálást.
-
Ezen változók élettartama dinamikus.
A nemobjektumok szerepe a módszerek törzsén belül van.
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
143
VEZÉRLŐ UTASÍTÁSOK FELTÉTELES UTASÍTÁS
IF(feltétel) utasítás ELSE utasítás; Teljesen C-szerű, annyi eltéréssel, hogy a C-vel ellentétben a feltétel logikai típusú. TÖBBSZÖRÖS ELÁGAZTATÁS
SWITCH(egész_kifejezés) { CASE egész_literál: utasítások [CASE egész_literál: utasítások] ... [DEFAULT: utasítások] } ELŐFELTÉTELES CIKLUS
WHILE(feltétel) utasítás; VÉGFELTÉTELES CIKLUS
DO utasítások WHILE(feltétel); Akkor ismétel, ha a feltétel igaz. ELŐÍRT LÉPÉSSZÁMÚ CIKLUS
FOR(p1; p2; p3) utasítás; BREAK UTASÍTÁS
BREAK[cimke]; A fenti konstrukciókban is ott vannak a blokkok. A break utasítás befejezteti azt a legbelső blokkot, amelyben ezt az utasítást kiadtam. Ha meg van adva az opcionális címke, akkor bármelyik szintről befejeztethetek egy egész blokksorozatot. Viszont módszerekből és inicializáló blokkokból nem lehet kilépni. CONTINUE UTASÍTÁS
CONTINUE[címke]; Ciklusok esetén adható ki: a törzs hátralevő részét nem hajtja végre, hanem visszatér a vezérlő részhez, a fejhez. Ha szerepel a címke, akkor az adott címkéjű ciklus fejére tér rá a vezérlés. RETURN UTASÍTÁS
RTURN[kifejezés]; Függvényben kötelező a return kifejezés;
144
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
OSZTÁLYOK Az osztály egy absztrakt adattípus implementációja! A hangsúly a típuson van. A Java program legkisebb egysége az osztály, így a programozás az osztályok, megírásából áll. Az osztály egyben a legkisebb fordítási egység. Természetesen egy fordítási egység akárhány osztályt tartalmazhat, de egyet mindenképpen kell. Az osztályok csomagokba szervezhetők. Ha egy osztályt lefordítunk, meg kell mondani, hogy melyik csomag osztálya. (Még visszatérünk rá.) Egy osztály attribútumait változó deklarációk (adattagok − data member), módszereit függvény definíciók alkotják (amely függvényeket a Java tagfüggvényeknek – member function-nek hív). Közös néven tehát tagokról beszélünk. Egy Java osztály tagok segítségével építhető fel. Egy osztály minden példánya saját készlettel rendelkezik az adattagokból (példányváltozók). A példányosítás során az adatoknak megfelelő tárrész lefoglalódik. Ahány példány van, annyiszor foglalunk helyet a tárban. Egy osztály valamely módszerének meghívásánál meg kell adni, hogy melyik példányra hívtuk meg. Ez az aktuális példány. A Java ismeri a példányváltozó, példánymódszer illetve osztályváltozók és osztálymódszerek fogalmát. A példányváltozók a példányok állapotait, a módszerek a példányok viselkedését írják le. Az osztályváltozók és osztálymódszereket a Java statikus tagoknak hívja. Ezek magához az osztályhoz kapcsolódnak, az osztályváltozókból egy-egy van és az osztálymódszerek, ezeken dolgoznak. Ezek akkor is működnek, ha egyetlen példányuk sincs. Az osztály szerkezete: -
fej
-
törzs
Fej:
[módosító] CLASS név [EXTENDS szuperosztály_név] Absztrakt adattípus létrehozása a Javaban. A Java az egyszeres öröklődés elvét vallja. Ha nem adok meg szuperosztályt, akkor automatikusan az Object osztályhoz fog kapcsolódni az adott osztály. Az osztályhierarchia egy fa, amelynek gyökere az Object osztály. Terminológia: a Java időnként nem öröklődésről, hanem kiterjesztésről beszél.
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
145
Módosítók a következők lehetnek: -
abstract: absztrakt osztály definíció, nem példányosítható.
-
final:
ennek segítségével olyan osztályt definiálunk, amely osztály nem kiterjeszthető. Az osztályhierarchiában levélelemet definiálunk ezzel, nem lehet belőle örökölni.
-
public:
ezzel olyan osztályt definiálunk, amely bármelyik csomagból látható. (Egyébként csak abban, amelyikben van.)
Több is szerepelhet belőlük értelemszerűen egyszerre: pl. abstract és final-nak nem lenne értelme. Törzs:
{ } zárójelben tetszőleges sorrendben tetszőleges számú tagdefiníció áll.
ATTRIBÚTUMOK Egy példányváltozó definíciója:
[módosító] típus név [=kezdőérték] [,név [=kezdőérték]]... ; −
A kezdőérték kifejezés. Az objektum származtatáskor sem lehet hatrozatlan állapotban, alapállapotba kell hozni (ld. konstruktorok). Ha nincs explicit kezdőértékadás, akkor a fordító implicit kezdőértéket ad a példányváltozóknak:
−
−
logikai típus esetén: false
−
a többi beépített típus esetén: 0
−
objektum típus esetén: null értékre állít.
A módosító: −
Itt is létezik final, amely egy konstans adattagot ír elő (nem változtatható adattag), vagyis ha egy explicit kezdőértékkel ellátott adattagot definiálunk, az nevesített konstans lesz. Ha final esetén nem adunk meg kezdőértéket, akkor ez egy üres nevesített konstans lesz, ennek kezdőértékét konstruktorral kell rögzíteni.
−
A láthatóságot (a bezárást) a következő módon szabályozza: −
Alapértelmezés szerint (amikor nincs alapszó) “friend” azaz félnyilvános. Ekkor ez az adattag az adott csomagból látszik, ahol az osztályt elhelyeztem.
−
A private (privát) alapszóval ellátott adattagot csak az adott osztály látja. Ezeket az osztály bezárja.
−
Ha a protected (védett) az alapszó, akkor ezt csak a leszármazottak láthatják.
−
A public (nyilvános) alapszóval deklarált adattag mindenhonnan látszik.
−
Vannak még további módosítók, amelyekkel később foglalkozunk.
146
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
MÓDSZEREK A módszerek definíciója a következőképpen néz ki:
[módosító] fej törzs -
A fejet szokás objektum-orientált körökben signature-nek (lenyomat, szignatúra) hívni (ez a korábbi specifikációnak felel meg), és a törzs a szokásos. Ld.: C, C++. (Kiegészül majd a kivételkezelésnél.)
-
A módosítók a példányváltozóknál megbeszéltek, plusz bejönnek újabb módosítók: -
final: nem implementálható újra, nem polimorf.
-
abstract, mellyel absztrakt módszer definíciót írunk elő. Ilyenkor nincs törzs (implementáció), csak absztrakt osztályokban szerepelhet értelemszerűen. Valamely leszármazott adja meg az implementációt. Egy osztály mindaddig absztrakt, míg legalább egy módszere absztrakt. Absztrakt osztályokban szerepelhet nem absztrakt módszerdefiníció is.
-
A static módosítóval statikus tagokat definiálhatunk.
-
Itt is vannak további módosítók, amelyekkel később foglalkozunk.
Az osztályon belül definiált módszerek látják az osztályon belül definiált minden adattagot. Amikor egy módszert meghívok, az nem más, mint egy függvényhvás. Paraméterkiértékelés módszereknél: -
sorrendi kötés
-
számbeli egyeztetés
-
típusegyeztetés
Látni fogjuk, hogy e két utóbbi nagyon-nagyon lényeges. Paraméterátadás: A paraméterátadás értékszerinti. Hivatkozás Minősítéssel hivatkozunk egy tagra.
csomagnév.osztálynév.objektumnév.tagnév
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T Példa osztálydefincióra a Java útikalauzból:
public class Alkalmazott{ String nev; int fizetes; void fizetesEmel(int novekmeny){ fizetes = fizetes+novekmeny; } boolean tobbetKeresMint(Alkalmazott masik){ return fizetes > masik.fizetes; } } -
-
Van két példányváltozó: -
név, ami a String osztály egy példánya
-
fizetes, ami egy egyszerű típusú adattag.
Van két példánymódszer: -
fizetesEmel : egy eljárás
-
tobbetKeresMint : egy függvény, benne return kifejezés;
Konvenció: -
Az osztálynév nagybetűvel,
-
A tagnév kisbetűvel kezdődik.
Módszer hívása: -
Vagy kifejezés
-
vagy utasítás: valamit csinál, azaz mellékhatása van.
System.out.println(”szövegkiírás”);
147
148
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
PÉLDÁNYOSÍTÁS A new operátor segítségével történik, amely referencia típusú operátor. Amikor egy objektumot hozok létre, egy speciális változót definiálok. A változó neve segítségével nevezzük meg az objektumot. .
Alkalmazott a; Definiálunk egy osztályt, az osztály ezáltal része lett a hierarchiának. Definiálhatok ilyen típusú változókat. Az a
Alkalmazott típusú lesz. Ez nem példányosítás, nem rendelkezik értékkel. Amennyiben egy Alkalmazott osztálybeli példányt akarok definiálni, a következő módon tehetem meg:
Alkalmazott a=new Alkalmazott(); Ekkor létrejön egy a nevű változó: lefoglalódik egy tárterület számára. A new hatására valahol hely foglalódik a példányváltozók számára, azok kezdőértéke beállításra kerül (a megírt vagy default Alkalmazott() konstruktor hatására), és az a változó értékül kapja a lefoglalt terület kezdőcímét. Ez azonban nem egy mutató típus. Ezt a Java referenciának hívja. Különbség a referencia és a mutató között: az
a változó mindig a mögötte lévő objektumot fogja hivatkozni, nem címként kezelendő, értéke nem machinálható: nem lehet például hozzáadni egy értéket, csak az a nevű objektummal dolgozhatok. Lehet:
Alkalmazott a, b;
-- Változók, de nincs értékük, -- vagy null értékűek (ha adattagok).
a = new Alkalmazott(); b = a;
-- a cím átmásolását jeleni, -- nincs új terület foglalás -- az a és b ugyanazt az obektumot hivatkozza
a.fizetes=50000; Ha valamely osztály definícióban ilyen adattagot definiálok, megtehetem:
final Alkalmazott a=new Alkalmazott(); ami annyit jelent, hogy definiáltam egy a nevű referencia típusú változót, amely mindig ugyanarra az alkalmazottra mutat (amely tetszés szerint változtathatja az állapotát), és nem címezhet más objektumot. A JVM minden példány vonatkozásában tartalmaz egy referenciaszámlálót, amelyet a JVM az objektum létrejöttekor rendeli hozzá az objektumhoz. Ez a referenciaszámláló (mely megmutatja, hogy hány változó címzi az objektumot) nő, ha hivatkozok egy példányra, és ha megszüntetek egy hivatkozást, csökken. Erre épít egy garbage collection-t (szemétgyűjtögetés). Ha a referenciaszámláló értéke 0, akkor az adott példány törölhető, a rendszer fölszabadítja a helyét. Automatikus.
a = null; bármikor lehet. Ez megszünteti a referenciát. Nem kell explicit módon felszabadítani egy fölöslegessé vált objektum területét.
149
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
STATIKUS TAGOK STATIKUS ADATTAGOK Alakja:
static int nyugdijKorhatar = 65; Osztálynévvel minősíthető:
Alkalmazott.nyugdijKorhatar;
STATIKUS MÓDSZEREK Például: static void nyugdijKorhatarEmel(){
nyugdijKorhatar++; } Osztálymódszereknél nincs példány.
FŐPROGRAM A program indításakor egy osztálynevet kell megadni, amelyben van egy main nevű eljárás a következő specifikációval:
public static void main (string args[]) −
A virtuális gép ennek adja át először a vezérlést.
−
A specifikáció kötött
−
Paramétere egy tömb, amely az indításkor megadott argumentumokat tartalmazza. Ezek megadása, szerepe rendszerfüggő.
−
A program befejeződik, ha: −
Ez befejeződik vagy
−
Ha a programon belül meghívjuk a System osztály exit módszerét.
Ha egy osztály definíciójánál nem adunk meg szuperosztályt, akkor automatikusan az Object osztály alosztálya lesz. Ez az osztály a hierarchia gyökere a Javaban.
150
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
Példa:
class Factorial{ public static void main(String[] args){ int factorial = 1; int i = 1; while(i < 10){ factorial = factorial * i; System.out.println(i + “!=” + factorial + “
” );
i++; } System.out.println(); } } A this ÉS A super PSZEUDÓVÁLTOZÓ Egy módszer törzsében használható a this és a super pszeudóváltozó. Ezzel hivatkozhatunk egy módszer törzsében a megfelelő aktuális példányra, illetve a megfelelő szuperpéldányra.
boolean kevesebbetKeresMint (Alkalmazott masik ){ return masik.tobbetKeresMint(this); } A this és a super szavak túlterheltek a Javaban.
MÓDSZEREK TÚLTERHELÉSE A módszernevek túlterhelhetők a Javaban, azaz több módszert ugyanazzal a névvel nevezhetünk meg, ha a formális paraméterek száma vagy a sorrendi kötés értelmében a típusa eltérő. Ekkor a módszer meghívásakor a megfelelő kódot a fordító az aktuális paraméterek száma és típusa alapján választja ki. A paraméterkiértékeléskor derül ki, hogy melyik a meghívandó kód. Ld. Factorial példánál a println() módszer esetén.
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
151
POLIMORFIZMUS Egy örökölt példánymódszer implementációja tetszés szerint megváltoztatható. Az újraimplementált módszernek felülről kompatibilisnek kell lennie az örökölt módszerhez, azaz: −
Specifikációjuk megegyezik.
−
Az újraimplementált módszerek csak azokat a kivételeket válthatják ki, mint az örökölt módszer.
−
A bezárást csak enyhíteni lehet, szűkíteni nem: pl. protected → public lehetséges, de fordítva nem.
A Java a dinamikus (késői) kötés elvét vallja, nem kell külön előírni, mint a C++-ban. (Nem kell virtual!) Egy módszer meghívásakor egy módszer nevéhez mindig az aktuális példány osztályában definiált, vagy a legközelebbi örökölt kód fog meghívódni. Ezzel szemben az osztálymódszerek statikusan kötnek, osztálymódszereket nem lehet átdefiniálni. (Egy osztályhierarchia van, és az újradefinálással ezt a hierarchiát rúgnánk szét.)
ADATTAGOK ELREJTÉSE Az összes tag örökődik. A Java tiltja, hogy bizonyos tagokat elhagyjunk az öröklődés során. Viszont bevezeti az elrejtés (elfedés) fogalmát. Jelentése: az öröklődés során a leszármazott újraimplementálhatja a módszereket, és átdefiniálhatja az adattagokat, új tagokat definiálhat, de megszüntetni nem szüntetheti meg az örököltetett. Ezzel elrejti az eredetieket a hozzáféréstől az adott és az innen leszármazott osztályokban.
KONSTRUKTOROK Amikor példányosítunk, a példány alapállapotát be kell állítani, ezt megtehetjük paraméterek segítségével. Ezt a célt szolgálják a konstruktorok (a konstruktorok tehát az alapállapot definícióját segítik elő), amelyek típus nélküli, az osztály nevével azonos nevű módszerek, amelyek láthatóságát szabályozhatjuk csak. A konstruktorok a példányosításnál automatikusan meghívódnak, és inicializálják azt. Az adattagoknak lehet kezdőértéket adni explicit módon, ha nem, akkor a rendszer inicializál. A programozó egy osztályhoz tetszőleges számú konstruktort írhat, és ezek neve túlterhelhető (a pataméterek száma és/vagy típusa különböző kell legyen). Kostruktor kizárólag a new operátor mellett hívható meg. A konstruktorok nem örökölhetők. A konstruktor törzsének első utasítása lehet egy adott osztálybeli, vagy egy szülőosztálybeli másik konstruktor meghívása a következő szintakszissal:
this(); illetve super(); A programozó nem köteles megadni konstruktort. Ha a programozó nem ad meg konstruktort, akkor a rendszer automatikusan felépít egyet, méghozzá olyat, amely paraméter nélküli és a törzse üres.
152
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
Példa:
public class Alkalmazott{ ... public Alkalmazott(String egyNev, int egyFizetes){ nev=egyNev; fizetes=egyFizetes evesFizetes=12*fizetes; } public Alkalmazott(String egyNev){ nev=egyNev; fizetes=30000; evesFizetes=12*fizetes; } ... } Két konstruktort definiáltam. Egyiknek egy paramétere van, a másiknak kettő. Ez alapján tudja a rendszer eldönteni, hogy aktuálisan melyiket hívjuk meg. Említettük, hogy minden konstruktor törzsének első utasítása lehet egy másik osztályhoz tartozó konstruktor meghívása. Ebben az esetben átírható a következő módon:
public class Alkalmazott{ ... public Alkalmazott(String egyNev, int egyFizetes) { nev = egyNev; fizetes = egyFizetes; evesFizetes = 12 * fizetes; } public Alkalmazott(string egyNev){ this(egyNev,30000); } ... } A szuperosztály konstruktorai nem öröklődnek, azokat mindig minden osztályhoz meg kell adni (vagy implicit). Viszont minden alosztály bármely konstruktora törzsének első utasításaként a super kulcsszóval meghívható a szuperosztály valamelyik konstruktora. (Például új adattagokat definiáltunk, de az átvetteket ugyanígy kell
153
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T inicializálni.)
A konstruktorok aktuális paramétereit példányosításnál a new operátor paramétereiként kell megadni a következőképpen:
new konstruktor_név(aktuális_paraméter_lista);
Alkalmazott a = new Alkalmazott("Ó Pál "); Alkalmazott b = new Alkalmazott("Jó Jenő”, 55555); A Java lehetővé teszi, hogy osztály-konstruktorokat definiáljunk, az osztály definíción belül akárhol, tetszőleges számút. Ezek lényegében blokkok, amelyek előtt ott áll a static kulcsszó. Ezek az ún. statikus-konstruktorok (statikus inicializátorok). Definiálom az osztályt, a konstruktorokat. Amikor az osztályt először használom fel típusként, a rendszer végrehajt egy osztály inicializálást, melynek során automatikusan lefutnak az osztály-konstruktorok, abban a sorrendben, ahogy felsoroltam őket.
A finalize() MÓDSZER Az osztályokkal kapcsolatos fogalomrendszert azzal zárjuk, hogy az Object osztályban létezik olyan módszer, amelynek finalize() a neve (protected és void). A finalize() olyan módszer, amelyet minden osztály örököl és átdefiniálhat. Minden osztály implementálhatja. Amikor egy objektum felszabadulásra kerül (azaz az objektum megsemmisítésekor), a tényleges felszabadítás előtt lefut ez a módszer: "Az adott példány egy utolsó kívánsága. " Meghívása automatikus. Létezik ennek egy osztály szintű változata is (osztálymódszer):
classFinalize() Abszolút platform függő. Ez az osztálymódszer az osztály megszünésekor automatikusan lefut.
.Mit jelent az, hogy megszünik egy osztály? -
154
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
INTERFÉSZEK Speciálisan JAVA eszköz. Az interfész egy speciális referenciatípus, amely konstans adatttagokat és módszer specifikációkat tartalmaz. Az interfész nem objektum. A célja az absztrakciós szint növelése. Úgy tudunk problémát megoldani, hogy az implementációt nem adjuk meg. Programfejlesztés közben behozok egy absztrakciós szintet, amikor a specifikációval foglalkozom, és az implementációval nem. Az interfészek között is értelmezhető az öröklődés, méghozzá többszörös. Közös ősinterfész nincs, tehát a programfejlesztés folyamán interfész hierarchia-gráfok építhetők fel. Az interfészek implementációját mindig egy osztály végzi teljes mértékben. Az interfészt teljes mértékben implementálnia kell az osztálynak. Tehát az interfész hierarchia alján mindig osztályok állnak. Egy osztály tetszőleges számú interfészt implementálhat. Az interfészek által jelenik meg a többszörös implicit öröklődés a Javaban. Az interfész, mint referencia típus mindenütt szerepelhet, ahol az osztály, mint típus szerepelhet. Lehet vele változókat, tagokat definiálni, és formális paramétereket leírni. Az így definiált eszköz egy olyan referenciát kaphat értékül, amelynek a típusa egy olyan osztály, amely az adott interfészt közvetve vagy közvetlenül implementálja. Múltkori példánkban eljárhattunk volna úgy, hogy az egyes osztályokat, mint interfészeket definiáljuk, és az Ellipszis interfészt implementálja közvetlenül a Kör osztály, és a Zárt alakzat és az Alakzat interfészeket közvetve. Példa: Alakzat Nyílt
Zárt Poligon
Ellipszis Kör
Az interfész definíciója:
módosító INTERFACE név [ EXTENDS szuperinterfész_nevek ] törzs Az interfész módosító – ja csak public lehet.
155
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
A törzsben: −
az adattagok módosítói alapértelmezés szerint:
public static final Nem írandók ki, és nem változtathatóak. Konstans adattagokról van szó, tehát a kezdőérték adás kötelező. −
a módszereknek csak a specifikációjuk szerepel A módosítók:
public abstract. Nem kiírandók, és nem kiírhatók. A többszörös öröklődésből származó névütközéseket a Java nem kezeli.
Interfészek implementálása :
módosító CLASS név IMPLEMENTS interfész_nevek törzs Az összes absztrakt módszert implementálni kell. Nagyon kemény absztrakciós eszköz és nagyon jól használható.
CSOMAG Abban az értelemben, ahogy az Adában. A csomagok tartalmazzák a Java fejlesztői környezetet és az alkalmazásokat is csomagokban írjuk meg. A csomag egy hatásköri egység. Fordítási egység a Javaban: −
osztály deklaráció vagy
−
interfész deklaráció
−
vagy ezek tetszőleges együttese.
Az osztályokat és az interfészeket együttesen hívja a Java típusnak. Tehát fordítási egységek a típusdeklarációk és ezek tetszőleges együttese. A fordítási egység mögötti kód mindig kötelező módon csomagokban jelenik meg. A csomagok között hierarchia építhető föl (könyvtárszerkezet). A Java rendszer tehát csomagfák (csomagok alkotta fák) együttese, melyek tartalmazzák a fejlesztői környezetet és az alkalmazásokat is. A csomag tartalmazhat alcsomagokat és típusdeklarációkat tetszőleges mélységben. Megnevezés: névvel. Hivatkozás: minősítéssel. Egyrészt a csomagfán belüli csomagra, másrészt csomagon belül: típus – objektum – tag.
156
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
Egy fordítási egység teljes szerkezete:
[CSOMAGLEÍRÁS][IMPORTLEÍRÁS][TÍPUSDEKLARÁCIÓK] − csomagleírás:
PACKAGE csomagnév ; A megadott nevű csomaghoz fog tartozni a lefordított kód. Ha nem szerepel, akkor a Java rendszer egy név nélküli csomagba tartozónak tekinti (ebbe most nem megyünk bele). A név nélküli csomagok kezelése rendszerfüggő. Nem javallott, hogy név nélküli csomagokat használjunk, a hordozhatóság sérülhet. Általában igaz, hogy egy név nélküli csomag van. −
importleírás: import minősített_név ; Gyakorlati eszköz. Az Ada környezet leírásának felel meg. Arra szolgál, hogy más csomagokban deklarált nyilvános típusok itteni használatát segítse elő úgy, hogy egyszerű és ne minősített névvel kelljen rájuk hivatkozni. Példa:
import Alakzat.Zart.Ellipszis.Kor ; És ezután elég a Kor hivatkozás, de akkor ennek egyedinek kell lennie. Megengedett az:
import Alakzat.Zart.Ellipszis.*; ekkor az összes publikus típust eléri a nem minősítet neve alapján. −
típusdeklarációk: itt osztály definíciók állnak.
A Java alapcsomagjai a java csomagban vannak. Ennek alcsomagjai: − java.lang
− java.net
− java.io
− java.applet
− java.util
− java.awt
− java.sql
− java.awt.event
A java.lang tartalmazza a legalapvetőbb eszközöket, ennek minden nyilvános típusa automatikusan importálódik, nem kell külön megadni. Ebben van például az Object osztály, a primitív típusok, stb. Az összes többi tagot importálnom kell.
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
157
KIVÉTELKEZELÉS A JAVABAN Adaszerű elveket vall. Ez is alapvető eszköze a Javanak. Ipari szabványszerű. A Java program működése közben módszerek hívódnak meg. Ha bekövetkezik egy speciális esemény egy módszer futása közben, akkor egy kivétel-objektum jön létre: vannak kivétel-osztályok és annak kivétel-példányai. Ekkor a módszer “eldobja” a kivételt, és a kivétel a Java virtuális gép hatáskörébe kerül át. Az adott módszer befejezi a futását annál az utasításnál, ahol a kivétel bekövetkezett, és jön a kivételkezelés. A JVM feladata, hogy megkeressen egy adott objektumnak megfelelő típusú, az adott pontba látható kivételkezelőt, amely kivételkezelő az adott kivételt elkapja. Egy kivételkezelő megfelelő típusú, ha: −
a kivételkezelő típusa megegyezik a kivétel típusával
−
a kivételkezelő típusa őse a kivétel típusának.
A láthatóságot maga a kivételkezelő definiálja. Maga a kivételkezelő egy blokk. Az Adában kivételkezelő fordítási egység végén helyezhető el, ezzel szemben a Javában tetszőleges kódrészlethez köthető. Ezek a kivételkezelők tetszőleges mélységben egymásba ágyazhatók. Ha van egy kivételkezelő, amely nem kezel minden kivételt, kérdés: hogyan tovább? A kivételkezelőblokkon lépked kifelé a JVM az Ada illetve a PL/1 filozófiája szerint dinamikusan, amíg nem talál megfelelő kivételkezelőt. Egy blokk végső soron egy módszer törzse. Ha nincs ott kezelve a kivétel, akkor a JVM továbbadja a kivételt a hívási láncon a hívónak. Ha talált megfelelő típusú kivételkezelőt a JVM, átadja annak a vezérlést, a kivételkezelő lefut, és a program folytatódik a kivételkezelő kódját követő utasításon. A kivételkezelőben bekövetkezett kivételeket ugyanígy kezeli a JVM, mint bárhol máshol. A kivételek két csoportjáról beszél a Java: −
ellenőrzött
−
nem ellenőrzött kivételek
Elengedi az ellenőrzést olyan eseményeknél, amelyek bárhol bekövetkezhetnek, ellenőrzése vagy nagyon kényelmetlen vagy lehetetlen, irreálisan nagy kódtöbbletet eredményezne, vagy a programozó ezekkel a kivételekkel nem tud mit kezdeni. Ez utóbbi kivételek tartoznak a nem ellenőrzött kivételek közé. Javallott, hogy használjunk ellenőrzött kivételeket. Az ellenőrzött kivételeket, amely egy metódus láthatósági körében felléphet, a programozónak mindig specifikálnia kell, vagy el kell kapnia őket. Ezt a fordító vizsgálja, és hibát jelez, ha ez nem teljesül. Biztonságos kódot kell írni! Egy módszer fejében tehát meg kell adni azokat az ellenőrzött kivételeket kivételeket, melyeket a módszer nem kezel, de futás közben bekövetkezhetnek. Ennek specifikálása a módszer fejének végén:
THROWS kivételnév_lista utasításrész segítségével történik. Itt soroljuk fel, tehát a módszer láthatósági körében keletkezett, ellenőrzött, de nem kezelt kivételeket. A kivételek kezeléséhez a java.lang csomagban definiált ősosztály a Throwable
158
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
objektumai “dobhatók el” (miután a kivétel is objektum). Az eldobás a THROW utasítás segítségével történik. Két standard alosztálya van: −
Error
−
Exception : ellenőrzött kivételek osztálya. Ebből az osztályból származtathat a programozó
: ide tartoznak a rendszerhibák, ezek nem ellenőrzöttek.
saját ellenőrzött kivételeket. Természetesen a Java csomagjaiban számos leszármazottja van (standard kivételek). A throw utasítás alakja (a kivétel-osztály példányosítása): THROW NEW_OPERÁTOR; Ez kivált egy megfelelő típusú kivétel objektumot, és eldobja a kivételt, átadja a JVM-nek. Példa: Saját kivétel definiálása és kivételkezelés:
class VeremMegteltException extends Exception{ Object utolso; public VeremMegteltException( Object o){ utolsó = o; } public Object nemFertBele(){
-- ezzel érjük el a tetejét
return utolso; } }
class Verem{ final static public int Meret = 100; Object tarolo[]; int mutato = 0; : public void push (Object o){ ... if ( mutato != Meret ){ tarolo[mutato++] = o; } else{ throw new VeremMegteltException(o); --példányosítás } ... } ... }
JAVA FILOZÓFIÁ KERESZTÜL BEMUTATVA AZ OO-T
159
A kivételkezelő szerkezete a Javaban:
TRY { Utasítások } -- ellenőrzött kivételek, amelyek látják a kivételkezelőt CATCH (típus változónév ) {Utasítások} [CATCH ( típus változónév){ Utasítások}]... [FINALLY { Utasítások}] A TRY blokkban elhelyezett utasításokban keletkezett kivételek esetén a JVM a CATCH utasításoknak adja át a vezérlést. Ha ott talál megfelelő típusú ágat, lefutnak a blokkbeli utasítások, és végrehajtódnak a FINALLY utáni utasítások, és a program folytatódik a FINALLY után. Ha nincs egyetlen megfelelő kivételkezelő sem, és van FINALLY ág, lefutnak az utasításai, és a kivétel az adott kódrészt (TRY) meghívó módszerhez kerül, vagy beágyazott blokk esetén a tartalmazó blokkhoz. A FINALLY ág akkor is lefut, ha nem volt kivétel! A CATCH ág teljesen hiányozhat. A típusegyeztetés miatt a felírás sorrendje nagyon lényeges, ugyanis a CATCH ág az ellenőrzött kivételt elkapja. A hierarchiában lentről fölfele kell elkapni. Módosítsuk ezek alapján a kódot:
class Verem{ final static public int Meret = 100; Object tarolo[]; int mutato = 0; : public void push (Object o){ ... try{ if ( mutato != Meret ){ tarolo[mutato++] = o; } else { throw new VeremMegteltException(o); } ... } /*try*/ catch(VeremMegteltException e){ System.out.println(“A(z) ”+e.nemFertBele()+ ” obektum nem fért el a veremben!”); } catch(Exception e){ System.out.println(“Hiba! Hívja a rendszergazdát!”); } finally {System.out.println(“A push lefutott.”); }... }
160
JAVA - SZÁLAK
SZÁLAK A párhuzamos programozás eszközei a Javában a szálak. A szálak objektumok, a Thread osztályból származnak. Ezek run() módszere adja a futtatandó kódot. A Runnable interfészt implementáló osztállyal is megadhatunk szálakat. Ebben a run() módszert is implementálni kell. Itt is ez adja a kódot. A Thread osztály a Runnable egy implementációja. A szálak a Javaban a következő állapotban lehetnek: −
új
−
futtatásra kész
−
fut
−
várakozik
−
halott
Új szálat (mint objektumot) a
new operátorral hozunk létre, ekkor csak létrejön. Él a szál, de semmi több, semmi aktivitást nem mutat. Futásra kész állapotba hozni a start() módszerrel lehet. A futásra kész állapot annyit jelent, hogy beáll a sorba. A Java rendszerek általában egyprocesszorosak. A futásra kész szálak közül az ütemező választja ki a futtatandó szálat. Az egyprocesszoros rendszerek indeterminisztikusak! A kiválasztott szál működni kezd, fut (a
run() módszer indul el). Egy szál halott állapotú lesz, ha meghívjuk a stop() módszerét, vagy a run() módszer kódja elfogy. Nem lehet egy halott szálat újra elindítani. A várakozás a szinkronizáció eszköze a Javában. A szálak szinkroniációja meglehetősen sokszinű. A szinkronizációra a Hoare-féle monitort alkalmazza a Java. Ezzel kapcsolatban a következő eszközök állnak rendelkezésre: −
Egy szál a következő esetekben kerülhet várakozó állapotba: −
a sleep(x) módszer meghívásával, a paraméter ezredmásodpercben értendő. Hatásra az adott szál adott ideig várakozik.
−
−
a wait() módszer meghívásával. Szintén várakozást tudunk előidézni.
−
a suspend() módszer felfüggeszti a szál futását
−
I/O művelet befejezésére vár
Várakozó szál a következőképpen kerülhet futásra kész állapotba: −
sleep() esetén továbbmegy a szál az idő letelte után.
−
wait() esetén notify() vagy notifyAll() módszer meghívásával megszűnik a várakozás
−
suspend() módszer esetén a resume() módszerrel újraindul a futás
−
ha befejeződött az I/O művelet
JAVA - SZÁLAK
161
KÖLCSÖNÖS KIZÁRÁS Módszer esetén ha a fejben szerepel a synchronized módosító, akkor a rendszer az adott módszer futtatását úgy végzi, hogy érvényesüljön a kölcsönös kizárás. Ha blokk előtt szerepel a synchronized(objektum) előírás, a megadott objektum zárát helyezi el a blokkra. A szálak csoportokba szervezhetők, közösen, együtt kezelhetők a ThreadGroup osztály segítségével. Az egy csoportba tartozó szálakat egyszerre vezérelhetjük a suspend(), resume(), stop() módszerekkel. A Javaban vannak démonszálak. Ezek végszinkronizációs eszközök, akkor fejezik be működésüket, ha az összes nem-démonszál befejeződött. A join() módszer is a szinkronizációt szolgálja. Hatására egy másik szál hívható meg úgy, hogy a hívó szál megvárja, míg lefut a hívott szál. Ez időzíthető is.
162
MIT ÉRTÜNK JAVA PROGRAM ALATT?
MIT ÉRTÜNK JAVA PROGRAM ALATT? Kétfajta programról szokás beszélni: −
Alkalmazás: A Java rendszerben saját osztályokat definiálunk, és mószereket hívogatunk.
−
Appletek: programkák.
APPLETEK (PROGRAMKÁK) HTML oldalba ágyazható Java programok. Végrehajtásukat egy böngésző program végzi (Netscape vagy Explorer) esetleg egy segédprogram. Az appletek letölthetők és futtathatók. A java.applet csomag tartalmazza a szükséges interfészeket és osztályokat.
TOVÁBBI ESZKÖZÖK A java.net osztály eszközei arra szolgálnak, hogy hálózatos kommunikációt megvalósító programokat tudjunk írni. Egy alkalmazást olyan komponensekre tudunk bontani, amely komponensek a hálózat különböző pontjain futnak. Ehhez a Java a távoli objektumok kezelését nyújtja (remote object). A távoli objektumok módszereit egy másik gép el tudja érni. A Java protokolja (eszközrendszere) az RMI (Remote Method Invocation). A távoli objektumok módszereit interfészekben kell rögzíteni, specifikálni, és a JVM felépít egy csonkobjektumot, amely csonkobjektum képes felépíteni a kapcsolatot a távoli objektummal: meghívja a távoli objektum módszereit, azaz megszóllítja a másik JVM-et. Ott lefut a meghívott módszer, és a csonk visszaközvetíti a visszatérési értéket. A java.sql csomag adatbázis programozást tesz lehetővé. Relációs adatbázis kezelést valósít meg. Az adatbázisok elérését a Java a JDBC protokolon keresztül teszi lehetővé. Ez a JDBC egy adatbázis kezelő programozói interfész. A következő szolgáltatásokat nyújtja: −
összekapcsolódás egy relációs adatábzis-kezelővel
−
SQL utasításokat tudunk felolgozni.
Kliens – szerver architektúrát használunk, ahol a szerver rész a relációs adatbázis kezelő, amit megszólítunk, és a kliens az általában megírt program. Ennek van egy csomó nyűgje, ezt hívják kétrétegű architektúrának. Azonban jelen pillanatban divat a többrétegű architektúra. A program és az adatbáziskezelő közé jön egy középső réteg: middleware. A JDBC-n keresztül a midleware-t érem el. Ez a középső rétag a kommunikációt szolgálja. Mindkét végpont ezt a középső réteget szólítja meg, és a középső réteg szolgáltat információt a két végnek.
163
SMALLTALK
SMALLTALK Nem típusos nyelv.
KARAKTERKÉSZLETE −
a szokásos.
−
Kis és nagybetű megkülönböztetendő.
MEGJEGYZÉS −
” ” között, tetszőleges karaktersorozat.
ELHATÁROLÓ JELEK −
szóköz
−
(
−
)
−
[
−
]
−
.
−
|
A Smalltalkban minden objektum. Mint OO nyelv elvei: −
egyszeres öröklés
−
késői kötés (más lehetőség nincs)
−
üzenetalapú nyelv, alapeszköze az üzenet
−
automatikus objektum megsemmisítés: hatékony garbage collection (referencia alapú)
−
bonyolult bezárási mechanizmus, láthatóság szabályozása bonyolult
−
léteznek példány és osztályszintű elemek
−
vannak absztrakt osztályok
−
kollekciók léteznek
−
nem léteznek template-k
−
bonyolult standard osztályhierachiája van (fa)
164
SMALLTALK
A SMALLTALK OSZTÁLYHIERARCHIA RÉSZLETE
Object
Collection
Dispatcher
Boolean
True
Magnitude
LookupKey
Date
Time
Assosiation
Character
Number
Integer
SmallInteger
Fraction
LargeInteger
A Magnitude, Number, LookupKey absztrakt osztályok.
LITERÁLOK A megfelelő osztály példányai. Írásmód: a belsejében szóköz és egyéb elválasztó karakter nem lehet. −
SZÁMOK: egész, tört, lebegőpontos Például: 28, 3/4, 3.28
−
Létezik a KARAKTER, mint literál.
Float
False
165
SMALLTALK
−
Alakja:
$karakter
Pédául:
$c;
A SZTRING, mint literál: Alakja:
‘tetszőleges karakter sorozat’
Például:
‘almafa’
Létezik egy String osztály, ami karakterek egy egydimenziós tömbje. −
SZIMBÓLUM: (symbol) Alakja:
#tetszőleges_karakter_sorozat
Például:
#output
Speciális jelentése van. Alapvető szerep két szempontból: −
Mindig egységes és oszthatatlan, bizonyos objektumok neveit kezelhetem bizonyos helyzetekben szimbólumként.
−
A szimbolikus műveletek.
ATOM: Speciális jelentése van. Alakja: −
##tetszőleges_karakter_sorozat
TÖMB: tetszőleges literálok egydimenziós tömbje, literálként: Alakja:
#(tetszőleges literál sorozat) egymástól szóközzel elválasztva
Példa:
#(‘egy’ ‘kettő’ ‘három’ ‘négy’) #(1 2 3 4 5) #(1 ‘kettő’ #A) #(#1 28.3 $x)
VÁLTOZÓK Létezik a változó fogalom. Minden változó egy objektumot címez. Van neve, értéke és címe. Nincs típuskomponens (nem is kell). A változó is objektum, értéke is csak objektum lehet. Speciálisan oldja meg az objektumazonosítást: OID-vel. Mivel minden objektum, a változók nem magát az objektumot tartalmazzák, hanem egy OID-t. Ez egy referencia. Így nem kell a típus, hiszen az OID egyetlen módon van megvalósítva. Indirekt címzést valósít meg. A változó értékként egy objektum azonosítóját veheti fel. Mutat egy objektumra. Amíg egy változónak nem adunk értéket, addig NIL, ahol a NIL az UndefinedObject osztály példánya. Az objektumazonosító egységes. Bármilyen osztályt meg tud címezni. (Speciális referencia)
166
SMALLTALK
A BEZÁRÁS SZINTJEI: −
Példányváltozók (ld. Java: objektumok állapotának a leírására szolgálnak). −
Csak az adott osztály példánymódszerei látják. Az osztálymódszerek nem látják a példányokat.
−
Kisbetűvel kezdődik a nevük.
−
Privát változó.
−
Példányoknál újra elhelyeződik.
−
Ideiglenes változók: (temporary) módszerek lokális változói. Kisbetűvel kezdődik a nevük.
−
Globális változók: program globális változók.
−
−
Az adott program minden módszere látja őket.
−
Nagybetűvel kezdődik a nevük.
−
Nyilvános változók.
Szótárváltozók: a szótár speciális kollekció: táblázat. −
A Smalltalk lehetővé teszi, hogy bizonyos változók ilyen kollekciókba legyenek szervezve, és őket több osztály lássa. A szótárváltozók bizonyos osztályok módszerei által elérhető változók.
−
−
Félnyilvános jellegű. Külön eszköz.
−
A korábbi verziók megosztott változóknak hívja.
−
Nagybetűvel kezdődik a nevük.
Osztályváltozó (ld. Java osztályonként egy példányban létezik). Egy osztályhoz tartozó változó, minden példány ugyanazt a változót látja. Látják a leszármazott osztályok is. −
Az osztálymódszerek, példánymódszerek látják őket, beleértve a leszármazottakat is. Lentről felfelé látszik.
− −
Nagybetűvel kezdődik a neve.
Osztálypéldány-változó: −
Egy adott osztályban az osztálymódszerek látják őket, a leszármazottak nem. Nem öröklődik.
−
Kisbetűvel kezdődik a neve.
Terminológia: −
Kisbetűvel kezdődő nevű változók: privát változók, láthatóságuk korlátozott.
−
Nagybetűvel kezdődő nevű változók: megosztott változók, egyszerre több osztály látja.
167
SMALLTALK
OSZTÁLY Fordítási egység és egyben programegység. (ld. még később)
BLOKK Létezik a módszereken belül programegységként a blokk. (ld. még később)
MÓDSZEREK Módszerek nem ágyazhatóak egymásba, blokkok igen. Blokkok módszerekben fordulhatnak elő. (ld. még később)
UTASÍTÁSOK A program szövege utasításokból áll. Az utasításokat ponttal zárjuk le. A kód utolsó utasítása után nem kötelező a pont.
ÜZENET A Smalltalk egy üzenet alapú rendszer. Az objektumok üzenetek segítségével működnek együtt. (Ez megfelel egy alprogram hívásnak.) Az egyik objektum küldi (küldő); a fogadó megkapja, és válaszol mindig az üzenetre. Az üzenet formáját a módszer interfész része írja le. Az üzenetnek lehetnek argumentumai (~módszer paramétere). Minden üzenet meg van nevezve. A küldő elküldi az üzenet nevét és argumentumait, a fogadó értelmezi az elküldött üzenetet, majd megválaszolja azt. Mindig van visszatérési érték. Tehát az üzenetek módszerek. Mindkét objektumnak tudnia kell, hogy az üzenet mire való. Elnevezés: az üzenet neve szelektor. Ez a név kisbetűvel kezdődik. Egy üzenetnek van egy szelektora és lehetnek argumentumai. argumentumok Küldő
Fogadó
objektum
objektum visszatérési érték
Üzenetek két csoportja: −
Lekérdező módszerek (üzenetek): az objektum állapotát kérdezi le. Nincs argumentumuk.
−
A beállító módszerek (üzenetek): megváltoztatják az objektum állapotát (beállítja a fogadó objektum valamely változójának értékét). Általában van argumentumuk. Nevüket kettősponttal kell lezárni.
A változók és módszerek neve lehet ugyanaz: névtúlterheltség. A kódban levő pozíció dönti el, hogy változóról vagy módszerről van-e szó. Az üzeneteknek három csoportja van: −
unáris: nincs argumentumuk, lekérdező üzenetek. Csak szelektor van, nincs paraméter.
−
kulcsszó (keyword): van argumentumuk a szelektor után. (beállító üzenetek)
−
bináris: realizálják az eljárásorientált nyelvek operátorait. Az aritmetikai, logikai, hasonlító operátorokat realizáló üzenetek. Módszerekként vannak realizálva ezen operátorok.
168
SMALLTALK
KIFEJEZÉS Egy Smalltalk utasítás egy vagy több kifejezést tartalmaz. KIFEJEZÉS TÍPUSAI 1.
elsődleges kifejezés
2.
üzenet kifejezés
3.
kaszkád kifejezés
1. ELSŐDLEGES KIFEJEZÉS LEHET: −
változónév
−
literál
−
(kifejezés)
2. ÜZENET KIFEJEZÉS: ez a leggyakoribb. Alakja:
fogadóobjektum üzenet_neve argumentumok Paraméterkiértékelés: −
sorrendi kötés
−
számbeli egyeztetés
−
(típus nincs)
Paraméterátadás: üzenettől függ, hogy mi kerül átadásra. Az objektum vagy az objektum állapota kerül átadásra. −
Értékadás: A következő üzenetkifejezéssel írható le a Smalltalkban:
x := 5. x
: fogadó objektum (változó)
:=
: szelektor (üzenet)
5
: argumentum
Ez egy üzenet kifejezés. Minden osztályban létezik ez a módszer (:=). A változó egy objektumazonosítót tartalmaz. Ezen objektum “értéke” állítódik be – egy megadott állapotba kerül. Hatására az x változó értékét állítja be arra a referenciára, amely az 5-t, mint a SmallInteger osztály példányát címzi. Általánosan:
változónév := utasítás.
169
SMALLTALK
−
Return kifejezés:
^utasítás : Ez egy olyan üzenet, aminek nincs fogadója. Az objektum értékére, állapotára való hivatkozást jelenti. Az utasítás értékével tér vissza. Példa: egy return kifejezésre:
x := x + 1.
(:=
kulcsszavas üzenet)
^x. x : fogadó :=
: szelektor
x + 1: paraméter Ki kell értékelni a paramétert, ami egy üzenetkifejezés, ahol:
x : fogadó + : szelektor 1 : argumentum De ugyanazt adja a következő is:
^x := x + 1. A
^x + 1. is ugyanazzal az értékkel tér vissza (x-nél egyel nagyobbat), de x értéke nem változik. − Tömbliterálok osztályában létezik egy módszer az index_módszer:
#(1 2 3 4 5) at: 2. A 2 index argumentum. Az at: kulcsszavas üzenet. Fogadó objektum: bármely objektum lehet, amely ismeri ezt a módszert. A módszer visszaadja a második értéket, mint literálobjekumot. 3. KASZKÁD KIFEJEZÉS: ugyanahhoz a fogadó objektumhoz több üzenetet akarok küldeni, anélkül hogy mindig felírnám a fogadó nevét. Alakja:
fogadó üzenetek (üzenetkifejezés_lista pontosvesszővel elválasztva)
170
SMALLTALK
KIFEJEZÉSEK KIÉRTÉKELÉSE Prioritási sorrend van az üzenetek három csoportja között: 1. unáris 2. bináris 3. kulcsszavas Egy kifejezés tetszőleges bonyolultságú lehet. Tetszőleges sok üzenet lehet egy kifejezésben. A balról-jobbra szabály érvényes. Egy kifejezésen belül kiértékeljük az összes unáris üzenetet balról-jobbra, aztán az összes bináris, majd a kulcsszavasok, a felsorolás sorrendjében. A kiértékelést ()-kel szabályozhatom. Egyértelmű a kiértékelés. 1. UNÁRIS ÜZENETEK: A not unáris üzenet:
(3 > 2) not
”De a 3>2 not ROSSZ!”
(3 > 2): Boolean példány : üzenet (argumentum nélküli)
not
2. Bináris üzenetek (KIFEJEZÉSEK): −
Aritmetikai üzenetek: a Number osztály módszerei. Operátorok:
+
: összeadás
-
: kivonás
*
: szorzás
/
: osztás
// : egészosztás \\ : maradékképzés Példa:
4/3
Literál, a Fraction osztály egy példánya.
4 / 3 Üzenet kifejezés. Keletkezik egy olyan objektum, amelynek értéke: 4/3. −
Hasonlító operátorok: a Magnitude osztály módszerei:
<
: nagyobb
>
: kisebb
=
: egyenlőség, két objektum értékének azonossága
<=
: kisebb vagy egyenlő
>=
: nagyobb vagy egyenlő
==
: két objektum azonossága, referenciában való egyenlőség (ugyanaz az objektum)
171
SMALLTALK
Eredményük mindig egy olyan objektum, amely a True vagy a False osztály egy példánya.
x := $A < $a. ñ
ñ
kulcsszavas bináris
−
Logikai bináris üzenetek: A Boolean osztály példányain értelmezett. &
: és
|
: vagy
Teljes kiértékelés van, nincs rövidzár kiértékelés.
BLOKK Olyan objektum, amely végrehajtható kódot tartalmaz. −
Utasításokat tartalmaz.
−
Elhelyezhető módszerek törzsében.
−
Egymásba skatulyázható.
−
Formálisan [ ] zárójelek között áll.
−
Tartalmazhat lokális változókat. Ezek a változók a blokk argumentumai. Nevük előtt kettőspont áll. A lokális változókat [ | között soroljuk fel: közvetlenül a [ jel után vannak felsorolva, ezután egy | jön, majd az utasítások.
| ] között utasítássorozat áll.
VEZÉRLÉSI SZERKEZETEK FELTÉTELES VÉGREHAJTÁS:
ìifTrue: ü Boolean_példány í ý blokk îifFalse:þ A Boolean_példány a fogadóosztály. A blokk argumentum nélküli. Az ifTrue és az ifFalse a Boolean osztály egy-egy módszere: üzenet. Példa:
a < b ifTrue: [max := b]; ifFalse: [max := a]. ”kaszkád üzenet”
172
SMALLTALK
Ezek kulcsszavas üzenetek. Két egymástól független módszer. Ha a módszereket egymás után alkalmazni akarom, vannak olyan módszerek, amelyek a kettőt összevonják. Ez kiváltja a kaszkád üzenetet. Egyetlen objektumra több üzenetet alkalmazok. RÖVIDZÁR LOGIKAI MŰVELETEKET REALIZÁLÓ ÜZENETEK:
ìand :ü Boolean_példány í ý blokk îor: þ
"Logikai objektumot eredményez."
Boolean_példány not CIKLUSOK:
Integer_példány timesRepeat: blokk Integer_példány -szor hajtja végre a blokkban lévő kódot. A blokk argumentum nélküli.
KEZDŐFELTÉTELES CIKLUSNAK MEGFELELŐ KONSTRUKCIÓ:
ìwhileTrue: ü Boolean_példány í ý blokk îwhileFalse:þ A blokk argumentum nélküli. Példa:
x := 5. y := 0. [y <= x] whileTrue: [y := y + 1] ELŐÍRT LÉPÉSSZÁMÚ CIKLUSNAK MEGFELELŐ KONSTRUKCIÓ: ELÖLTESZTELŐ
Number_példány to: Number_példány by: Number_példány do [:v | utasítások] Például:
k to: v by: l do: [:cvkód] k, v, l (kezdet, vég, lépésköz) :a Number osztály egy-egy példánya. Ha l-et nem adjuk meg, annak értéke alapértelmezés szerint 1.
cv : ciklusváltozó kód :ciklusmag
173
SMALLTALK
Példa:
s := 0. 1/2 to: 1 by: 1/8 do: [:i s := s + i]. ^s. Példa: A magánhangzókat kicsire, a mássalhangzókat pedig nagyra változtatja a sztringben.
string := ’Ez a sztring.’. index := 1. string size timesRepeat:
"a size egy üzenet"
[c := string at: index. "a megfelelő indexű elem helyettesítése"
string at: index put: (c isVowel
ifTrue:[c asUpperCase]; ifFalse:[c asLowerCase]).
index := index + 1]. ^string. A string as: index a string tömb megfelelő értékéhez való hozzáférést teszi lehetővé. A tömbindex mindig 1-től indul. Az asUppercase és az asLowerCase unáris üzenet. Példa: Hány magánhangzó van egy adott sztringben?
m := 0. s := ’No ebben mennyi van?’. v := s size. 1 to: v do: [:ic := s at: i. c isVowel ifTrue: [m := m + 1]]. ^m. A Smalltalk is algoritmikus nyelv. Rengeteg művelet implementálva van üzenet szinten.
OSZTÁLYOK Az osztályok definiálása (az osztályhierarchiába való elhelyezése) interaktív módon, az eddigi osztályokhoz kapcsolódóan történik. Önálló osztályok nem léteznek, csak osztályhierarchia. A Smalltalk egy integrált fejlesztői környezetet ad, és ebben több lehetőség van új osztály létrehozására. Az új osztály szuperosztályához új példány-, osztály- és szótárváltozókat, új módszereket definiálhat, és módszereket újraimplementálhat. Itt kódírás következik, majd a módszerek lefordítása, majd tesztelése. Minden egyes osztály a MetaClass osztály példánya. Egy osztály így jön létre. A MetaClass osztály alosztálya az Object osztálynak.
174
SMALLTALK
Új osztály definíciója:
szuperosztály_név SUBCLASS: #név
”A #név egy szimbólum.”
Példányosítani minden osztályban a megfelelő osztályhoz küldött new üzenettel lehet. Az adott változó értékét az új példány OID-jére állítom.
változónév := osztálynév NEW. A változók automatikus kezdőértéke nil, amely az UndefinedObject osztály példánya.
MÓDSZEREK A módszerek definíciója három részből áll (a második elmaradhat) a Smalltalk terminológiája szerint: −
interfész
−
lokális változók
−
kód
1. AZ INTERFÉSZ RÉSZ: Megfelel a korábbi alprogram specifikációnak. Szerkezete:
név argumentumok [név argumentumok]... . −
Ha a név kettősponttal zárul, akkor ezek a módszerek beállító üzenetek. Ekkor argumentumok szükségeltetnek, minimum 1.
−
Ha nincs kettőspont, akkor lekérdező módszer. Itt általában tilos argumentumot megadni.
−
A formális paraméterek, az argumentumok lokális változói szerepkörben vannak. Ha több nevet adok meg, nem kell kaszkádolni, felsorolhatóak.
2. ADATRÉSZ: Az interfész után | | között szerepelnek a lokális változók, amennyiben vannak. Ez az adatrész, ahol a lokális változókat sorolja fel. 3. KÓD: Utasítások sorozata. A módszer visszatérési értékét egy ^üzenet realizálja. Ez megfelel egy return kifejezés C utasításnak. A módszerek neve kisbetűvel kezdődik, ha beállító módszer, akkor a végén ott a kettőspont.
175
SMALLTALK
Definiáljuk például az Object osztályhoz kapcsolódóan a Szemely osztályt.
Object subclass: #Szemely Példányváltozói: −
nev
−
cim
−
telefonszam
Módszerei lehetnek például a következők: nev ”lekérdező módszer, unáris” ^nev
nev: egyNev ”beállító, kulcsszavas módszer” nev := egyNev cim ^cim cim: egyCim cím := egyCim Változók és módszerek nevei lehetnek azonosak, mert a pozíciójuk egyértelműen eldönti, hogy melyikről van szó. A Smalltalk kifejezetten javasolja is. OO körökben kódolási szabálynak tekinthető, hogy ha osztályról és példányáról vagy változóról és értékéről van szó, akkor a következő kódolási konvenció érvényes:
egySzemély (angolul: aPerson ) nev: egyNev
cim: egyCim
self nev: egyNev. self cim: egyCim. A self az aktuális példányt, mint objektumot jelenti a Smalltalkban. Az intrefész részt nem zárjuk ponttal, csak az utasítást. Ezek után a következő utasítások alkalmazhatóak (példányosítás):
Valaki := Szemely new. Valaki nev: 'Kovács Jenő'. Valaki nev.
176
SMALLTALK
Az Integer osztálynak van egy factorial nevű módszere:
factorial self > 1 ifTrue: [^(self - 1) factorial * self]. self < 0 ifTrue: [^(self error: 'negative factorial'] . ^1. Példa: Egy sztring számmá konvertálásának a kódja a következő (egy egész vagy valós számot tartalmazó sztringet számmá konvertál)
ConvertToNumber: aString |subStrings whole decimal exponent| subStrings := aString subStrings: $. . whole := (subStrings at: 1) asNumber. subStrings size = 1 ifTrue: [^whole] ifFalse: [decimal := subStrings at: 2. (decimal includes: $e) ifTrue: [subStrings := decimal subStrings: $e. exponent := (subStrings at: 2) asNumber. decimal := subStrings at: 1] ifFalse: [exponent := 0]. ^(whole + (decimal asNumber / (10 raisedTo: (decimal(size))))*(10 raisedTo: exponent) asFloat]. Beszélő üzenetnevek használata javasolt. A sztringet egydimenziós tömbként tekinti, megnézi, hogy van-e benne pont, vagy exponens rész. A kollekciókról nem szólunk, de a Smalltalk ismeri a következőket: halmaz, multihalmaz, tömb, lista, rendezett lista, táblázat.
A DEKLARATÍV NYELVEK OSZTÁLYA Nem algoritmikus nyelvek. Amikor programot írok, nem azt mondom meg, hogy hogyan csinálja, hanem azt, hogy mit csináljon. A nyelvi rendszerbe van beépítve az a mechanizmus, ami a kérdéseket megválaszolja. A feladat megoldásához szükséges környezetet nekem kell definiálnom. Természetesen ezek is magasszintű programnyelvek. Általában igaz, hogy az imperatív nyelvekkel szemben ezek nem utasításszerkezetűek, bizonyos fogalmakat részben átvesznek, viszont a változó nem azért van, hogy az algoritmus segítségével a változó értékét módosítsam. Ezek a nyelvek jellegüknél fogva általában interpreteresek, illetve összemosnak interpreteres és egy fordítóprogramos technikát, de az alapelv az interpereteres technika. Ezen kívül interaktívak.
177
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA Ezek voltak az általános jellemzők.
LOGIKAI NYELVEK: PROLOG A logikai nyelvek legjelentősebb képviselője. '72-ben születik meg. A szoftverkrízisre adott egyfajta válasz, irányzat. Franciaországban születik. A ’80-as években több logikai nyelv születik. Mindegyiknek az alapja a matematikai logika valamely irányzata. Az elsőrendű predikátumkalkulusra épül fel a Prolog. A Prolog vissza fog köszönni a mesterséges intelligenciakutatásban, alapvető szerepet játszik. Az adatbázis-kezelő rendszerekben is felbukkan, pl. deduktív adatbázis-kezelő rendszerekben is fontos. Magyarországon a '80-as években kifejlesztenek egy Prolog rendszert, az MProlog-ot. Ez azon kevés termékek közé tartozik, amely szerepet játszik a világon (a másik az Ada). A Japánok az ötödik generációs gép nyelveként ezt választották. A logikai programozás szemlélete: −
A Prolog nem típusos nyelv.
−
Karakterkészlete a szokásos.
−
A Prolog elemi objektumai (alap építőelemei) a következők: −
Azonosítók: nem azonos az imperatív nyelvek azonosító fogalmával, hanem tetszőleges karaktersorozat lehet. Nincs olyan gond, hogy magyarul írom vagy nem.
−
Numerikus konstansok: A szokásosak.
−
Karakterlánc (sztring), idézőjelben áll.
−
Elhatároló jelek: pl. a kerek zárójelek, idéző jelek ...
−
Éles különbséget tesz kis és nagybetűk között.
−
A Prolognak is van változó fogalma. A változónak van neve, amely egy speciális azonosító, amely nagybetűvel vagy aláhúzásjellel kezdődik, és betűvel vagy számjeggyel folytatódhat. Nincs attribútuma. Van értéke, de: egy változó két állapotban lehet: −
nincs értékkomponense, ekkor lehet értéket adni neki
−
van érték komponense, nem lehet ekkor értéket adni neki, a változó értéke nem felülírható. Nem létezik az s:=s+1 utasítás. Ez az egyszeres értékadás szabálya. Át kell vinni abba az állapotba, hogy ne legyen neki értéke, és csak ekkor adhatok értéket.
Deklaratív jelleg: A változó értékének a beállítása elsősorban a rendszer feladata. Bizonyos esetekben a programozó is beállíthatja a változók értékét, de ez az eszközrendszer minimális. A változónak címe van, de ehhez a programozó nem férhet hozzá. Van még egy érdekessége a változónak: szemben az imperatív szemlélettel a teljes szövegben ugyanazt az értéket jelenti, ha egyszer értéket kapott. A deklaratív nyelvek általában szimbolikus nyelvek, ami a következőt jelenti: ha egy imperatív nyelvben leírtam egy változó nevét, akkor az általában a címet vagy értéket jelentette, elvétve a típust vagy nevet. A deklaratív
178
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
nyelvekben Xyz vagy az objektum neve, vagy attól függően, hogy milyen szövegkörnyezetben van azt a szimbólumsorozatot jelenti, amit leírtam: Xyz. A Prologban: a változó csak a nevet, mint karaktersorozatot jelenti, ha nincs értéke a változónak. Ha van értéke, akkor vagy szimbólumsorozat, vagy a változó értékét jelenti. Ha Prologban programot írok, akkor a matematikai logikában kell gondolkodnunk: meghatározom azt a környezetet, ahol le akarom futtatni. Alapvető eszközök: −
a tények
−
a szabályok
−
a feladat.
A TÉNYEK igaz értékű állítások. A tények alakja formálisan:
név(argumentumok). −
név: azonosító, mint elemi objektum
−
argumentumok: ()-ben, egymástól vesszővel elválasztva. Egy argumentumnak illik lennie, több lehet. Szemléletében tények megadásakor az argumentumokra vonatkozó igaz állításokat sorolom fel. Az argumentumok összetett objektumok lehetnek. (összetett objektumok fogalma ld. rövidesen)
−
ponttal zárja le
A SZABÁLYOK: az elsőrendű predikátumkalkulus következtetési szabályai, logikai formulák. Van a szabályoknak feje és törzse. Alakja:
fej:-törzs. −
A fej szerkezete megfelel a tények szerkezetének.
−
A törzs pedig egy feltételsorozat, vesszővel elválasztva. Ezek a feltételek éssel vannak összefűzve. A feltételek összetett objektumok (majd beszélünk róla). Ez attól egy következtetési szabály, hogy ha igaz a törzs, akkor igaz a fej. A tények törzs nélküli szabályoknak tekinthetők.
A FELADAT vagy KÉRDÉS: A rendszer a megadott környezetben keresi azokat az objektumokat, amelyek igazzá teszik a kérdést. Formálisan a kérdés úgy néz ki, mint egy fej nélküli szabály:
:-feltételsorozat. A szabályok és tények az imperatív szemlélet szerint alprogramoknak tekinthetők: elsősorban eljárásnak.
A Prolog programozás nem más, mint megadjuk először a tényeket, majd utána a szabályokat, ezzel
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
179
definiálom a megoldandó feladat környezetét. Végül feltesszük a kérdést:
:-feltételsorozat. Keresse meg a rendszer azokat a megoldásokat, amelyek ezt a feltételsorozatot igazzá teszik. A Prolog a feladatot mintaillesztéssel és backtrack-kel oldja meg. Alapvető a felsorolás sorrendje: tények, szabályok, feladat. Nézzünk egy feladatot:
apja(jános, ferenc). apja(ferenc, péter). Itt van két tény, mindkettőnek két argumentuma van, két azonosító. Ilyen értelemben szimbólumsorozat. Ami mögötte van, igaz állítások, a Prolog szempontjából tények. Nézzünk egy következtetési szabályt:
nagyapja(X,Z) :- apja(X,Y), apja(Y,Z). X, Y, Z változók. Logikailag annyit jelent, hogy X nagyapja Z-nek akkor, ha X apja Y-nak, és Y apja Z-nek. Felteszünk egy kérdést:
:-nagyapja(Valaki, péter). Keressük azon objektumokat, amelyekre igaz, hogy mindegyikük péter nagyapja. Hogyan válaszolja meg a Prolog ezt a kérdést? Mint már említettük: mintaillesztéssel: a karaktersorozatokra vonatkozóan. Abszolút a szimbolikus szinten vagyunk. A következőkképpen jár el a Prolog rendszer: −
Nekiesik a feladatoknak, és veszi a kérdés első feltételét, mint karaktersorozatot
−
Ezek után veszi a tényeket a felírás sorrendjében és a feladat első feltételét próbálja illeszteni a tényekhez. Először az első tényhez. (Jelen esetben nincs illeszkedés.)
−
Ha nem sikerül karakterről karakterre illeszkedést találni, akkor megnézi a rendszer, hogy van-e változó. Ha van változó, akkor a rendszer értéket ad a változónak. Most már nem a nevével, hanem az értékével hasonlít a rendszer. Két válasz lehetséges: −
Ha talál illeszkedést, a feltételt a változó helyettesítésével olyan formára hozta, hogy megegyezik a ténnyel. A rendszer a feladat törzséből törli az adott feltételt. Megyünk tovább a második feltétellel.
−
Ha semmilyen változó helyettesítéssel nem sikerült ténnyel egyezést találni, veszi a szabályokat a felírás sorrendjében, és az illesztést a szabály fejével játssza el. Két eset lehetséges: −
Nem talál egyezést. A rendszer megnézi, hogy van-e változó. Ha van változó, akkor a rendszer értéket ad a változónak, és a kapott értékkel hasonlít.
−
Talál egyezést, azaz a szabály feje megegyezik a feltétellel. Ez a feltétel igaz, ha a megfelelő
180
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA szabály törzse igaz. Az adott feltételt helyettesíti a feladatban az illeszkedő szabály törzsével. Ha a ténnyel illeszthető, akkor csökken a törzs. Ha szabállyal illeszthető, akkor nőhet a törzs.
−
Ha elfogynak feltételeim, kiürül a feladat törzse, megvan az első megoldás. Minden eredeti feltételt sikerült igazzá tennem. A kérdésre a válasz igaz, és a megoldást a feladatban szereplő változók értéke adja.
Feladatunk esetén:
X ← Valaki Z ← péter Találtam egy szabályfejet, ami közvetlenül nem egyezik meg, de a változó egy helyettesítésével igen. Be kell másolnom a szabály törzsét, úgy, hogy a szabály törzsben szerepelnek azok a változók, amelyeknek értéket adtam. Makrózás.
:-apja(Valaki,Y), apja(Y,péter) -vé alakul a feladat törzse. Vesszük az első feltételt: közvetlenül nem illeszkedik az első tényhez, de változó helyettesítéssel igen.
Valaki ← jános Y ← ferenc Így az illeszkedés fennáll. Találtam egy illeszkedő tényt, törlöm az első feltételt. A feladat törzsében marad:
:-apja(ferenc, péter) Az elsővel nem egyezik, a másodikkal igen. :- . Kiürült a feladat törzse. A megoldás jános. Tehát jános
péter nagyapja. Ez az első megoldás. Mi van, ha több megoldás érdekel? Mi van, ha felcserélem a két tény sorrendjét? Ekkor nem tudjuk kiüríteni a feladat törzsét, zsákutcába jutunk. Mi van akkor, ha a feladat törzse nem ürül ki ? Ekkor jön a backtrack.
Backtrack(visszalépés): Törli az utolsó illesztés hatását, ha az utolsó illesztésnél volt változó helyettesítés, akkor a változó értékét megszünteti, visszalép, és próbál új illesztést keresni. Ehhez, mint bármely illesztéses algoritmushoz hozzá lehet rendelni egy illesztési fát. Tegyük fel a
:-nagyapja(A,B). kérdést az előző környezettel. Tehát:
apja (jános, ferenc). apja (ferenc, péter). nagyapja(X,Z) :- apja(X,Y), apja(Y,Z). :-nagyapja(A,B)
.Mikor mondja a Prolog, hogy megvan az összes megoldás? -
181
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA Ekkor az illesztési fa a következőképpen néz ki: −
gyökérelem az induló kérdés
−
éleit címkézzük a változó helyettesítésekkel
−
a további csomópontokban ott szerepel a kérdés aktuális állapota egy illesztés után
:-nagyapja(A,B) X ← A, Z ← B :-apja(A,Y),apja(Y,B) A ← ferenc Y ← péter :-apja(péter,B) zsákutca
A ← jános, Y ← ferenc :-apja(ferenc,B) B ← péter :- kiürült, tehát van megoldás.
További megoldások keresése visszalépéssel (backtrack). Azon levélelemeknél van megoldás, ahol a feladat törzse kiürült, egyébként zsákutcába futottunk. Az adott úton szereplő változók értékei képezik a feladat megoldást: A ← jános, B ← péter. Ez a megoldásfa nem létezik eleve, fel kell építeni, majd utána bejárni. A Prolog nem egyenlő a fabejárással. Az összes megoldást akkor találjuk meg, ha a backtrack véget ér. Alapvetően a Prologban a szabályok rekurzívak.
Összetett objektumok (kifejezések) −
A kifejezés operandusokból, operátorokból áll. Viszont a Prolog operandusainak a köre jóval szélesebb, mint az imperatív nyelveké. Van itt nulla-, egy-, kétoperandosú és többoperandusú operátor.
−
Az operandus lehet elemi objektum vagy változó.
−
Az operandusok és bizonyos operátorok együtt összetett objektumokat (kifejezéseket) alkotnak. Az összetett objektumok a kifejezések.
−
A Prolog tudja kezelni a kifejezés mindhárom alakját: a pre-, in- és postfix alakot.
−
A feltétel nem más, mint egy kifejezés.
−
Kiértékelésnél a balról-jobbra szabály érvényes.
−
A Prolog rendszer ismerete a beépített operátorok ismeretét jelenti.
−
A programozó is definiálhat saját operátort.
−
A Prolog beépített operátorainak a egy része az imperatív nyelvekből ismert operátorokkal egyezik meg. Vannak: −
aritmetikai operátorok: +, - , *, /, div, mod. Ezek bizonyos szituációkban a megszokott módon viselkednek (aritmetikai operátorok), néha szimbolumként. Például: létezik egy IS kétoperandusú operátor, melynek baloldalán állhat egy változó neve,
182
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA jobboldalán pedig egy a fenti operátorok segítségével felépített kifejezés áll. Ekkor ez egy aritmetikai operátor: −
Ha a változónak nincs értéke, akkor a változó felveszi az IS jobboldalán álló aritmetikai kifejezés értékét. Ez azon ritka esetek közé tartozik, amikor a programozó állítja be egy változó értékét (egyszer!).
−
Ha a változónak van értéke, akkor illesztés történik az IS operátor két oldala közt, amire a válasz logikai értékű lesz: igaz vagy hamis.
−
szövegkezelő operátorok
−
I/O operátorok
−
tudásbázis kezelő része is van a Prolognak (deduktív adatbázis-kezelő), van kivételkezelője is, és grafikai része is van
−
Az adatszerkezetek közül a Prolog programok kezelik a: −
a listát és
−
az egydimenziós tömböt.
Például:
páros_szám(N) :- 0 IS N mod 2. Ha nem változónév áll a baloldalon, akkor egyértelműen illesztés történik. A backtrack technikát (kiértékelést) tudjuk befolyásolni a Prologban a vezérlésátadó operátorokkal. Ilyen például a vágó, vagy vágási operátor. Jele: ! jel. Például, ha csak arra vagyok kíváncsi, hogy egy feladatnak van-e megoldása vagy sem, akkor célszerű ezt az operátort használni. Ha valahol találkozik a ! operátorral kiértékelés közben, akkor elvágom a fát, és ezen felül a backtracket leállítom, az alsó részfára korlátozom. ← Ha itt elvágom, a backtrack nem lép vissza az első és második szintre.
Példa:
faktoriális (0,1). faktoriális (N,X) :-M is N-1, faktoriális (M,Y), X is N*Y. Amit eddig feltételnek hívtunk, a kifejezés. Itt egy rekurzív szabályt láthatunk. Nézzük meg, hogy ilyen környezetben milyen problémák adódhatnak.
183
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
−
Mennyi a 0!? K=1
:-faktoriális(0,K). N ← 0, X ← K.
:- Itt megvan a megoldás, de a program az összes
:-M IS 0-1, faktorialis(M,Y), K IS 0*Y.
megoldást keresi.
:-M is 0-1, faktoriális(M,Y), K IS 0*Y. esetén a nullát kezdi el csökkentgetni, így végtelen rekurzió alakul ki, mert megengedtem, hogy a program belemenjen olyan ágba, amibe nem szabadott volna. Itt a ! operátor szükségessége. Javítva tehát:
faktoriális (0,1):-!. faktoriális (N,X) :-M is N-1, faktoriális(M,Y), X is N*Y. −
Még mindig probléma van a:
: -faktoriális(0,2). esetben. Megint végtelen a rekurzió. A jó megoldás:
faktoriális(0,1):-!, X is 1. faktoriális(N,X):-M is N-1, faktoriális(M,Y), X is N*Y.
184
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
FUNKCIONÁLIS (APPLIKATÍV) NYELVEK: LISP A funkcionális nyelvek nem algoritmikus nyelvek, jellemzőjük a szimbolikusság. A funkcionális nyelvek alapeszköze a függvény ( amit az imperatív nyelvekben függvénynek hívtunk). Alapműveletek: −
rekurzió
−
függvény-összetétel: függvényeket állítok össze más függvények segítségével.
A tisztán funkcionális nyelvek jellemzője, hogy a függvénynek nincs mellékhatása. A funkcionális nyelvek egy része: −
nem típusos (a többség)
−
másik része típusos
A funkcionális nyelvek alapvetően interpreteresek, tehát interaktívak, de együtt létezik az interpreteres és fordítóprogramos technika is. Egy funkcionális nyelvi rendszer beépített függvények sokaságát nyújtja alaphelyzetben a
programozónak. A programozás abból áll, hogy
függvényeket definiálnunk a korábbi
függvények segítségével, ezek a rendszer részei lesznek, és megkülönböztethetetlen, hogy azt a függvényt a rendszer vagy a programozó definiálta. A program nem más, mint függvényhívás-sorozat.
LISP Az első, és sokáig az egyetlen funkcionális nyelv a LISP. A LISP az imperatív nyelvek tagadásaként jön létre, tagadja az Algol60-t. A mesterséges intelligencia kutatására használják. Vannak különböző verziói. Később jelennek meg más funkcionális nyelvek, és növik túl a LISP-et. Jelenleg is működik az AutoLISP implementáció. A karakter készlete a szokásos. Alapelemei: −
Atom: két fajtája van : −
Numerikus atom, amely megfelel a korábbi számkonstans fogalmunknak.
−
Szimbolikus atom (egy karaktersorozat), amely a LISP-ben a változót jelenti. A szimbolikus atomnak van (formailag az eljárás-orientált változónak felel meg): −
neve (a hagyományos LISP-ben azonosító, később tetszőleges karaktersorozat, tehát akár magyar betűk is használhatók)
−
Nincs típusa: nem típusos nyelv.
−
Van értéke, amely tetszőlegesen módosítható.
−
Címe nem hozzáférhető.
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
−
185
A LISP alkotó eleme a lista (hierarchikus lista: ld. Adatszerkezetek és algoritmusok). A LISP a LISt Processing rövidítése. A lista kerek zárójelek közé zárt atomok és listák sorozata. Például:
(
)
(4
2.6
ABC
X)
( Ez egy négyelemű lista) ((A) (ABC)) −
S_kifejezés (szimbolikus kifejezés): atomok és listák együtt. Kitüntetett S_kifejezés a −
T : logikai igaz
−
NIL: szövegkörnyezettől függően jelenthet logikai nemet vagy üreslistát.
Egy LISP program nem más, mint egy S_kifejezés sorozat. Az adatok a LISP-ben pedig S_kifejezések. A LISP megvalósítja azt a Neumann-elvet, hogy nem különböztethető meg az adat és a program, tehát ugyanaz az S_kifejezés lehet adat vagy program. Mitől funkcionális nyelv a LISP? Egy S_kifejezés kétféle szándékkal használható: −
szimbólumsorozatként akarom látni vagy
−
kifejezésnek akarom tekinteni. Ha egy S_kifejezés értékére vagyok kíváncsi, akkor a LISP formáról beszél.
S_kifejezések értéke: −
Ha az S_kifejezés numerikus atom, akkor mint literál, konstans szerepel. Például 28.
−
Ha az S_kifelezés szimbolikus atom, akkor változónak tekintjük, és a változó aktuális értéke lesz egyenlő az S_kifejezés értékével.
−
Ha az S_kifejezés lista, akkor a LISP azt tételezi fel, hogy a lista első eleme egy függvénynév, és a további elemek a függvény aktuális paraméterei. Ha az S_kifejezés lista, akkor azt egy függvényhívásnak tekinti a rendszer, ahol a függvény neve az operátor, aktuális paraméterei az operandusok. A függvények egymásba skatulyázhatók. A LISP tehát prefix alakú kifejezéssel dolgozik. A kifejezés kiértékelése balról jobbra történik.
További eszközök: −
A LISP-ben az általános elhatárolójel a szóköz és a ( ) zárójelek.
−
A függvény mindig rendelkezik visszatérési értékkel.
−
A LISP-ben vannak olyan függvények, amelyeknek van mellékhatásuk. Ezeket a LISP állfüggvényeknek hívja.
186
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA A LISP FÜGGVÉNYEI
A SETQ FÜGGVGÉNY A legalapvetőbb álfüggvény, melynek segítségével változóknak tudunk értéket adni. Alakja:
(SETQ A 28) ahol az A egy változó, és a 28 egy S_kifejezés. A függvény visszatérési értéke a második paraméterben megadott kifejezés értéke. Mellékhatás: az A paraméter értékét megváltoztatja.
ARITMETIKAI FÜGGVÉNYEK Az aritmetikai függvényekkel aritmetikai kifejezések írhatók fel. A következőkben felsorolt függvények nem egy létező LISP verzió függvényei, előfordulhat, hogy már létezik (esetleg más néven), de lehet, hogy nekünk kell megírnunk. Például:
PLUS
: összeadás, tetszőleges argumentummal (kommutatív)
TIMES
: szorzás, tetszőleges argumentummal (kommutatív)
DIFFERENCE
: kivonás, két argumentumú (kötött a helyük)
QUOTIENT
: egész osztás, két argumentumú (kötött a helyük)
EXPT
: hatványozás, két argumentumú (kötött a helyük)
MINUS
: ellentett képzés
ADD1
: a paraméter értékét növeli 1-el
SUB1
: a paraméter értékét csökkenti 1-el
ABS
: abszolút érték
MAX
: tetszőleges argumentumszámú maximum érték
MIN
: tetszőleges argumentumszámú minimum érték
Ezeknek a függvényeknek aktuális paraméterként S _kifejezés adható meg. A paraméterátadás lehet −
érték szerinti (ld. a fentiek esetén)
−
névszerinti
−
szövegszerinti.
187
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA Paraméterkiértékelésnél −
sorrendi kötés
−
számbeli egyeztetés kötött argumentumszámú függvények esetén.
A QUOTE FÜGGVÉNY Az argumentumként megadott S_kifejezést, amely egy tetszőleges karaktersorozat, szimbólumsorozatként tekinti, és azt adja vissza visszatérési értékként, azaz jelzi, hogy az érték szerinti paraméterátadást feltételező paraméterek ne érték szerint adódjanak át, hanem szimbolikusan kerüljenek átadásra. Annyira lényeges, hogy a LISP rendszerek többsége rövidíti, és a () jel is eltűnik egy argumentum esetén.
(SETQ A ‘B)
A ← B
(SETQ B ‘C)
B ← C
(SETQ C ‘(TIMES 5 2)) C ← (TIMES 5 2)
AZ EVAL FÜGGVÉNY Egy paramétere van. A paraméterként megadott S_kifejezés értékének az értékét adja. Az előbbi értékadások után: (EVAL A) eredménye a C szimbólum.
(EVAL B) eredménye (TIMES 5 2) (EVAL C) eredménye 10.
PREDIKÁTUM FÜGGVÉNYEK Az imperatív nyelvek hasonlító operátorait realizálják. Ezek a következők:
GREATERP
: nagyobb
LESSP
: kisebb
ONEP
: egyenlő-e 1-el?
ZEROP
: egyenlő-e 0-val?
MINUSP
: kisebb-e mint 0?
EVENP
: páros
ODDP
: páratlan
Valamennyi függvény P-re végződik, logikai értékkel térnek vissza. Feltételezi, hogy a paraméter értéke szám.
188
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
LOGIKAI FÜGGVÉNYEK
AND OR NOT Az AND és az OR tetszőleges argumentumú, és rövidzár kiértékelést tesz lehetővé. A NOT egyargumentumú, tagadást jelent. A mai verziók az ún. általánosított logikát alkalmazzák. A LISP verziókban van két logikai konstans, szimbolikus atom, nevesített konstans:
T
: logikai igaz
NIL
: logikai hamis vagy üres lista, szövegkörnyezettől függ, hogy mit jelent.
Általánosított logikában, ha egy operandus vagy paraméter értéke NIL, akkor az hamis, minden más esetben igaz. Példa:
(SETQ A NIL) (NOT A) → T (AND ‘KÁRÓ ‘PIKK ‘TREFF ‘KÖR) → KÖR KÁRÓ ≠ NIL
Þ továbbmegyünk
PIKK ≠ NIL
Þ tovább megyünk
TREFF ≠ NIL Þ tovább megyünk KÖR ≠ NIL
Þ visszatérési érték KÖR, ami igaz, mert nem NIL.
Példa:
(OR (AND (MINUSP N) (MINUS N)) N) N-nek rendelkeznie kell valamilyen értékkel. Legyen például: N=-5
MINUSP N = igen Þ továbbmegyünk MINUS N=5 ≠ NIL Þ AND függvény értéke 5 igaz Þ OR-t nem értékeljük tovább, igaz: 5. Legyen most: N=5
MINUSP N = hamis AND = hamis OR = 5, így a végeredmény 5. Tehát ez nem más, mint az abszolútérték függvény. Egy kifejezést balról-jobbra értékelünk ki, és a kifejezés értéke a legutoljára kiértékelt S_kifejezés értéke. Mindig visszajön valamilyen értékkel.
189
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA TOVÁBBI PREDIKÁTUMOK (logikai igaz vagy hamis értékkel tér vissza): −
ATOM: egy argumentumú, igaz, ha az argumentum atom, hamis, ha lista.
−
EQ: két argumentumú, az argumentumoknak atomoknak kell lenniük, és az atomok azonosságát vizsgálja.
−
NULL: igaz, ha az argumentum értéke hamis, egyébként hamis. Általánosított logikában az NULL=NOT.
−
EQUAL: két tetszőleges S_kifejezés értékét hasonlítja össze.
−
NUMBER: egy argumentumú, igaz, ha az argumentum szám, hamis, ha nem az.
NÉHÁNY LISTAKEZELŐ FÜGGVÉNY Alapfüggvények minden implementációban: −
CAR a lista fejét (első elemét) adja vissza. A visszatérési érték atom vagy lista.
−
CDR a lista farkát (az első eleme kivételével minden elemet) adja vissza, ez pedig mindig lista. Ezek argumentuma lista; csak nem üres listákra alkalmazhatók.
Vagy megvannak, vagy megírhatók könnyen a lista alapműveleteit megvalósító függvények: −
CAADR: (A = fej, D = farok): a lista fejének a fejének a farkát adja. Így tetszőleges elemet el lehet érni.
−
CONS: két paramétere van. Az első tetszőleges S_kifejezés lehet, a második lista. Az első paraméterként megadott S_kifejezés értékét, mint fejet a második paraméterként megadott lista elé fűzi. Nem más, mint a lista bővítése fejnél.
−
APPEND: két listát összefűz.
−
LIST: tetszőleges számú argumentuma lehet, az argumentumokban megadott elemek értékéből listát képez.
PROGRAMOZÓ ÁLTALI FÜGGVÉNYDEFINÍCIÓ
DE: saját függvényeket lehet vele definiálni. Paraméterei: a saját függvény neve, ( )-ben szimbolikus atomok szóközzel elválasztva, mint a saját függvény formális paraméterei és S_kifejezések, amelyek a függvény törzsét jelentik.
(DE név(p1 p2 ... pn)
S_kifejezés1... S_kifejezésm)
Olyan függvényt definiáltunk, ami: −
Fix paraméterszámú.
−
A paraméterátadás érték szerinti.
−
A paraméterkiértékelésnél:
−
−
sorrendi kötés
−
számbeli egyeztetés van (típus nincs).
A pi (i=1..n) az adott függvény lokális változói, szimbolikus atomok, formális paraméterek, amelyek az aktuális paraméterként megadott S_kifejezés értékét veszik fel. Opcionálisak: lehet paraméter
190
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA nélküli függvényt is írni.
−
A törzs végrehajtása balról-jobbra történik. A függvény értéke az utolsó S_kifejezés értéke lesz. Saját függvényeket definiálhatok interaktív módon.
−
A LISP megengedi a függvény törzsében a globális változók használatát.
(DE MELLÉKHATÁS (N)) (SETQ N (TIMES N 2)) (SETQ M N)) Tehát tudunk álfüggvényt definiálni, amely megváltoztatja a környezetét. N formális paraméter, lokális változó. Hatásköre ez a függvénytörzs.
M szabad változó. Hatásköre dinamikus: lépked kifele a hívási láncon. Ha M-t használtam valahol egy külső függvényben, akkor M globális változó. Ha nem, akkor sincs gond, ui. egy változó onnantól kezdve él, mikortól értékét rögzítettük programból vagy interpreter szinten. Mennyi lesz M értéke a következő esetben?
(DE MELLÉKHATÁS(N) (SETQ N (TIMES M 2)) (SETQ M N)) Egy LISP rendszerekben statikus és dinamikus környezet-meghatározás is lehetséges. A függvény definíció lefordítódik: − Statikus környezet-meghatározás, akkor történik, ha a globális változó értéke a definiáláskor meghatározódik. Azt az értéket tekintjük kezdőértéknek, amivel a változó definiáláskor rendelkezett. − Dinamikus estben a hívási környezettől függően történik az értékmeghatározás. Az M értéke az az érték, amivel a függvény hívásakor rendelkezett. Más-más hívás esetén M értéke más lehet.
FELTÉTELES ELÁGAZÁS
COND :a legalapvetőbb függvény. Argumentumai két elemű listák. A feltételes elágazás megvalósítására szolgál.
(COND (feltétel1 S_kifejezés1) (feltétel2 S_kifejezés2) ... (feltételn S_kifejezésn)) A feltételi értéke logikai.
191
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
A felsorolás sorrendjében keresi az első nem NIL értékű feltételt, kiértékeli az igaz feltétel mögött található
S_kifejezést, és ennek értéke lesz a függvény visszatérési értéke. Ha minden feltétel NIL, akkor a függvény értéke is NIL lesz. Az ELSE ág úgy valósítható meg, hogy az utolsó feltétel helyett egy T-t írunk. A későbbi verziók, az általános logikát használó rendszerek megengedik, hogy több S_kifejezés álljon egymás után egy feltétel mögött. Ilyenkor (feltétel
S_kifejezés_sorozat) alakúak az
argumentumok, és S_kifejezés nélküli ág is előfordulhat. Példa:
(DE FAKT(N) (COND ((ZEROP N) 1) (T (TIMES N (FAKT (SUB1 N > A > az összes nyitva maradt zárójelt automatikusan bezárja. A szokásos imperatív faktoriális számítás a LISPben (eljárásorientált szemlélet). A COND és rekurzió szerepel benne hibavizsgálat nélkül. Más megoldás (ami a LISP szemléletet tükrözi):
(DE LISTN(N) (COND ((ZEROP N) (LIST 1)) (T(CONS N (LISTN (SUB1 N >
(DE FAKT(N) (EVAL (CONS ‘TIMES (LISTN(N > Példa arra is, hogy végrehajtjuk egy adat (függvény) végeredményét. Készít egy, olyan listát, amelynek az elemei 1..N, és ezeket összeszorozzuk.
Példa: A lista elemeinek sorrendjét megfordító függvény: (DE LISTF (LIS) (COND ((NULL LIS) NIL) (T (APPEND (LISTF (CDR LIS)) (LIST (CAR LIS> Rekurzív függvény. Az üres lista fordítottja önmaga, egyébként a lista fejét a farka végéhez rakjuk, miután a farkát megfordítottuk.
192
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
Példa: Rendező függvény: Tegyük fel, hogy a függvény paraméterként megkap egy atomokból álló listát. Ezeket fogja rendezni szimbolikusan. Az atomok, mint szimbólumok összehasonlítására létezik egy ALPHORDER nevű predikátum.
(DE RENDEZ (SZAVAK) (COND ((NULL SZAVAK) NIL) ((NULL (CDR SZAVAK)) SZAVAK) (T BESZÚR (CAR SZAVAK) (RENDEZ (CDR SZAVAK > (DE BESZÚR (SZÓ SOR) (COND ((NULL SOR) (LIST SZÓ)) ((ALPHORDER SZÓ (CAR SOR)) (CONS SZÓ SOR)) (T (CONS (CAR SOR) (BESZÚR SZÓ (CDR SOR > Beszúró rendezést játszunk el. −
Ha a sorozat üres, akkor az rendezett.
−
Ha egyelemű, akkor is rendezett.
−
Különben: −
Ha a beszúrandó elem kisebb a lista fejénél, akkor az elejére szúr be.
−
Egyébként leválasztjuk a fejet, beszúrunk a rendezett farokba.
ÁLLOMÁNYKEZELÉS A LISP rendszerek minimális állománykezelési eszközrendszerrel rendelkeznek. Kezel egy szeriális állományt, ismeri az implicit állomány fogalmát. A file-műveletek függvényként vannak realizálva. Megnyitás: funkcióra nyit.
ìINPUT ü (OPEN ’állomány_név í ý) îOUTPUTþ Bezárás: (CLOSE ’állomány_név) A LISP filozófiája szerint tetszőleges számú állomány nyitva lehet, és az írás (olvasás) mindig az utoljára megnyitott állományba (-ból) történik. Ha input állománynál elérjük az állomány végét, akkor a rendszer automatikusan áttér az időben utoljára megnyitott állományra, és ha mind elfogyott, akkor az implicit állományra tér át, ami általában mindig van.
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
193
Néhány I/O függvény: − READ: Szimbolikus olvasás, egy S_kifejezést olvas be, és ezzel az S_kifejezéssel tér vissza. −
READC: karakterenként olvas.
−
PRINT: megadott S_kifejezést kiértékel, és kiírja az értékét. Van ennek más változata is, ami nem ír sorvége jelt.
Van egy speciális eszköz, ami csak írást realizáló függvényekben használható: a sztring, ami ” ” közötti karaktersorozat. A LISP ezt szimbolikusan kezeli. Az I/O a LISP rendszerek leginkompatibilisebb része.
ITERATÍV JELLEGŰ FÜGGVÉNYEK Rekurzió kontra iteráció: a rekurzió elegáns de helyigényes, viszont az iteratív eszközök jóval gyorsabb megoldást tesznek lehetővé. AZ ELŐÍRT LÉPÉSSZÁMÚ CIKLUST MEGVALÓSÍTÓ FÜGGVÉNY
(RPT S_kifejezés1 S_kifejezés2) Az S_kifejezés1 számértékű (nemnegtív egész) kifejezés, míg az S_kifejezés2 tetszőleges értékű. Működése: Kiértékeli az S_kifejezés1 –t, és az S_kifejezés2 –t S_kifejezés1 –szer kiértékeli, és az utoljára kiértékelt S_kifejezés2 értékével tér vissza.
KEZDŐFELTÉTELES CIKLUST MEGVALÓSÍTÓ FÜGGVÉNY
(WHILE S_kifejezés1 ... S_kifejezésn) Az S_kifejezés1 (a kezdőfeltétel) egyes LISP rendszerekben tetszőleges értékű, másokban logikai értékű. Működése: −
Kiértékeli az S_kifejezés1 –t.
−
Ha S_kifejezés1 ≠ NIL, akkor kiértékeli a többi S_kifejezés –t, majd újra az S_kifejezés1 –t.
−
Akkor fejeződik be, ha S_kifejezés1 = NIL lesz, és NIL-lel tér vissza.
194
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
Példa: A lista utolsó elemét visszaadó függvény:
(DE UTOLSOELEM(X) (COND ((NULL X) NIL) ((NULL (CDR X)) (CAR X)) (T (COND ((WHILE (NOT (NULL (CDR X))) SETQ X (CDR X))) NIL) (T (CAR X >
EGY MÁSIK KEZDŐFELTÉTELES CIKLUST MEGVALÓSÍTÓ FÜGGVÉNY
(UNTIL S_kifejezés1 ... S_kifejezésn) Működése: Ha az S_kifejezés1 értéke NIL, akkor értékel, és akkor ér véget a függvény, ha az S_kifejezés1 értéke nem NIL, és ezzel az értékkel tér vissza.
„KÖZÉPFELTÉTELES” CIKLUST REALIZÁLÓ FÜGGVÉNY
(DO ((v1 S_ kifejezés1) ... (vn S_kifejezésn)) (f S_kifejezés) S_kifejezés1 ... S_kifejezésk) Első n db. paramétere kételemű listákból áll, ahol vi-k (i=1..n) szimbolikus atomok, a függvény lokális változóinak tekinthetők, és az S_kifejezési –k tetszőleges értékűek lehetnek. Ez az inicializáló rész. Az f feltétel logikai értékű kifejezés. A többi S_kifejezés tetszőleges. Működése: −
Kiértékeli az S_kifejezési –ket, és értékül adja a vi-knek (i=1..n).
−
Következő lépésben kiértékeli az f feltételt. Ha f értéke: −
NIL: egymás után kiértékeli a harmadik paramétersorozatként megadott S_kifejezésj -ket (j=1..k), majd újra kiértékeli f-et.
−
Nem NIL: kiértékeli az f melletti S_kifejezés –t, és annak értékével tér vissza.
Az ős LISP-ben is megvan ez a függvény.
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
Példa: Határozzuk meg 2n-t:
(DO
((EREDMÉNY 1) (KITEVŐ N)) ((ZEROP KITEVŐ) EREDMÉNY) (SETQ EREDMÉNY (TIMES 2 EREDMÉNY)) (SETQ KITEVŐ (SUB1 KITEVŐ >
Példa: Határozzuk meg N faktoriálist.
(DE FAKT(N) (DO
((EREDMÉNY 1) (SZÁMLÁLÓ N)) ((ZEROP SZÁMLÁLÓ) EREDMÉNY) (SETQ EREDMÉNY (TIMES SZÁMLÁLÓ EREDMÉNY)) (SETQ SZÁMLÁLÓ (SUB1 SZÁMLÁLÓ >
LÉPTETÉSES CIKLUS Az újabb verziókban benne van ez a függvény. A ciklusváltozók értékeit felülírja minden lépésben.
(DO ((v1 S_ kifejezés11 S_ kifejezés12) ... (vn S_kifejezésn1 S_ kifejezésn2)) (f S_kifejezés) S_kifejezés1 ... S_kifejezésk) Példa: Határozzuk meg 2n-t:
(DO
((EREDMÉNY 1 (TIMES 2 EREDMÉNY)) (KITEVŐ N (SUB1 KITEVŐ))) ((ZEROP KITEVŐ) EREDMÉNY))
Példa: Határozzuk meg N faktoriálist.
(DE FAKT(N) (DO ((EREDMÉNY 1 (TIMES SZÁMLÁLÓ EREDMÉNY)) (SZÁMLÁLÓ N (SUB1 SZÁMLÁLÓ))) ((ZEROP SZÁMLÁLÓ) EREDMÉNY >
195
196
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
AZ ŐS LISP LEGHÍRESEBB FÜGGVÉNYE:
(PROG (v1 ... vn) S_kifejezés1 ... S_kifejezésn) Első paramétere szimbolikus atomokból álló lista, utána S_kifejezés-ek következnek. A vi-k (i=1..n) a
PROG függvény lokális változói, kezdőértékük NIL. E függvény imperatív jellemzőket hordoz. Az S_kifejezés-ek képezik a függvény törzsét. Csak itt fodulhat elő a LISP-ben a címke, ami szimbolikus atom, és a (GO címke) és a (RETURN kifejezés). Működése: −
Ha nem szerepel a fenti (GO címke) és a (RETURN kifejezés), akkor az S_kifejezés sorozat kiértékelődik, és a függvény az utoljára kiétékelt S_kifejezés értékével tér vissza.
−
Ha szerepel a (RETURN S_kifejezés), akkor befejeződik a függvény, és visszatérési értéke ez az
S_kifejezés lesz. −
A (GO címke) hatására feltétel nélküli vezérlésátadás történik a címkére.
Példa:
(DE FAKT(N) (PROG (K L) (SETQ K N) (SETQ L 1) KÖVETKEZŐ (COND ((ZEROP K) (RETURN L))) (SETQ L (TIMES K L)) (SETQ K (SUB1 K)) (GO KÖVETKEZŐ >
LISTAKEZELŐ FÜGGVÉNYEK: Adjunk meg egy olyan függvény (predikátumot), amely eldönti egy listáról, hogy egy adott elem szerepel-e a lista legkülső szintjén. (A listák ugyanis egymásba ágyazhatók.)
(DE ELEME (ELEM LISTA) (COND ((NULL LISTA) NIL) ((EQUAL ELEM (CAR LISTA)) T) (T (ELEME ELEM (CDR LISTA>
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
197
Alakítsuk át úgy, hogy töröljük a megtalált elem első előfordulását a listából.
(DE ELHAGY (ELEM LISTA) (COND (NULL LISTA) NIL) ((EQUAL ELEM (CAR LISTA))(CDR LISTA)) (T (CONS (CAR LISTA)(ELHAGY ELEM (CDR LISTA> Általában a LISP rendszerek tartalmazzák a következő függvényeket: −
REMOVE: Törli egy adott elem minden előfordulását a lista legkülső szintjén.
−
MEMBER: egy kétargumentumú függvény, amely megvizsgálja, hogy az első paraméterként megadott elem szerepel-e a második paraméterként megadott listában. NIL-lel jön vissza, ha az adott elem nem szerepel a lista külső szintjén, egyébként, ha megtalálta az első előfordulást, akkor a második paraméterként megadott listának azt a részét adja vissza, ahonnan kezdődően az elemet megtalálta, azaz levágja a megtalált elem előtti részlistát, és ezzel a részlistával tér vissza.
Írjunk egy olyan függvény, amely megadja egy S_kifejezés legnagyobb zárójelezési mélységét.
(DE MILYEN_MÉLY (LISTA) (COND ((NULL LISTA) 0) ((ATOM LISTA) 0) (T MAX (ADD1 (MILEN_MÉLY (CAR LISTA))) (MILYEN_MÉLY (CDR LISTA>
A listából töröljük ki egy adott elem minden előfordulását.
(DE TÖRÖL (SK LIS) (COND ((NULL LIS) NIL) ((EQUAL SK (CAR LIS)) (TÖRÖL SK (CDR LIS))) ((ATOM (CAR LIS)) (CONS (CAR LIS) (TÖRÖL SK (CDR LIS )))) (T (CONS (TÖRÖL SK (CAR LIS)) (TÖRÖL SK (CDR LIS> Miden adatszerkezetet listával kell szimulálni.
198
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
HALMAZ KEZELÉSE LISTÁVAL Halmazról akkor beszélünk, ha elemei egyediek, tehát nincs ismétlődés, és nincs sorrend. A halmaz egy speciális listának tekinthető, amelyben nincsen ismétlődés, és nem vesszük figyelembe, hogy a lista egyfajta sorrendet értelmez. Atom nem halmaz. Az üreshalmaznak megfelel az üreslista. Példa: Írjunk egy olyan függvényt, amely eldönti egy listáról, hogy halmaz, vagy nem halmaz.
(DE HALMAZ_E (LISTA) (COND ((NULL LISTA) T) ((ATOM LISTA) NIL) ((MEMBER (CAR LISTA) (CDR LISTA)) NIL)
A fej előfordul-e a farokban?
(T (HALMAZ_E (CDR LISTA > Példa: Nézzük meg két halmaz egyenlőségét. A függvény két halmazt reprezentáló listát kap paraméterként, és eldönti, hogy egyenlők-e.
(DE HALMAZ_EQ (H1 H2) (COND ((NULL H1) (NULL H2)) ((NULL H2) NIL) ((ATOM (CAR H1)) (AND (MEMBER (CAR H1) H2) (HALMAZ_EQ (CDR H1) (REMOVE (CAR H1) H2))) (T (AND (HALMAZ_PÁRJA (CAR H1) H2) (HALMAZ_EQ (CDR H1) (REMOVE (HALMAZ_PÁRJA (CAR H1) H2 )H2 >
(DE HALMAZ_PÁRJA (A B) (COND ((NULL B) NIL) ((HALMAZ_EQ A (CAR B)) (CAR B)) (T (HALMAZ_PÁRJA A (CDR B >
(DE HALMAZ_ELEME (E H) (COND ((NULL H) NIL) ((ATOM E) (ELEJE E H)) (T (AND (HALMAZ_PÁRJA E H)T >
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
199
Példa: Unióképzés:
(DE UNIÓ (H1 H2) (COND ((NULL H1) H2) ((HALMAZ_ELEME (CAR H1) H2) (UNIÓ (CDR H1) H2)) (T (CONS (CAR H1) (UNIÓ (CDR H1) H2 > Példa: Metszetképzés:
(DE METSZET (H1 H2) (COND ((NULL H1) NIL) ((HALMAZ_ELEME (CAR H1) (METSZET (CDR H1) H2))) (T (METSZET (CDR H1) H2 >
LISP FÜGGVÉNYEK OSZTÁLYOZÁSA A LISP a függvényeket a következőképpen csoportosítja: − Paraméterkiértékelés szerint beszél: --
Kötött argumentumszámú függvényekről.
--
Nem kötött argumentumszámú függvényekről.
− Paraméterátadás szerint beszél: --
Eval típusú függvényekről. Ez esetben a paraméterátadás érték szerinti.
--
Nem eval típusú függvényekről. Ilyenkor a paraméterátadás név vagy szöveg szerinti.
A beépített függvényeknél láttunk kötött argumentumszámú és nem kötött argumentumszámú függvényt is. A programozó is definiálhat kötött argumentumszámú és nem kötött argumentumszámú függvényeket, illetve eval és nem eval típusú függvényeket is. Sajátfüggvényt eddig csak fix paraméterszámút tudtunk definiálni. És ezeknél a függvényeknél érték szerinti paraméterátadásról beszélhetünk. A paraméter kiértékelésnél: −
Sorrendi kötés van minden esetben.
−
Nincs típusegyeztetés, mivel a LISP nem típusos nyelv.
−
Számbeli egyeztetés: −
van, ha a függvény kötött paraméterszámú.
−
nincs, ha a függvény nem fix argumentumszámú.
200
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
NEM FIX PARAMÉTERSZÁMÚ SAHJÁTFÜGGVÉNY Nézzük meg, hogyan definiálhatunk nem kötött argumentumszámú sajátfüggvényt. A DE függvény segítségével a programozó fix paraméterszámú és érték szerinti átadású függvényeket tud definiálni, de a DE függvénynek van olyan formája, ahol a formális paraméterlista helyén egyetlen szimbolikus atom áll. Ilyenkor nem fix paraméterszámú saját függvényt definiálunk. Alakja:
(DE név szimbolikus_atom S_kifejezés1 ... S_kifejezésn) Ekkor tetszőleges számú aktuális paraméterrel hívhatom meg a függvényt. Híváskor az aktuális paraméterek (S_kifejezés -ek) kiértékelődnek, és a rendszer ezekből az értékekből csinál egy listát, és ezt a listát adja át a szimbolikus atomnak, és ezzel dolgozik. Ezek a függvényk általában nem lehetnek rekurzívak.
NEM ÉRTÉK SZERINTI PARAMÉTERÁTADÁSSAL RENDELKEZŐ SAJÁTFÜGGVÉNY A DF függvény segítségével is saját függvény definiálható. Alakja olyan, mint a DE függvényé, csak a paraméterátadás név- vagy szövegszerinti. Ez is lehet kötött vagy nem kötött paraméterszámú függvény. A rekurzió itt sem megengedett.
(DF ÚJ_AND A (COND ((NULL A) NIL) ((NULL (EVAL (CAR A))) NIL)
rövidzár
(T (EVAL (CONS ’ÚJ_AND (CDR A > Nem nevezhető rekurziónak, mert a paramétert szimbólumként adom át.
FUNKCIONÁL: A funkcionál olyan függvény, amelynek az argumentuma függvény. A LISP rendszerek tudják kezelni a funkcionálokat. Legegyszerűbb funkcionál:
APPLY (S_kifejezés lista) Az első argumentuma egy olyan S_kifejezés, amelynek értéke egy függvénynév, második argumentuma egy lista. Ezt a megadott függvényt hajtja végre a lista elemein.
201
DEKLARATÍV NYELVEK OSZTÁLYOZÁSA
Példa: Írjunk olyan függvényt, amely számokat tartalmazó listából leválogatja, és külön listába fűzi a negatívokat.
(DE NEGATÍV (SZÁMOK) (COND ((NULL SZÁMOK) NIL) (MINUSP (CAR SZÁMOK)) (CONS (CAR SZÁMOK) (NEGATÍV (CDR SZÁMOK)))) (T (NEGATÍV (CDR SZÁMOK > Példa: Egy halmazból leválogatja azon elemeket, amelyek maguk is halmazok, és azokat egy külön halmazba gyűjti, felfűzi egy listába.
(DE HALMAZ_HALMAZ (HALMAZ) (COND ((NULL HALMAZ)NIL) ((HALMAZ_E (CAR HALMAZ)) (CONS (CAR HALMAZ) HALMAZ_ HALMAZ (CDR HALMAZ)))) (T (HALMAZ_HALMAZ (CDR HALMAZ > A két függvényben közös, hogy listát kezelnek, és listából újabb listát állítanak elő. Az APPLY függvény az absztrakció egy nagyon fontos eszköze, amely segítségével az összes fenti típusú függvényt meg tudjuk írni néhány sorban. Az összes leválogatási algoritmust leprogramozhatjuk ezzel ami a LISP-ben van. Írjunk meg ezt az általánosított függvényt.
(DE AZOK (LISTA PREDIKÁTUM) (COND ((NULL LISTA) NIL) ((APPLY PREDIKÁTUM (LIST (CAR LISTA))) (CONS (CAR LISTA) (AZOK (CDR LISTA) PREDIKÁTUM))) (T (AZOK (CDR LISTA) PREDIKÁTUM >
(DE NEGATíV (LISTA) (AZOK (LISTA ’MINUSP))
(DE HALMAZ_HALMAZ (HALMAZ) (AZOK HALMAZ ‘HALMAZ_E)) Itt kezdődik a LISP programozás. Jelenleg az egyik legelterjedtebb LISP verzió az AutoLISP: az AutoCAD rendszer nyelve. A legáltalánosabbnak tekinthető a COMMONLISP. A LISP sem kerülte el a OO világot: a CLOS a COMMONLISP OO-val kibővített változata.
202
ADATFOLYAM NYELVEK (DATA FLOW)
ADATFOLYAM NYELVEK DATA FLOW Az OO paradigma pillanatnyi állapotában a legmesszebbre elviszi az újrafelhasználás és az absztrakció eszközrendszerét. A párhuzamosság kérdésére viszont nem ad választ. Az OO nyelvek egyrésze ismeri a párhuzamosságot, más része nem. De van olyan nyelvcsalád, amely specialitásánál fogva „kilóg a sorból”, amely választ ad a párhuzamosság kérdésére. Kb. kéttucatnyi nyelv tartozik ide. Ezek a Neumann architektúrát tagadják. Azt mondják, hogy a Neumann architektúra szűk keresztmetszete a tárkezelés illetve a szekvenciális működésű processzor. Egy
z=x+y jellegű utasítás mögött sok gépi szintű utasítás áll. A tárból elő kell venni az x-szel illetve az y-nal jelölt adatot stb., mindez szekvenciálisan működik. Ezt totálisan tagadja. Más hardverplattformot igényel. Azt mondja, hogy minden algoritmust párhuzamos algoritmusként kell tekinteni, és az algoritmust realizáló program
pedig
minden
párhuzamosan
végrehajtható
kódot
hajtson
végre
párhuzamosan.
Totális
párhuzamosságra kell törekedni. Ez a paradigma felerősíti a párhuzamos algoritmusok kutatásának elméletét. Gondoljunk a gyorsrendezésre: megírható szekvenciálisan, de párhuzamosan is, és ekkor az összes létező csoportra egyszerre hajtja végre a gyorsrendezést. Minden szekvenciális algoritmus átírható párhuzamos algoritmussá. (Eddig csak szekvenciális algoritmusokról beszéltünk.) Teljesen más világszemlélet. A kultúránk olyan, hogy szekvenciálisan látjuk a világot, így tanítják. Ld. az írás olvasás tipikus szekvenciális tevékenység. Holott az agyunk párhuzamos működésű. Mint már említettük, minden szekvenciális algoritmus átírható párhuzamos algoritmussá. A szekvenciális algoritmusleírás egyik eszköze a folyamatábra. Ehhez hasonlóan az adatvezérelt paradigmának is van egy leíró eszköze: egy összefüggő, irányított gráf, amelynek van egy kitüntetett kezdőpontja, vannak csomópontok, amelyek tevékenységeket írnak le és lehet több végpontja (kimeneti pontok). Egy csomópontnak egy tevékenység felel meg (operátorok), a műveletek realizálására szolgálnak. Az egyes programnyelvekben kérdés, hogy mi az operátorkészlete. Irányított a gráfról van szó, tehát a csomópontokhoz vezető- és a csomópontokból kivezető élek nyilak. Az élek mentén a nyilak irányában ún. tokenek, adatcsoportok mozognak (míg a folyamatábra statikus). Algoritmus: van egy input token, amit a kezdőponton keresztül halad az operátor felé. Az operátor (csomópont) csak akkor kezd el dolgozni, amikor a bemenő élei mindegyikén megjelenik egy token. És ha rendelkezésre áll minden adat, azonnal dolgozik vele, és a kimenő nyilak mentén kiadja az eredményt. Ez az adatvezéreltség elve. x + z y Az adatcsomagok tetszőleges adatszerkezetet reprezentálhatnak, és a műveletek tetszőleges bonyolultságúak lehetnek.
203
ADATFOLYAM NYELVEK (DATA FLOW) Vannak konvencionális jelölések a modellben: −
Egy él akárhányfelé ágazhat:
−
Kapcsolat: a gráf különböző pontjaiból kapcsolódhatnak a tokenek. Élek nem végződhetnek élen, csomópontban kell végződjenek. Szükséges egy gyűjtő.
Speciális csomópontok: −
Feladata: egy logikai értékű tokent produkál egy vizsgálat és feltételkiértékelés után.
−
Igazkapu: két bemenő- és egy kimenő éle van. A bemenő élek egyike egy logikai értékű token, a másik értéke tetszőleges. Ha a logikai token értéke igaz, akkor a másik tokent átengedi, egyébként nem lesz kimenet, a kapu lezár.
T −
Hamiskapu: hasonló az igazkapuhoz, annyi kulönbséggel, hogy hamis esetben engedi át a másik input tokent. F
−
Kiválasztó csomópont: az igaz- és hamis kapu kombinációja. Lényeges a szerkezet is: 2+1 bemenő- és egy kimenő éle van. A legelső bemenő él a vezérlő él, értéke egy logikai token. A két másik bejövő token közül kiválasztja, hogy melyiket engedi át. Ha a vezérlő token értéke igaz, akkor az első tokent teszi a kimenetre, míg ha hamis, akkor a másodikat.
T
F
204
−
ADATFOLYAM NYELVEK (DATA FLOW)
Elosztó csomópont: két bemenő tokent és két kimenő tokent tartalmaz. A bemenő tokenek közül az első, a vezérlő token csak logikai értéket hordozhat. Működése: ha a vezérlő token értéke igaz, akkor a bemenő adat token az első outputon jelenik meg, ellenkező esetben a másodikon. Egyetlen csomópont, aminek két kimenete van.
T
F
Ilyen elemekből építünk fel egy összefüggő irányított gráfot. Ezzel tudjuk leírni a párhuzamos algoritmusokat. Az adatvezérelt paradigmán belül többféle modell létezik, attól függően, hogy milyen megszorításokat teszünk a gráfra.
Modellek: −
Alapmodell: Determinisztikus, csak korlátozott párhuzamosságot enged meg. A következőket mondja: −
Az egy input tokennel indított tokensorozat hullámfrontszerűen terjedjen végig a gráfon.
−
Később indított tokensorozat nem előzhet meg korábban indított tokensorozatot.
−
A hullámfront mintegy összeköti a tokeneket, nem hajolhat el, nem értelmezhető ciklus és rekurzió. Például:
−
Denis-féle (MIT) modell: Megszorítás: követeljük meg, hogy gráfban egy csomópont csak akkor működhessen, ha nincs az output élen token. Két hullámfront között mindig legyen operátor. A hullámfrontot eltoljuk legalább egy csomóponttal. Így válik lehetővé ciklus létrehozása. Egy tokent visszaküldhetek, beküldhetem egy körútba, és addig nem engedem ki, amíg valamilyen feltételnek nem tesz eleget. Itt is igaz, hogy hullámfront nem előzhet meg hullámfrontot. De még mindig determinisztikus a modellem.
ADATFOLYAM NYELVEK (DATA FLOW)
205
Például:
−
Színezett modell: Jelenleg a legfejlettebb adatvezérelt programozási modell. Párhuzamos, így nem determinisztikus. Az egy inputtal indított tokensorozatot színezzük egy szinnel. A csomópont akkor működik, ha az azonos színű tokenek közül az összes input, azaz az összes bemenő élen azonos színű tokenek jelennek meg (addig pufferel). Adott szintű tokent ad ki. Értelmezhető az iteráció és a rekurzió. Nincs hullámfront. Ugyanazon feladatot megoldó gráfban az összes azonos jellegű feladat egyszerre megoldásra kerül. Kérdés, hogy hol van az az architektúra, ahol ez a feladat leprogramozható és futtatható? Léteznek ilyen architektúrák prototípus szinten minden modell mögött. Jellemzőjük: −
asszociatív tár
−
A program végrehajtásánál biztosítja a párhuzamosságot.
−
A tokent kezelni tudja.
−
Csomópontok leprogramozását lehetővé teszi.
206
ADATFOLYAM NYELVEK (DATA FLOW)
Példa: Az n! szinezett gráfja hibavizsgálat nélkül. 1
>1
F
T
T
*
-1
-1 : egy csökkentő cikus * : egy szorzó ciklus Ha ezek az ágak hamisak, akkor lezárul mindkét ciklus. Példa: n
åi2 i=1
1
0 n
≤n
T +1
T i2
F +
207
ADATFOLYAM NYELVEK (DATA FLOW) Pélada: Az előző feladat általánosítása tetszőleges f függvény négyzetösszegére. n
å[f(i)]2 k
i=k
0 n
≤n
T
T
+1
f2(i)
F +
AZ ADATFOLYAM NYELVEK, MINT PROGRAMOZÁSI NYELVEK JELLEMZŐI −
Az általános csomópontokat általános függvénnyel reprezentálják. Ezeknek a függvényeknek nincs mellékhatásuk. A paraméterátadás mindig értékszerinti. Általában nem használnak globális változókat, de ha mégis, akkor az egyszeres értékadás érvényes. A változó értékének módosítása nem megengedett. Csak lokális adattér van.
−
Általában a rekurzió alapeszköz.
−
A programfejlesztés is nagyon egyszerű. Megírok egy primitív programot, és azt transzformálom. Az így fejlesztünk bonyolult programok biztosan jók. Egy token akármilyen bonyolult adatot ír le, akkor is egy tokennek számít.
−
Gond: az architektúra hiánya. Ezen nyelveknek létezik a Neumann-architektúrán futó implementációja.
−
A programhelyességbizonyítás automatikus, és nagyon egyszerű.
−
Általában fordítóprogram-orientáltak, és a gépi kódjuk ez a gráf (grafikus gépi kód). Hordozható, hiszen ugyanazt a gépi kódot generálják. Ez a gráf automatikusan könnyen kezelhető.
208
ADATFOLYAM NYELVEK (DATA FLOW)
IGÉNYVEZÉRELT PROGRAMOZÁS: Kb. lassan 15 éve a programfejlesztés iránya: az adatvezérelt programozás kiterjesztése az igényvezérelt programozásra. Itt eltűnik az irányított gráf, a tokenek mindkét irányban mozoghatnak. Prioritások vezethetők be. Ez alá architektúra még prototípus szinten sem létezik. Ez még mindig kutatási szinten áll.
ADATVEZÉRELT PROGRAMNYELVEK: Néhány adatvezérelt programozási nyelv: VAL, LUCID, ID, LAU, SISAL, HDFL. VAL: Denis féle modellt megvalósító nyelv.
for Y:integer:=1; P:integer:=N do if P ≠ 1 then iter Y:=Y*P; P:=P-1 enditer else Y endif endfor LUCID: Deklaratív plattformú. Példa: n! kiszámítása:
FIRST(i,j)=(n,1) NEXT(i,j)=(i-1,j*i) OUTPUT = j AS SOON AZ i=1 A nyelvek egyfajta szempont szerint felírt együttese: COBOL FORTRAN PL/1 Pascal C Ada imperatív-deklaratív határ LISP MIRANDA PROLOG Z Az algoritmusosság egyre kevésbé dominál. VDM Obj Clear Specifikációs nyelvek, nincs algoritmus, csak specifikálunk. A Clear mögött pléldául nincs futtatható küd. MIRANDA: A MIRANDA a funkcionális világ egy viszonylag friss nyelve. Szigorúan típusos. Deklaratív jellegű, szimbolikus, a Prolognál lényegesen hatékonyabb illesztéssel dolgozó nyelv.
ADATFOLYAM NYELVEK (DATA FLOW) Példa: Ez igazából definíció és nem algoritmus.
factorial 0=1 factorial (x+1)=(x+1)* factorial x Z: A Z egy olyan specifikációs nyelv, amely matematikai rendszerek leírására alkalmas, alapja a halmazelmélet. Példa:
factorial : N → N factorial 0 =1 ∀ n∈Nn>0.
factorial n=n factorial(n-1) VDM: Magasabbrendű predikátumkalkuluson alapszik. Szintén matematikai specifikációs nyelv. Példa:
factorial : N → N pre_factorial(n)=TRUE post_ factorial(n,r)[ = ]r=n! Clear: Olyan specifikációs nyelv, amely mögött nincsen kód. Az univerzális algebrán alapul. Obj: Az univerzális algebrán alapuló specifikációs mtematikai nyelv. Példa:
obj Factorial/Natural ops factorial:nat → nat vars n:nat eqns (factorial(0)=1) (factorial(succ(n))=malt(succ(n), factorial(n))) jbo. Napjaink kedvenc programozási nyelvei a vizualizációs nyelvek. Egy új irányzat: nincs kód, egérrel "szerkesztjük meg" a programot.
209