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 operátorok túlterhelése
- összehasonlítások túlterhelése
- accessor túlterhelés
- hívás túlterhelése
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:
- add__ Overloads "+" operator.
- sub__ Overloads "-" operator.
- mul__ Overloads "*" operator.
- div__ Overloads "/" operator.
- mod__ Overloads "%" operator.
- pow__ Overloads "**" operator.
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:
- neg__ Overloads the unary prefix negation operator ("-" in front of a symbol).
- inc__ Overloads the prefix "++" increment operator.
- dec__ Overloads the prefix "--" decrement operator.
- incpost__ Overloads the postfix "++" increment operator.
- decpost__ Overloads the postfix "--" decrement operator.
Ö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