Eiffel (programozási nyelv)

A Wikipédiából, a szabad enciklopédiából
Eiffel
Paradigma objektumorientált
Tervező Bertrand Meyer
Fejlesztő Bertrand Meyer & Eiffel Software
Típusosság statikus típusosság, Erősen típusos
Megvalósítások EiffelStudio, SmartEiffel, Visual Eiffel, Gobo Eiffel, "The Eiffel Compiler" tecomp
Hatással volt rá Ada, Simula, Z
Befolyásolt nyelvek C#, D, Java, Lisaac, Racket, Ruby,[1] Sather
Weboldal


Az Eiffel programozási nyelvet Bertrand Meyer tervezte a 80-as évek közepén. Egy objektumorientált programozási nyelv, amely jellegzetesebb tulajdonságai közé tartozik a többszörös öröklődés és a Design by Contract. A nyelv a nevét Gustave Eiffel, francia építészről kapta, az Eiffel-torony és a budapesti Nyugati pályaudvar tervezőjéről.

A nyelv szintaxisa a Pascalra és az Adára hasonlít. Erősen típusos, tisztán objektumorientált nyelv. A programnyelvet úgy tervezték meg, hogy a program kódja maga legyen a dokumentáció.

Meyer, a nyelv alkotója, saját céget alapított, Eiffel Software néven, amelyik a programnyelv fejlesztésére szakosodott, és saját implementációjuk az EiffelStudio.

Elvek[szerkesztés | forrásszöveg szerkesztése]

Az Eiffel objektumorientált, és ezt igyekszik minél következetesebben megvalósítani. A programkönyvtárak és az alaptípusok is osztályok. A programkönyvtárak használata is objektumorientált: örökölni kell belőlük.

Az azonos osztályú objektumok nem látnak bele jobban egymás belsejébe, mint más kliensek. Sok más nyelvvel, például a C++-szal és a Javával ellentétben a leszármazott osztály példányai korlátozások nélkül hozzáférhetnek azokhoz a feature-jeikhez is, amelyeket az alaposztályból örököltek. Tehát az Eiffel nyelv a többi, osztályorientált nyelvvel szemben objektumorientált.

Engedélyezi a többszörös öröklődést, és a gyémánt probléma megoldására több eszközt is kínál. Sem az összeolvasztás, sem a szétválasztás nem automatikus. A probléma megoldását a programozóra bízza, és ehhez nyelvi eszközöket is kínál.

A nyelv tervezésében fontosak voltak a kényelmi szempontok és az intuíció támogatása is. A leszármazottakban a láthatóság akár szűkíthető is. A rutinok paraméterkészlete, értelmezési tartománya szűkíthető. Ha G generikus osztály, és B alosztálya A-nak, akkor G[B] is alosztálya G[A]-nak, holott az elmélet a másik irányt indokolná. Ezekkel az eszközökkel egyszerűen megvalósíthatók olyan megkötések, amelyek más nyelvekben bonyolult, vagy akár lehetetlen lenne megvalósítani. Mindezek az eszközök azonban aláássák a típusbiztonságot, tehát a típusok nem bizonyítják a program helyességét. A problémán a polimorfikus catcall segíthet.

A nyelv alapelvei közé tartozik az általánosítás is. A nyelvhez csatolt módszertanban a projekt külön fázisában végzik az általánosításokat, a projektben szereplő osztályok generikussá tételét. A generikusság korlátozható közös ősosztállyal. Ennek az a hátránya, hogy a közös ősbe be kell tenni minden műveletet, amire hivatkozni kívánunk. Ez részletes előzetes tervezést igényel. Az ágensek segíthetnek megkerülni ezt a kérdést.

A helyesség ellenőrzésére az elő- és utófeltételek, meg az invariánsok szolgálnak. Közös néven szerződéseknek nevezik őket, és a vállalkozók közötti szerződésekhez hasonlítják. Még az ős és a leszármazott osztályok is szerződnek, ahol is a leszármazott kapja az alvállalkozó szerepét. Az egyes objektumokban a privát rutinok belsejében nincsenek szerződések; az egyes objektumnak nem kell önmagával szerződnie, de az invariánst a művelet elvégzése után biztosítania kell.

Szintaxis és szemantika[szerkesztés | forrásszöveg szerkesztése]

Az Eiffel nyelv csak az ASCII karaktereket fogadja el. Ha más karakterek bukkannak fel, akkor a fordító hibát jelez.

Beépített típusok[szerkesztés | forrásszöveg szerkesztése]

Az Eiffel beépített elemi típusai:

INTEGER, REAL, DOUBLE, CHARACTER, BOOLEAN.

A fordító ismeri még a STRING, az ARRAY és a BIT_N (bitsorozat típus) típust is. Mindezek a típusok nullaszerű értékre inicializálódnak: az IBNTEGER 0-ra, a REAL 0.0-ra, a DOUBLE 0.0-ra, a CHARACTER nullkarakterre és a BOOLEAN False-ra.

Konvenciók[szerkesztés | forrásszöveg szerkesztése]

Maga a nyelv nagybetűérzéketlen: a kis- és a nagybetűk használatát konvenciók szabályozzák. A nevekben csak ASCII karakterek szerepelhetnek; a neveket betűvel kell kezdeni, de a továbbiak lehetnek betűk, számok és aláhúzásjelek is. Az aláhúzásjel a több szavas nevek tagolására szolgál. Az osztályok neve a konvenció szerint csupa nagybetűből áll. A konstansok nagybetűvel, a többi feature kisbetűvel kezdődik. Az előre definiált entitások és kifejezések szintén nagybetűsek. A nagybetűérzéketlenség ellenére lehetséges egy osztály példányának az osztály kisbetűs nevét adni.

A ; tagolásra szolgál. A sorok végén kitehető, de nem hiba, ha nincs.

Konstans megadása például:

Solar_system_planet_count: INTEGER = 9

Vezérlési szerkezetek[szerkesztés | forrásszöveg szerkesztése]

A feltételes utasítás így néz ki:

if feltetel

then

--utasitasok

elseif feltetel

then

--utasitasok

else

--utasitasok

end

ahol akárhány elseif utasítás lehet, és ha nincs elseif, akkor else sem kötelező.

Többszörös elágazás írható ezzel is:

inspect --egesz, vagy karakter kifejezes

when ertek1,ertek2

then utasitas

when ertek3..ertek4

then utasitas

else utasitas

end

A ciklus:

from

--kezdofeltetel

invariant

--invarians

variant

--ciklusvaltozo

until

--amig nem

loop

--ciklusmag

end

ahol is az invariant és a variant nem kötelező.

Rutinok[szerkesztés | forrásszöveg szerkesztése]

A rutinok a Result változóban levő értékkel térnek vissza.

A paraméter nélküli rutinok zárójel nélkül hívhatók. Ez lehetővé teszi, hogy egy absztrakt osztály egyik konkrét utóda kiszámítsa azt, ami egy másik leszármazottban attributum.

A once rutinok csak első meghívódásukkor futnak le, a későbbiekben ennek a futási eredménye érhető el ugyanazzal a névvel. Általában paraméter nélküli, de lehetnek paraméterei is. Ezekben a rutinokban a do helyett once szerepel.

Az elavult rutinok a obsolete utasítással jelölhetők meg. Ennek egy string a paramétere, amiben leírható, hogy mit használjunk helyette.

A felülírás letiltható a frozen deklarációval.

Ágensek[szerkesztés | forrásszöveg szerkesztése]

A rutinok ágensbe burkolva átadhatók paraméterként. Ezek az ágensek azonban nem azonosak a robotok programozásában használt ágensekkel.

Legyen az r rutin definálva a C osztályban! Ekkor a C.r rutinból ágens készíthető:

class C

feature

--...

r(parameterek)

do

--a rutinban megvalositott tevekenyseg

end

agent r

end

Az agent r kifejezés szerepelhet azokban az osztályokban is, amelyekből az r rutin egyébként is hívható lenne. Ha az r rutinnak vannak paraméterei, akkor többféleképpen is átadható, mint ágens:

agent r(a,?)

azt jelenti, hogy az első paraméter az a, a második paraméter nyitott. Hasonlóan,

agent r(?,b)

jelentése az, hogy az első paraméter nyitott, a második paraméter a b. De lehet ilyen is:

agent r(a,b)

itt minden paraméter meg van adva, de a behelyettesítés még nem történt meg, hanem arra vár, hogy meghívják az ágenst.

Lehet akár ilyen is:

agent r(?,?)

ami ugyanazt jelenti, mint

agent r.

Osztályok[szerkesztés | forrásszöveg szerkesztése]

Osztályok a class kulcsszóval deklarálhatók. Az Eiffel programok osztályokból állnak. A beépített típusok és a programkönyvtárak is osztályok. A többszörös öröklődés és az ismételt öröklődés megengedett. A metódusokat és az osztály- és példányváltozókat, konstans tagokat a nyelv közös néven feature-nek nevezi. Alapelveiben szerepel az egységes kinézet elve, ami szerint a paraméter nélküli tagfüggvények zárójel nélkül hívhatók. Ez lehetőséget biztosít arra, hogy egy absztrakt osztály két leszármazottja közül az egyikben kiszámoljuk azt, amit a másikban megadunk.

Az osztályokban kiemelt szerep jut a létrehozó feature-öknek. Ezek a konstruktoroknak felelnek meg. A létrehozó feature-öket az osztály elején a create mondatban kell felsorolni:

class 
    HELLO_WORLD
create
    make
feature
   make
   do
      print ("Hello, world!")
   end
end

A létrehozó rutinok szokásos neve make. Ezt nem egyszerű átállítani, ezért javasolt, hogy az egyik létrehozó rutin a make legyen. Ezek a rutinok indíthatók el példányosításkor:

create x.make(a : INTEGER, b : STRING, c : ACCOUNT)

Osztályon belül nincs túlterhelés, de különböző osztályokban szabad egy műveletet ugyanazon a néven deklarálni.

Absztrakt osztályok és rutinok a deferred kulcsszóval deklarálhatók. Egy absztrakt osztály utódai mindaddig absztraktak maradnak, ameddig az absztrakt rutinokat nem implementálják. Konkrét osztályok utódai is absztrakttá tehetők ugyanezzel a kulcsszóval. Az absztrakt osztályok közvetlenül nem példányosíthatók, csak konkrét utódaik révén.

Az osztályok alapesetben referenciák révén példányosulnak. Ha ezt nem akarjuk, akkor az osztályt expanded-nek kell deklarálnunk. Nem expanded osztályok példányai is lehetnek nem referenciák, ha expanded-ként hozzuk létre őket. Az alaptípusok is expanded osztályok.

expanded class PONT

feature

x, y : REAL

eltol(dx, dy : REAL)

do

x := x + dx

y := y + dy

end

end

Az üres referencia a Void, ami a NONE fiktív osztály egyetlen példánya. Ha egy osztály attached, akkor az ilyen osztályú objektumok nem lehetnek Voidok. Az expanded osztályok implicit attached tulajdonságúak is, mivel nem referenciák. Az attached ellentéte a detachable. Újabban az attached az alapértelmezett, de a régebbi verziókban a detached volt az alapértelmezés. Kompatibilis módban megint a régebbi verzió az érvényes. Erre külön figyelni kell. Az attached típusú tagok nem vehetnek fel detached típusú értéket. Ez alól kivétel, ha egy elágazásban ellenőrizzük, hogy nem Void-e.

Az alacsony szintű pointereket a POINTER osztály példányai pótolják.

Az osztályokban Current jelöli az adott példányt. Az adott példány dinamikus típusára a like Current utal. Ez lehetővé teszi, hogy a rutinok értelmezési tartománya szűküljön az öröklődés folyamán, ami veszélybe sodorja a típusbiztonságot, hiszen az értelmezési tartomány nem szűkíthető típusbiztonságos módon. Ezen a catcall segíthet.

Öröklődés[szerkesztés | forrásszöveg szerkesztése]

A többszörös öröklődés megengedett:

class D

create --...

inherit

A

B

feature

--...

ahol is az ősosztályok nem fontosságuk szerint csökkenő sorrendben vannak felsorolva, mint a Python nyelvben, hanem egyenrangúak. Névütközés esetén a fejlesztő döntheti el, hogy összevonja-e a feature-öket, vagy elkülöníti. Ehhez az Eiffel nyelvi szinten is biztosít eszközöket.

Egy feature átnevezhető (remane mondat), absztrakttá tehető (undefine mondat), vagy újradefiniálható (redefine mondat). Újradefiniálás esetén Precursor jelöli az eredetit. Átnevezés és elkülönítés esetén a select jelöli ki, hogy mi fog meghívódni az eredeti néven. Ha több absztrakt feature-t örököl az osztály, akkor elég hozzá egy implementációt írni. Meyer ezt úgy fejezi ki, hogy egy konkrét kővel több absztrakt madarat meg lehet ölni.

A létrehozó rutinok öröklődnek, de az utódokban már nem lesznek automatikusan létrehozó rutinok. Azokat minden újabb osztály elején új create mondattal be kell jelenteni.

Az öröklődésben az osztályoknak van egy közös őse, az ANY. Ez implementál néhány hasznos feature-t, mint a copy, a clone vagy a deep_clone. Van benne egyenlőségvizsgálat, amely az objektumok egyenlőségét vizsgálja. További egyenlőségvizsgálatra szolgál az equal és az is_equal. Ezek a rutinok felüldefiniálhatók az utódokban, de mindegyikhez tartozik egy alapértelmezett, aminek neve default_-tal kezdődik, és ami nem definiálható át. Az egyenlőségvizsgálatban = jelöli az egyenlőt, és /= a nem egyenlőt. A ~ az equal alapján készül. Az := értékadás mellett feltételes értékadás is van, a ?=. A példányok fájlba menthetők: x.store(fajl_neve). Az ANY bővíthető is, de szülője, a GENERAL már nem.

Az összes osztálynak van egy közös utóda, a NONE fiktív osztály, aminek csak egy eleme van, az üres referencia, a Void.

Az ANY és a NONE az exportlistákban válik fontossá:

export{ANY} feature xyz

azt jelenti, hogy az xyz feature bárhonnan látható,

export{NONE} feature

pedig azt, hogy az adott objektumon kívülről még az azonos osztályú példányok sem férhetnek hozzá. A többi eset ezek között áll, a kapcsos zárójelek között felsorolt osztályú objektumok láthatják az osztály adott feature-ét. Ez a láthatóság finomabb szabályozását teszi lehetővé.

Szerződések[szerkesztés | forrásszöveg szerkesztése]

Az Eiffel nyelv alapelve a szerződés alapú programozás. Ez azt jelenti, hogy akár minden utasításnak lehet elő-, utófeltétele, vagy invariánsa. Ezek felfoghatók szerződésként a hívó és a hívott között. A hívó biztosítja az előfeltételt és az invariánst, és a hívott vállalja, hogy normális lefutás esetén biztosítja az utófeltételt és az invariánst, kivétel esetén pedig legalább az invariánst. A konstruktoroknak nincs külön előfeltételük, és az invariánst sem kell a hívónak biztosítania, hiszen éppen a konstruktor állítja be az invariánst. Az előfeltételeket a hívó ellenőrzi, ezért nem hívhatók benne olyan rutinok, amiket a hívó nem tud hívni. Az utófeltételben old-dal hivatkozhatók a rutin futása előtti értékek.

A szerződések alakja abban az osztályban, ahol az öröklésben először definiálják az illető feature-t:

feature nev

require

--az elofeltetelt leiro allitasok

do

begin

--a rutin utasitasai

ensure

--az utofeltetelt leiro allitasok

end

invariant

--az invarianst leiro allitasok

Az öröklődés folyamán az előfeltételek enyhülhetnek, az utófeltételek szigorodhatnak. Ha nem írunk elő- vagy utófeltételt, akkor azok azonosan igazak lesznek. Ha egy rutinnak nincs előfeltétele, akkor a továbbiakban sem lehet, viszont az utófeltételben már lehetnek vállalásai. Az előfeltételhet vaggyal, az utófeltételhez éssel lehet továbbiakat hozzávenni:

feature nev

require else

--az elofeltetelt leiro allitasok

do

begin

--a rutin utasitasai

ensure then

--az utofeltetelt leiro allitasok

end

Többszörös öröklődés esetén is az előfeltételek összevagyolódnak, az utófeltételek és az invariánsok összeéselődnek.

Az Eiffel lehetővé teszi, hogy egy rutint, aminek nem volt paramétere, újraimplementáljunk attributumként. Ekkor az előfeltételek elfelejtődnek, az utófeltételek és az invariánsok az osztály invariánsai lesznek.

Az only a megváltoztatható elemeket jelölheti ki. Az utófeltételben az old a rutin futásának kezdetekor meglevő értékekre hivatkozik. A check egyes ellenőrzendő állításokat jelöl meg. Ha a fordítónak a megfelelő opciót adjuk, akkor az ellenőrzi ezeket az állításokat.

Kivételkezelés[szerkesztés | forrásszöveg szerkesztése]

Az Eiffel nyelv a normál tevékenységtől való eltérést első vonalban a szerződésekkel fogja meg. A kivételek megmaradnak kivételes eseménynek. Akkor kerül rájuk sor, ha az adott rutin nem tudja biztosítani az utófeltételét. Ekkor az az elvárás, hogy legalább az osztályinvariánst állítsa helyre. A kivételkezelésre a rutin törzsén kívül, az end előtt elhelyezett rescue mondat szolgál. Innen a retry a rutin elejére visz vissza, hogy egy másik módon próbálja meg biztosítani az utófeltételét. Ha nincs kiírva, akkor az osztály alapértelmezett rescue rutinja, az esetleg átnevezett default_rescue hívódik meg. Gyakran a konstruktort teszik meg default_rescue-nak, hiszen annak is az a dolga, hogy beállítsa az osztályinvariánst. Ha nem sikerült előállítani az utófeltételt, akkor a kivétel továbbdobódik a hívóba.

Egy Eiffel rutin vázlata a rescue mondattal:

feature nev

do

begin

--a rutin utasitasai

rescue

--a kivételek kezelése

retry

end

Kivételek az EXCEPTIONS osztályból származtathatók.

Generikusok[szerkesztés | forrásszöveg szerkesztése]

Az általánosítás és az absztrakció az Eiffel nyelv alapelvei közé tartozik. A módszertan kiemelten kezeli az általános, de specializálható szerkezeteket. Bertrand minden projekthez javasol egy általánosítási szakaszt, ahol a projektben megalkotott osztályokat lehetőség szerint általánosítják. Generikusok valósítják meg például az adatszerkezeteket.

Egy generikus osztály vázlata:

class SHD[G]

feature

--definiciok

end

Egy általános generikusban csak olyan műveletekre szabad hivatkozni, amelyek általánosságban minden osztályra meghívhatók, különben a generikus hibás lesz, és nem fog lefordulni. Megkötésekkel ez a hiba javítható:

class SORTABLE_ARRAY[G->COMPARABLE]

feature

--definiciok; hasznalhatja az osszehasonlitast, a COMPARABLE muveleteit

end

Keverhető a nem korlátozottal:

class HASH_TABLE[G, H-> HASHABLE]

feature

--definiciok

end

Tömbök[szerkesztés | forrásszöveg szerkesztése]

Tömbök megadhatók az ARRAY generikus osztály példányaiként, vagy például így:

tomb = <<4, 6, 9>>

Ez egy manifeszt tömb, típusa ARRAY[INTEGER], tehát ekkor is a generikus ARRAY osztály áll a háttérben.

Manifeszt konstansok stringekhez is elérhetők:

INTEGER = "Go get a life!"

Használható az unique is: a, b, c, n = unique

A fordító ad nekik értéket. Ez pótolja a felsorolási típust.

Tuple és rutin típusok[szerkesztés | forrásszöveg szerkesztése]

A TUPLE az Eiffel direkt szorzat típusa. Hasonlít a generikusokra, de nem az, hiszen akárhány paramétere lehet:

TUPLE[] = TUPLE :> TUPLE[STRING]

TUPLE[STRING] :> TUPLE[STRING, INTEGER]

TUPLE[STRING, INTEGER] :> TUPLE[STRING, INTEGER, STRING]

--...

ahol is a hosszabb TUPLE a kezdőszeletét tartalmazó TUPLE altípusa (:>). Emellett még teljesül az is, hogy a bővebb típust tartalmazó TUPLE tartalmazó típus (<:):

TUPLE[] = TUPLE

TUPLE[STRING] <: TUPLE[ANY]

TUPLE[STRING, INTEGER] <: TUPLE[ANY, ANY]

TUPLE[STRING, INTEGER, STRING] <: TUPLE[ANY, ANY, ANY]

--...

A rutin típusok leszármazása:

A rutinok típusa a ROUTINE; ebből származik a PROCEDURE és a FUNCTION típus. A logikai függvények típusa a PREDICATE a FUNCTION altípusa. A TUPLE-hoz hasonlóan további altípusok képezhetők:

ROUTINE[TUPLE[]] = ROUTINE[TUPLE] = ROUTINE :> ROUTINE[TUPLE[STRING]]

ROUTINE[TUPLE[STRING]] :> ROUTINE[TUPLE[STRING, INTEGER]]

ROUTINE[TUPLE[STRING, INTEGER]] :> ROUTINE[TUPLE[STRING, INTEGER, STRING]]

--...

valamint

ROUTINE[TUPLE[]] = ROUTINE[TUPLE] = ROUTINE

ROUTINE[TUPLE[STRING]] <: ROUTINE[TUPLE[ANY]]

ROUTINE[TUPLE[STRING, INTEGER]] <: ROUTINE[TUPLE[ANY, ANY]]

ROUTINE[TUPLE[STRING, INTEGER]] <: ROUTINE[TUPLE[ANY, ANY, ANY]]

--...

ahol a TUPLE-k a paramétereket tartalmazzák. A függvények általános típusa hasonló, csak kibővül a visszatérési értékkel:

FUNCTION[TUPLE, T]

ahol T a visszatérési érték típusa. Az altípusok a ROUTINE altípusaihoz hasonlóan képződnek, a visszatérési érték figyelembe vételével.

A rutin típusok az ágensbe való burkoláskor válnak fontossá.

Példaprogram[szerkesztés | forrásszöveg szerkesztése]

   class 
       HELLO_WORLD
   create
       make
   feature
      make
         do
            io.put_string ("Hello, world!")
            io.put_new_line
         end
   end

Jegyzetek[szerkesztés | forrásszöveg szerkesztése]

  1. Cooper, Peter. Beginning Ruby: From Novice to Professional, 2nd, Beginning from Novice to Professional, Berkeley: APress (2009). ISBN 1-4302-2363-4 „To a lesser extent, Python, LISP, Eiffel, Ada, and C++ have also influenced Ruby.” 

Forrás[szerkesztés | forrásszöveg szerkesztése]

  1. Nyékyné Gaizler Judit: Programozási nyelvek