Szintaxis | Jelentés | Kifejtés |
def name(patterns) guard { expr } |
Függvény definíció | def name { to run(patterns) { expr } } |
def name { method* } |
Tisztán metódusokból álló objektum | kernel |
Def name { method* matcher } |
Részben metódusokból álló objektum | kernel |
def name { method* matcher* } |
Kiterjesztett matcher rész | *** |
def name match pattern { expr } |
Metódusok nélküli objektum | kernel |
class name(patterns) { expr } |
Öröklés delegáció által | *** |
Az E-ben minden objektum, függvények nincsenek a függvény definíciót egy name nevu objektum run nevu metódusának definíciójává fejti ki a fordító (a metódusokat a „to” kulcsszóval vezetjük be). A függvényhívásnál láthattuk, hogy ez nem bonyolítja a jelölést, mert a name(patterns) hívást a fordító name.run(patterns) hívássá fejti ki.
Nem lehet függvényeket különbözo paraméterszámmal definiálni, azért hogy majd túlterheljük a függvényt. Íme egy példa rá:
# E syntax def times2(a) {return a * 2} def compute(a,b) { def times2(c,d) {return (c + d) * 2} # ....do computation... # The following line will throw an exception def answer := times2(a) return answer }
Ez itt egy kivételt dobna, mert a függvényszeru objektum, times2, belsobb definíciója teljesen eltakarja a külso times2-ot.
Megjegyzés: Nem csak a függvények, de még a beépített konstansok, mint az integerek és a floatok, is objektumok. Az olyan operátorok, mint például a „+” is, igazi rövidítés a paraméterátadásban, hiszen a „3.add(4)” megegyezik a „3 + 4” –el. Egyébként ez az amiért ez a muvelet
„The number is „ + 1
muködik, de
1 + „ is the number”
már nem. Egy sztring tudja, hogy hogyan kezeljen egy konkatenációs üzenetet egy szám argumentummal, de egy integer már határozatlan, amiatt, hogy mit csináljon egy sztring hozzáadásával. Amióta a „+” valódi rövidítése az „add” muveletnek, azóta készíthetünk objektumokat amelyek muködnek a „+” operátorral, csak implementálni kell egy „add(argument)” metódust az objektumban.
A metódusdefiníciók szintaxisa: ’to’ verb ’(’ arg-patterns ’)’ guard ’{’ method-expression ’)’.
Metódushíváskor az aktuális argumentumokat illeszti a rendszer a definícióban megadott argumentummintához. Ha nem illeszkednek a rendszer kivételt dob. Ha illeszkednek akkor az eredményezett változódefiníciók kontextusában kiértékeli a törzset a rendszer.
Lehetoség van kikapcsolni ezt a „guard” funkciót(amit lentebb tárgyalunk), a következo sorok beillesztésével:
# E sample pragma.enable("easy-return") pragma.disable("explicit-result-guard")
Ennek a beállításnak a segítségével már egyszerübben lehet definiálni a függvényeket. Ilyenkor a visszatérési értéket a „return” kulcsszó utáni kifejezésbol veszi a fordítóprogram. Példa az egyszerübb függvényekre:
# E sample def addNumbers(a,b) { return a + b } # Now use the function def answer := addNumbers(3,4)
Beépíthetsz függvényeket/objektumokat másik függvények/objektumok belsejébe, ezzel is funkcionális hasonlóságot adva, mintha Java osztályban lennél. A beépített/beillesztett függvények/objektumok kritikus tényezoi az E nyelvnek, különösképpen az objektumok készítésénél.
A paraméter nélküli függvényeknél szükséges egy üres nyitó- és zárójel. A paraméter nélküli függvények hívásánál szintén szükségesek az üres zárójelek.
Természetesen a függvények rekurzív módon meghívhatják önmagukat, mint itt is:
# E sample def factorial(n) { if (n == 0) { return 1 } else { return n * factorial(n-1) } }
A törzs értéke lesz a visszaadott érték, amelyet „megszur” a guard feltétel. Erre többnyire lehet úgy gondolni, mint a metódus visszatérési értékének típusára. Az „:any” guard nem tesz korlátozást a visszatérési értékre. Az alapértelmezett „:void” guard (amit akár el is hagyhatunk) nem enged meg semmilyen visszatérési értéket, mindig null-t ad vissza. A feltétel teljesülését csak futási idoben ellenorzi a nyelv.
Megengedett feltételek:
Megjegyzés: guard feltételeket alkalmazhatunk változók illetve formális paraméterek definíciójakor is. Pl:
# E sample def title :String := "abc" def reciprocal(x :float64) :float64 { 1 / x }
Az E nyelv megengedi a polimorfizmust a metódus neveken keresztül. Ebben a példában elmozgatunk egy autót, és egy repülot egy új helyre, egy egyszerü közös kód darab segítségével:
# E sample def makeCar(name) { def car { to moveTo(x, y) { # move the car } # other methods } return car } def makeJet(name) { def jet { to moveTo(x, y) { # move the jet, very different code from the code for car } # other methods } return jet } def vehicles := [makeCar("car"), makeJet("jet")] for each in vehicles { each.moveTo(3,4) }
Érdekesség, hogy metódusokon belül is definiálhatunk metódusokat, azok látják mindazt, amit a láthatósági szabályok megengednek nekik, és az oket definiáló metódus visszatérése után is hívhatóak.
Pl:
? def getterSetterPair(var value) :any { > def getter() :any { value } > def setter(newValue) :void { value := newValue } > [getter, setter] > } # value: <getterSetterPair> ? def [g1,s1] := getterSetterPair(3) # value: [<getter>, <setter>] ? def [g2,s2] := getterSetterPair("foo") # value: [<getter>, <setter>] ? g1() # value: 3 ? g2() # value: "foo" ? s1("bar") ? g1() # value: "bar" ? g2() # value: "foo"
Minden egyes alkalommal, amikor a getterSetterPair függvényt meghívjuk, az egy új variable változót deklarál (a paraméterátadás az E-ben érték szerinti paraméterátadást jelent), illetve létrehoz, és visszaad egy két függvénybol álló listát, amely az adott változó olvasására, írására szolgál. Így a változó olvasásának, írásának jogát külön tudjuk választani.
A második bemutatott lehetoség esetén egy objektumot hozunk létre, amely több metódust tartalmazhat (ennek is lehet run metódusa). A metódusok neve vagy aritása eltéro kell, hogy legyen páronként. Hibás hívás (nem létezik az adott nevu metódus, vagy rossz az aritás) esetén kivétel lép fel.
A marcher ág(ak) az ilyen kivételek lekezelésére jók. A matcher ág szintaxisa: ’match’ pattern ’{’ match-expresion ’}’. A matcher ágakra a következoképp gondolhatunk:
if (.../*no methods match*/) { switch ([verb, args]) { match pattern { matcher-expression } ... } }
Ha van illeszkedo matcher ág, akkor a program azt értékeli ki és annak az értéke lesz a visszatérési érték, ha nincs illeszkedo ág, akkor kivétel lép fel.
A „hagyományos” osztályokat a nyelv nem támogatja. Pl.: öröklodés nem létezik a nyelvben. Ennek oka, hogy külön megírt, egymással szemben potenciálisan bizalmatlan osztályok nem építhetnek egymás tulajdonságaira, implementációjára.
Azonban az öröklodés, illetve a „nyílt implementáció” technikája hasznos eszköz. (Nyílt implementáció alatt azt értik, hogy az objektum viselkedése bizonyos szempontból megváltoztatható, „felülírható”. Pl.: ha az „A” osztálynak van egy „myFoo” változója, és a változó elérésére létrehozunk egy „getMyFoo” metódust, akkor az osztály számára lehetové tesszük, hogy a változót „felüldefiniálják”, megváltoztassák az osztály viselkedését.)
Az E esetén ennek a megoldása, hogy olyan konstrukciót raktak a nyelvbe, amellyel könnyen lehet egy adott objektumba (A) egy másik objektum (B) egy példányát „beleépíteni”, úgy, hogy amennyiben egy metódushíváshoz A nem tartalmaz illeszkedo metódust, akkor amennyiben B rendelkezik ilyennel, akkor azt hívjuk meg. (Hangsúlyozni kell, hogy itt két külön objektumról van szó, A B „belsejét” csak úgy látja, ahogy azt B lehetové teszi.)
Pl.:
A következo Java kóddal:
public class B extends A { //private b-slice instance variable private int myFoo; //initialize b-slice public B(String str, int foo) { //initialize a-slice super(str); myFoo = foo; } //non-final, may be overridden public int getFoo() { return myFoo; } //overrides, but wraps, a-slice's getBar() public int getBar() { return super.getBar() + this.getFoo(); } } Ekvivalens megoldás az E-ben (az E nyelv ennél jobb megoldást is kínál (ld. köv)): def BMaker { # only needed if, in Java terms, B is non-abstract to new(str, foo) :any { def self := BMaker adopt(self, str, foo) } # initialize b-slice to adopt(self, str, foo) :any { # initialize a-slice def super := AMaker adopt(self, str) def bSlice { # you are invited to override to getFoo() { foo } # overrides, but wraps, a-slice's getBar to getBar() { super getBar() + self getFoo() } delegate { super } } } }
Az E korábbi verzióiban a következo konstrukció ekvivalens volt a fenti kóddal (ezt a megoldást (és a class kifejezést) ma már nem támogatják – helyette ld. a lenti közelítést):
class BMaker(str, foo) :any { def super := AMaker adopt(self, str) def bSlice { to getFoo() { foo } to getBar() { super getBar() + self getFoo() } delegate { super } } }
Az alábbi kifejezés hivatott átvenni a fenti class kifejezés helyét.
define param extends super-expression { to verb(arg-patterns) guard { method-expression } }
A fenti kifejezés hasonlóan muködik, mintha param törzsében az alábbi kódot helyeznénk el.
match [verb, args] { E.call(target-expression, verb, args) }
Az egyik különbség, hogy a target-expression-t minden futáskor kiértékeljük (ha a match ág futására egyáltalán sor kerül), míg a super-expression kifejezést csak egyszer, a param objektum létrejöttekor/ példányosításakor. A másik különbség, hogy a param törzsében a super-expression kifejezés értéke egy super nevu változóba kerül.
Edoc
A JavaDoc E megfeleloje
Az E támogatja a javadoc-stílusú kommentek készítését, amelyekbol késobb utófolyamatként HTML dokumentumot lehet generálni. Erre a célra a „/** … */” komment formátum van fentartva. Az ilyen stílusú kommentek csak objektum/függvény definíció ( a „def” kulcsszó ) elott, vagy objektum metódusa ( a „to” kulcsszó) elott állhatnak.
Pl.:
# E sample /** * Add 2 numbers together. * * Currently works with any objects that support the "plus" method * * @param a the first number. * @param b the second number * @return the result of adding. */ def adder(a, b) {return a + b}
Interface a Java-hoz (Csatlakozás a Java-hoz)
Importálhatunk Java osztályt az alapul szolgáló Java virtuális géptol az „import” állítással, és beszélhetünk a Java objektummal, épp úgy, mintha egy E objektum volna. Példaként helyettesítsük a vektort:
# E sample #készítsünk egy egyszerü vektort egy közvetlen Java hívással def myVector := <unsafe:java.util.Vector>() myVector.addElement("abc") #készítsünk egy makeVector függvényt, amit ismételten meghívhatunk, hogy további vektorokat készítsünk def makeVector := <unsafe:java.util.Vector> def vector2 := makeVector() # készítsünk egy rövidítést/gyorsítást a java.util package -hez, ami meggyorsítja az összes java.util –beli osztály elérését def <util> := <unsafe:java.util.*> def vector3 := <util:Vector>()
A példában 3 módot mutattunk arra, hogy hogyan használhatunk Java objektumot, amelyek nagyjából összehasonlíthatóak a más stílusú Java-beli „import” használatához. Ha csak egy vektort akarsz, akkor a vektor konstruktor közvetlen hívásával kaphatsz. Ha azt tervezed, hogy több vektort készíttetsz, importálhatod az osztályt egy változóba. És ha azt tervezed, hogy sok osztályt használsz egy bizonyos csomagból, akkor használhatod úgy is hogy a ’.’ után csillagot teszel, majd mikor felhasználod, akkor az „unsafe:” helyett beírod a változó nevét, és utána az osztályt amit akarsz használni. A „:” elott lévot uriGetter-nek nevezik.
Eddig már láttunk 3 uriGettert. Ezek a file:, unsafe:, és a programozó által deifiniált util:. Négy további eloredefiniált uriGetter van még. Ezek a swing: (a javax.swing eléréséhez), az awt.( a java.awt eléréséhez), a resource: (csak olvasási jogosult fájl olvasásokhoz, mint pl.: képek, hangok, és statikus szövegek), és az import:, amit lentebb tárgyalunk.