A Falcon programozási nyelv

Objektum-orientált programozás

Osztályok

Általánosan

A Falcon osztályok sok adat és kód keveréke, amik az objektumok példányosításához szükségesek. Definiálása a következőképp történik:

class class_name[ ( param_list ) ] [ from inh1[, inh2, ..., inhN] ] [ static block ] [ properties declaration ] [init block] [method list] end

Egy osztály tulajdonság listája tartalmazza az osztály nevét, és az inicializálandó értékeket. Bármilyen valid érték beírható ezek helyére. Ha nem tudunk mit beírni inicializáláskor, írjunk nil értéket. Alapértelmezetten a Virtual Machine és a compiler úgy működik, hogy a lehető legminimálisabb szükséges értékeket kelljen csak megadni inicializáláskor.

class mailbox( max_msg, is_public ) capacity = max_msg name = is_public ? "" : "none"; messages = [] a_property = nil end

A példányosítás kétféleképpen valósítható meg. Az egyik mód, mikor úgy hívjuk meg az osztályokat mint bármelyik más osztályt. Ilyenkor egy üres objektum kreálódik, amibe az osztálykonstruktor kerül bele.

my_box = mailbox( 10, false ) printl( "My box has ", my_box.capacity, "/", len(my_box.messages), " slots left.")

A másik lehetőség, hogy vagy kiterjesztünk egy alap meglévő osztályt, vagy normális módon inicializáljuk a még inicializálatlan tagokat.

object my_box from mailbox( 10, false ) name = "Giancarlo" end

Lehetőségünk van az osztályokat manipulálni úgy, mintha függvények volnának.

if some_condition right_class = mailbox else right_class = X_mailbox end /* some code here */ my_box = right_class( 10, false ) printl( "My box has ", my_box.capacity # len( my_box.messages ), " slots left.")

Metódus deklarálására is van lehetőségünk, a korábbi részekben leírt módon a function kulcsszó segítségével.

class mailbox( max_msg ) capacity = max_msg * 10 name = nil messages = [] function slot_left() return self.capacity - len( self.messages ) end end

Sokszor egy objektum inicializációja többől áll mint pár kifejezés jelölésében. Két lehetőségünk van ilyenkor. Egyik az, hogy írunk egy metódust, ami teljessé teszi az inicializációt, ha az objektum már egyszer deklarálva lett. A másik lehetőség az explicit inicializációt használni, ezt explicit konstruktor segítségével lehet megtenni az init blokkban.

class mailbox( max_msg ) capacity = max_msg * 10 name = nil messages = [] init printl( "Box now ready for ", self.capacity, " messages." ) end function slot_left() return self.max_msg - len( self.messages ) end end

Tudunk static tulajdonságokat is deklarálni, ekkor az adott tulajdonság minden osztály minden példánya számára elérhetővé válik, azonban ez nem lehet dinamikus adat. Az osztály létrehozásakor egyszer értékelődik ez ki, minden változtatás a statikus tulajdonságban az egész alap statikus objektumot meg fogja változtatni, illetve egy új példány is létre fog jönni ebből.

class with_static static element = "Initial value" function getElement(): return self.element; function setElement( e ): self.element = e; end obj_a = with_static() obj_b = with_static() printl( "The initial value of the property was: ", obj_a.getElement() ); obj_a.setElement( "value from A" ) obj_c = with_static() printl( "The value in B is: ", obj_b.getElement(), " and in C: " , obj_c.getElement() );

Az init blokknak lehet static blokkja is, ami nagyon hasonlóan működik a statikus függvény blokkokhoz. Azonban az init static blokk csak az objektum példányosításakor egyszer hívódik meg. Ez lehetőséget nyújt ahhoz, hogy egy előre beállított környezetet készíthessünk, ami ugyanabban az osztályban egy objektum által használható.

class with_static_init static numerator = nil my_number = nil init static self.numerator = 1 printl( "Class initialized" ) end self.my_number = self.numerator++ end end obj_a = with_static_init() obj_b = with_static_init() obj_c = with_static_init() printl( "Object number sequence: ", obj_a.my_number, " ", obj_b.my_number, " ", obj_c.my_number )

A kimenet:

Class initialized Object number sequence: 1 2 3

Osztályszintű metódusok

Arra is van lehetőségünk, hogy osztályon belül elérjünk egy metódust. Ezek az osztályszintű metódusok közvetlenül az osztály nevével hívhatóak meg, de ezek semilyen példányra nem hivatkoznak. Ezeknek a lekorlátozása és irányítása a névterekkel a legegyszerűbb.

class FunnyFunctions function a() > "This is funny function a" end function b() > "This is funny function b" end end FunnyFunctions.a() FunnyFunctions.b()

Többszörös öröklődés

A Falcon támogatja a többszörös öröklődést, ilyenkor a leszármaztatott osztály a szülő osztályok minden tulajdonságával, metódusával és alosztályával rendelkezni fog

class parent1( p ) prop1 = p init > "Initializing parent 1 with - ", p end function method1(): > "Method 1!" end class parent2( p ) prop2 = p init > "Initializing parent 2 with - ", p end function method2(): > "Method 2!" end class child(p1, p2) from parent1( p1 ), parent2( p2 ) init > "Initializing child with ", p1, " and ", p2 end end instance = child( "First", "Second" )

Ahogy a példa is mutatja, a gyerek osztály inicializálása a szülő osztályoké után következhet csak, és a sorrend is fontos példányosítási okokból.

Base metódus túlterhelés

Sokszor előfordul, hogy az alosztály annyira megváltoztatja az alap osztályt, hogy szükség van a metódusok újradefiniálására. Az overriding pontosan ezt jelenti, ugyanazon a helyen,néven és paraméterekkel létrehozni ugyanannak a metódusnak egy másik "verzióját". Például:

class base ction mth(): > "Base method" function callMth(): self.mth() end class derived from base function mth(): > "Derived method" end

Private tagok

Lehetőségünk van privát láthatósággal ellátni az osztályt, illetve azon részeit. Ha egy objektum neve _-vel kezdődik, akkor csak a osztályon belül látható illetve érhető el, egyéb esetben mindenhonnan látható és elérhető. De van lehetőségünk privát láthatósággal ellátott tagokat is elérni, erre szolgálnak a már ismert getter és setter metódusok.

object privateer _private = 0 function getPrivate(): return self._private function setPrivate(value): self._private = value end

Operátor túlterhelés

Az operátor túlterhelést a kettős aláhúzással jelöljük "__". Például a + operátor túlterhelése a következőképp zajlik:

class OverPlus( initValue ) numval = initValue function add__( operand ) return OverPlus( self.numval + operand ) end end op = OverPlus( 10 ) nop = op + 10 > nop.numval //: 20

Természetesen az osztály talán megnézi az operandus típusát, de a kompatibilis típusokkal nagyon csodálatosan fut.

class OverPlus( initValue ) numval = initValue function add__( operand ) if operand provides numval return OverPlus( self.numval + operand.numval ) elif operand.typeId() == IntegerType or operand.typeId() == NumericType return OverPlus( self.numval + operand ) elif raise "Invalid type!" end end end op = OverPlus( 10 ) + OverPlus( 10 ) > op.numval //: 20

Ha konzisztens, akkor van lehetőség önmagában módosítani az értéket.

... function add___( operand ) self.numval += operand return self end ...

Az operátorok túlterhelése a következőkre bontható szét:

Matematikai túlterhelés

Az unáris és bináris operátorok túlterhelése elég hasonló. A bináris operátorok kapnak egy paramétert, és általában, de nem szükségszerűen vissza is tér önmagával azonos típussal. A bináris:

class OperOver( num ) val = num function add__(v): return self.val + v end o = OperOver( 10 ) o += 5 inspect(o) // a numeric 15 value

Az unáris operátorok nem kapnak paramétert:

Összahasonlításos túlterhelés

Az összehasonlító operátorok, név szerint: <,>,<=,>=,==,!= mindegyike ugyanazon túlterhelési módszerhez köthetők, az összehasonlításhoz. Megjegyezzük, hogy itt nincs __, egyrészt történelmi, másrészt különböző szemantikai okok miatt. Megkötés, hogy visszatérjen kisebb, mint nulla, nulla, vagy nagyobb, mint nulla értékekkel a hasonlított elemhez képest. Ezzel szemben a matematikai operátoroknál nem okoz hibát az, ha az elem nem összehasonlítható: a Falcon VM-jének van egy rendezési stratégiája, amivel rendezi az elemeket, amennyiben ennek nincs fizikai oka. Ha nincs semmilyen rendezési stratégia vagy elem, akkor nil-lel tér vissza a rendezés. A nil miatt értesül a VM, hogy a rendezés elakadt, tehát a túlterhelés "feladta", és így az alapértelmezett rendezési algoritmus lesz érvényes.

class CmpOver( val ) number = val function compare( oper ) if oper provides number return self.number - oper.number elif oper.typeId() == NumericType or oper.typeId() == IntegerType return self.number - oper end // else let the VM do our work return nil end end ten = CmpOver( 10 ) > "Is ten > 5? ", ten > 5 > "Is ten != 3? ", ten != 3 > "Is ten <= 10? ", ten <= 10 > "Is ten > an array? ", ten > [1,2,3]

Az összehasonlítási metódust nem csak az összehasonlító operátorok feloldására használhatjuk, hanem akkor is, amikor az alapértelmezett rendezési metódus szükséges, például mikor egy szótárba történik beszúrás vagy keresés, vagy arraySort() hívás. Fontos: ha csak egy elemmel dolgozunk ilyenkor, akkor is az összes elemet egy collection-be kell gyűjteni és tárolni eközben.

function cmpFunc( o ) if o provides number: return self.number - o.number return nil end class CmpOne( val ) number = val compare = cmpFunc end class CmpTwo( val, name ) number = val name = name compare = cmpFunc end dict = [ CmpOne( 10 ) => "ten", CmpOne( 5 )=>"five", CmpTwo( 7, "seven" ) => "seven" ] for k,v in dict > @"$(k.number) => $v" end
Subscript túlterhelés

2 módszer van a tömbön keresztüli subscript accessor túlterhelésre. Egyik a getIndex__, ami olvasó módban terhel túl és 1 paramétert kap, másik setIndex__, ami író módban terhel túl és 2 paramétert kap.

class GrowArray( initSize ) content = nil init if initSize self.content = arrayBuffer( initSize ) else self.content = [] end end function len(): return self.content.len() function getIndex__( pos ) pos = self.absolutize( pos ) return self.content[pos] end function setIndex__( pos, value ) pos = self.absolutize( pos ) return ( self.content[pos] = value ) end function absolutize( pos ) // for simplicity, consider only integer and not ranges. if pos < 0 pos = abs( self.content.len() + pos ) end if pos >= self.content.len() self.content.resize( pos+1 ) end return pos end end arr = GrowArray() arr[1] = "one" arr[2] = "two" arr[3] = "three" inspect( arr.content )
Hívás túlterhelés és funktorok

A függvényhívás valójában a Falconban egy kifejezésnek tekinthető két operandussal: a hívott elem és a paraméterlista. Használhatjuk a call__ metódust felülbírálva a call metódust. Ilyenkor a call__ metódust funktornak nevezzük. A funktorok olyan függvény objektumok, melyeknek saját belső állapotuk és metódusaik vannak, de úgy hívhatók meg, mint a hétköznapi átlagos függvények. a következő példán egy funktorral iterálunk végig egy vektoron.

class gimmeNext( array ) array = array pos = 0 function call__() if self.pos >= self.array.len(): return oob(0) return self.array[ self.pos++ ] end end gn = gimmeNext( ["one", "two", "three"] ) while not isoob( data = gn() ) > data end

Automatikus string konverzió

Gyakran nagyon hasznos, hogy bármilyen objektumot string-gé tudjunk alakítani, és ezt kiíratni bárhova gyorsan. Ebben van segítségünkre a tostring metódus, ami egy string-gel tér vissza minden esetben. Használhatjuk ezt a gyorskiíratásnál is: ">".

object Test function toString() return "I am the test" end end // automatic transformation to string value = "Represent me " + Test > value // or also directly > "Directly: ", Test