Falcon Wiki - Survival:Objects and classes
- Objects and classes
- Falcon singleton objects
- Classes
- Stateful classes
Objects and classes
Falcon provides a powerful object model that includes multiple inheritance, class states, singleton objects and operator overloading among other features. In this chapter, we'll explore the Object Oriented Programming paradigm as it is implemented in Falcon.
Shortly we'll introduce Falcon objects before speaking about the more general cases of the classes. Objects are actually class instances whose class remains hidden in the virtual machine and is not accessible at script level.
Falcon singleton objects
An object is an entity that possesses a series of properties. A property may be seen as a "variable" that belongs exclusively to a certain object. Some of these properties hold a function that is made to operate on the objects they belong to; these special functions are called methods. For example, an object modeling a cash box will have a property that records the current amount of money being held in it and a method to deposit and withdraw cash. So, supposing we have already our cashbox ready to be used, we may do natural operations on it like this:
cashbox.amount = 50 // initial amount /* some code here */ object.deposit( 10 ) /* some code here */ object.withdraw( 20 ) /* some code here */ printl( "Currently, the cashbox holds ", object.amount, " Euros." )
The "." (dot) operator is also called the "object access operator", and is used to access a property or a method that is inside an object. Properties can written and read without any particular limitation; in some contexts, however, you'll prefer to have a method that knows how to handle the objects internally. For example, we can code a withdraw method that raises a warning if it finds the amount property has fallen below 0.
A sort of protected scope is provided when declaring properties starting with an underline _.
The simple cashbox object may be declared as follows:
object cashbox amount = 0 function deposit( qt ) self.amount += qt end function withdraw( qt ) self.amount -= qt end end
Our object has a quantity of money (initially set to 0), and two methods: deposit and withdraw. In the object statement, every property is simply declared by assigning a constant (initial) value to it; the object declaration supports only the assignment from constant statement, other than the method declarations. Methods are nothing more than the functions we have just seen; as the object statement body does not support function calls, the declaration keyword function is not necessary to state that a method is being declared; the name of the method followed by parenthesis, optionally including a parameter list, is enough to define it.
The cashbox example includes a new keyword: self. This keyword refers to the object that is currently handled by the method. It is necessary for every method to use the self object to access object properties: look at this example:
object cashbox amount = 0 function deposit ( qt, interest_rate ) amount = qt * interest_rate self.amount += amount end /* other things */ end
As this example explains, methods may need local variables too, and they are simply defined by assigning some value to them. So, to distinguish between the "amount" local variable and the "amount" object property, the self object alias must be used.
Here follows a more formal definition of the object statement:
object object_name [ from class1, class2 ... classN] property_1 = expression property_2 = expression ... property_N = expression [init block] function method_1( [parameter_list] ) [method_body] end ... function method_N( [parameter_list] ) [method_body] end end
The method declarations may be shortened with the colon sign (":") if they hold only one statement, exactly as the function declaration.
We'll see later on that the class statement has a much more flexible way to define object entities, but the object statement is meant to provide a fast but clean way to define simple objects that are unique in their instances.
Once an object is defined, it is not possible to add new properties or new methods. Anyhow, it is possible to change its methods and properties at will; look at this example:
function new_deposit( qt ) if self.amount + qt > 5000 printl( "Sorry, you are too rich to be a programmer" ) else printl( "Ok, we authorize you to have that money" ) self.amount += qt end end old_deposit = cashbox.deposit cashbox.deposit = new_deposit cashbox.deposit( 10000 ) // we are now forbidden to do that. old_deposit( 10000 ) // but the old method still works printl( cashbox.amount ) // will be initial amount + 10000
Function names can be seen as items, and assigned to a property (or to any variable in general); it does not matter if that property was originally a method or not. In fact, we may have even assigned a function to cashbox.amount, but in this context it would not make much sense. Also, it is possible to store a method from an object in a variable; the variable also records the object from which the method was from, and when used to call the old method it will feed the old object in self. Finally, as you can see, functions also may access the self object; it is possible that they are assigned to an object method, and in that case, the self item will assume the value of the original object.
If a function call is not performed via a method, that is, if it's a function that is just called as we have done before this paragraph, the self item will assume nil value. So, it is possible for a function to know if it's being called as a method or not:
/* Data definitions */ function maybe_method() if self = nil printl( "I am a function" ) else printl( "I am a method" ) end end object test property = 0 end /* Main program */ maybe_method() // prints "I am a function" test.property = maybe_method test.property() // prints "I am a method" maybe_method() // prints "I am a function" again
We are going to see how to further configure the actions of possibly methods or object users in the next paragraph.
The "provides" and "in" operators for objects
As object structures may be configured independently from predefined formal definitions (also known as classes), it is necessary to provide a way to have some information about object internals at runtime, so that generic object users have a minimal ability to configure themselves depending on object structure. In advanced object oriented languages, it is generally possible to know which type of object is currently being handled; in Falcon, although this information is present, it may not be enough to manipulate objects. Some of them, in fact, are just "objects", to which any property may be attached.
The provides keyword is a relational operator that assumes the value of 1 (true) if a certain object (first operand) provides a certain property (second operand). Let's redefine the new_deposit function/method to act a little smarter. Provides never raises an error; it will just be false when the first operand is not an object:
function new_deposit( qt ) if not self provides amount printl( "This should have been a method of an object with an amount" ) return end if self.amount + qt > 5000 printl( "Sorry, you are too rich to be a programmer" ) else printl( "Ok, we authorize you to have that money" ) self.amount += qt end end
Now, if you try to call this function directly from the program, like this:
new_deposit( 1000 )
the function will refuse to access the self object, which in fact does not exist.
We have already seen the in operator in action: it's the relational operator that scans an item for some contents: substrings in strings, items in arrays and keys in dictionaries. The in operator is so flexible that it can also be applied to objects and properties.
While the provides operator is specifically meant for objects, and is able to understand that its second operand should be a property, in is more generic and doesn't have this ability. As a side effect, in is slower than provides, but it is more flexible. The in operator will check for a string to be the same as a property name in the second operand, if it is an object; the function we have just seen may be also be written as:
function new_deposit( qt ) if not "amount" in self printl( "This should have been a method of an object with an amount" ) return end /* the rest as before */ end
One first thing to note is that in would also work if the second operand were a string, an array or a dictionary, in which "amount" may be found. So, except for the special case of self which may only be an object or nil, it is necessary to check for the second operand to be an object (or to be sure of that) before trying to use in. The other thing to be noted is that, as in does not takes the value of the first operand as a literal, the value to be checked may be also set in a variable:
function new_deposit( qt ) if some_condition property = "amount" else property = "interest" end if not property in self .... end ... end
The init block
As you have probably noticed, the [init block] that has been introduced in the formal object declaration has not yet been explained. Objects can have an initialization sequence; however the explanation about how to use the init block in objects must be postponed, as it is first necessary to introduce the classes.
Classes
Defining an standalone object can be useful when there is specifically the need of one exact single object doing a very particular task; they are useful when the modeled object is so different from the other ones that it have very little in common with them (or nothing at all). However, in real world programs, this is a very rare case. Most of the time, there will be many points in common among different objects; it's natural for the human mind to categorize the reality so that every item of its attention is framed into a class. A class is then a schema, a basic description of the characteristics (properties) and behavior (methods) of a possibly infinite set of objects that may be represented or nearly approximated with this schema. The objects having those characteristics are called "instances", and the process to create an object from a class is called "instantiation".
Falcon classes are a mix of data (which is mainly extracted by the compiler at compile time implicitly) and code that can be used to instantiate objects. Classes are defined this way:
class class_name[ ( param_list ) ] [ from inh1[, inh2, ..., inhN] ] [ static block ] [ properties declaration ] [init block] [method list] end
The property list of a class contains the name of the class properties and their initial values. Any valid expression can be assigned to the properties. If there isn't a meaningful value for an expression at initialization time, just use the nil value. Actually, the virtual machine and the compiler will work jointly to minimize the required initialization time, so that properties being given nil are not really assigned a value in the virtual machine loops during object creation, sparing time.
class mailbox( max_msg, is_public ) capacity = max_msg name = is_public ? "" : "none"; messages = [] a_property = nil end
The instantiation process can be performed in two ways. One is that of calling the class as it were a function; the effect will be that of create an empty object that will be fed in the class constructor, like this:
my_box = mailbox( 10, false ) printl( "My box has ", my_box.capacity, "/", len(my_box.messages), " slots left.")
The other way is that to use the object semantic to expand a base class or initialize normally uninitialized members:
object my_box from mailbox( 10, false ) name = "Giancarlo" end
As classes are considered generically "callable items", it is possible to manipulate them more or less like if they were functions. For example :
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.")
To define a method in a class, the function keyword must be used:
class mailbox( max_msg ) capacity = max_msg * 10 name = nil messages = [] function slot_left() return self.capacity - len( self.messages ) end end
At times, the initialization of an object requires a little more than just assigning some expressions to properties. In the cases, there are two options. One is to write a method that will be called separately to complete the initialization of the object once it is already created. Although this is a clean way to do the job, this may be painful when creating singleton objects. The other method is to use the explicit initializer, also known as explicit constructor, the init block.
The explicit initializer works as a method, with the exception that it is called during object initialization, right after property assignments; it receives the parameters that are declared in the class statements, so it cannot be given any other parameter declaration. This is an example:
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
Properties can be declared static. By declaring a static property, it becomes shared among all the instances of a class. As the property is shared among many objects, it should not be initialized with dynamic data. The property will be initialized exactly the first time an object of that class is created; any update to the property will take effect both into all existing objects of the same class and into the new instances that are created afterwards.
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() );
The init block can have a static block that works very like function static blocks. However, the init static block is only called the first time an object of a certain class is instantiated. This allows preparing setup of an environment that will be then used by objects of the same class.
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 )
The output will be:
Class initialized Object number sequence: 1 2 3
Methods with static blocks
Static blocks can be declared in methods as they can be declared in normal functions; however, their behavior can be a bit counterintuitive. A method with a static block will enter and perform it only the first time the method is called for every instance of the parent class. Similarly, variables declared in the static block of a method are actually class static, and they are modified by all the instances of a certain class.
If there is the need to execute a part of a method only the first time that method is executed for a certain object, use a property, or even better, an attribute (see page74) and check its value with a normal branch.
Classwide methods
It's also possible to access a method from inside a class. Instance-less methods, or classwide methods, can be called directly from the class names, but, as they do not refer to any instance, they cannot access the self object.
Declaring and accessing methods directly from classes can be a simple way to define a namespace where to access normal functions. For example :
class FunnyFunctions function a() > "This is funny function a" end function b() > "This is funny function b" end end FunnyFunctions.a() FunnyFunctions.b()
Classwide functions can be seamlessly merged with normal instance-sensible functions; the only requirement for a function to be callable not just from an instance, but also from the class, is that id doesn't access the self item.
Property accessors
Since 0.9.4.4
Accessors are hidden methods that intercept access on properties. Falcon accessors work on "virtual" properties. In short, you don't have to declare any property for the accessors to work on that; just declare the accessors for read, write or both the operations, and a "phantom" property will be created for you.
Write accessors are declared by creating a function named __set_propname, while read accessors are declared with __get_propname. It's two underline characters "_" followed by "set" or "get", another underline and the property name you want to mask.
For example, this creates an accessor for the mean property, which returns the mean of a series.
class Series( values ) values = values function __get_mean() sum = 0 for i in self.values: sum += i return sum / self.values.len() end end s = Series( [14,53,18,8] ) > "The mean is: ", s.mean // 23.25
As there isn't any __set_mean function declared in the class, mean is considered a read-only property; trying to set it to a different value will cause an AccessError to be raised.
The __set_ accessor receives as a single parameter the value that should be set in the property. The following code ensures that the property value is a number in the 0..100 range.
class Guarded( value ) _value = 0 // We can't declare a "value" property, but... init // we can initialize it via accessors here: self.value = value end function __set_value( v ) if v.typeId() != NumericType: raise "Assigned not numeric value to 'value'" if v < 0 or v > 100: raise "Assigned value out of range to 'value'" > "Setting value ", v self._value = v end function __get_value(): return self._value end g = Guarded( 10 ) > "Initial value: ", g.value g.value = 30 > "Value is now: ", g.value
It is also possible to declare just the __set_ accessor for a given property. In that case, the property becomes "write only" and can't be read back. This can be useful to create "plug points" that alter the host object depending on the data they receive, like in the following example:
class WOnly() data = nil init self.randomSeed = (seconds() * 1000) % 1000 end function __set_randomSeed( seed ) randomSeed( seed ) self.data = random() end end > "New random number: ", WOnly().data
Notice that it's not possible to create a real property with the same name of a property guarded through accessors. Doing so will cause a Syntax Error for duplicate property name in class declaration to be raised.
Multiple inheritance
One class (or one object) can be derived from multiple classes. The resulting class (or object) will have all the properties and methods of the subclasses. For example:
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" )
As the example shows, the initialization of the child class is performed after the initialization of its parents, which is performed following the order in which the inheritance are declared.
The instance will have all its functions and methods, and all the methods declared in the base and child classes.
Base method overriding
It is often desirable that subclasses change the behavior of a base class by re-defining some methods. Creating a new method (or property) in place of a method (or property) with the same name and parameters declared in a subclass is called overriding.
Once overridden, all the references to that property or method will receive the new member provided by the subclass. Consider this example:
class base function mth(): > "Base method" function callMth(): self.mth() end class derived from base function mth(): > "Derived method" end
When an instance of the base class is created, it's mth method member is the function that prints "Base method". When an instance of the derived method is created, its mth method prints "Derived method". The derived instance inherits the callMth method. As now the mth member is the one provided by the derived class, the call self.mth() in its body will actually call the method in the derived class.
Continuing the above example:
base_inst = base() base_inst.mth() // prints "Base method" base_inst.callMth() // again, prints "Base method" derived_inst = derived() derived_inst.mth() // prints "Derived method" derived_inst.callMth() // again, prints "Derived method"
It is however possible to access the base method (or property) of a derived object. For example, suppose that a class doesn't want to change a behavior of the base class, but just to extend it. Then it has to call the base class method before doing or after having done special processing. To access the method in the base class, the derived one must prepend the name of the base class to the name of the method (otherwise, it would call itself). In our case, the base class name is just base, so if we want to provide some callBase method in our derived class, it must be rewritten as follows:
class derived from base function mth(): > "Derived method" function callBase() > "pre-processing" self.base.mth() > "post-processing" end end derived_inst = derived() derived_inst.callBase()
This will result in the base class method being called instead of the derived one. The base class methods and properties may also be accessed from the main code directly; it is possible to append the base class name after the instance name to reach the base class.
In the case of multiple inheritance, overriding may also happen among subclasses; if two or more subclasses have a method with the same name, the classes being instantiated last are the ones overloading former methods. In the following example, the mth method is offered by both subclasses:
class first function mth(): > "First method" end class second function mth(): > "second method" end class derived from first, second end instance = derived() instance.mth() // "second method" instance.second.mth() // again "second method" instance.first.mth() // "first method"
Private members
In object and class declarations, member names starting with the _ underline symbol can be accessed only through the self object. This means that only the object, or the class that declared them and its direct descendants, are able to access them. In case some of those properties or methods are needed outside the instance, they must be returned (or modified) using an accessor, that is a method returning or changing the property, as in the following example:
object privateer _private = 0 function getPrivate(): return self._private function setPrivate(value): self._private = value end
However, private members should be used mainly to store data that should not visible by other parts of the program; for example they may be used to store an internal state or counter.
Subclasses can access private members declared by parent classes, but they can access them only through the self object. It is not possible to access a private member through a base class; once overloaded, the instances can access only the topmost overloading of the private member.
Operator overloading
Falcon objects (and class instances) provide several callback hooks that are called when the virtual machine applies operators on them. They all work similarly, by providing a method in the class or object definition, and they all are relative to things done to the target instance. In short, the virtual machine looks at the type of the first operand in an expression; if the operators are left-associative (i.e. +, *, - etc), the first operand is the leftmost. If that operand is an object or instance, and if it provides an appropriate overloaded method, that method is called and the other operands are passed as parameters.
For example, that's how "+" operator can be overridden in a class:
class OverPlus( initValue ) numval = initValue function __add( operand ) return OverPlus( self.numval + operand ) end end op = OverPlus( 10 ) nop = op + 10 > nop.numval //: 20
Operator overloads are prefixed with "__" (two underlines) to indicate that they are special, and to provide some "namespace protection" as names like "add", "sub" and so on may be very common and used by developers for other reasons.
Before version 0.9.6, the underlines were postfixed as in "add". If you have an older version of Falcon, remember to move the prefix after the function names.
Of course, a class may want to check the type of the operand, and eventually work gracefully with compatible types. For example:
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
Operator overloads are not bound to be read-only. If consistent, it is possible also to modify the values in self, as in:
... function __add( operand ) self.numval += operand return self end ...
Also, the return value is totally arbitrary; you may want to return a different value. This allows you to create so called manipulators, having special significance in expressions as in the following example:
object saver function __add( operand ) > "Saving value... ", operand // save it to a file return operand end end persistent = saver + 100 > "data is ", persistent // 100
This model has two limitations: first, it's not possible for the second operand to take control of the operator overloading. It's the first operand that is in control of overloading. Second, all the operator overloading is performed in atomic mode. This means that the virtual machine is not interruptible while inside those callback methods, and that functions that may interrupt or suspend the workflow of the virtual machine are forbidden. In short, calling sleep() from inside __add would raise an error, returning the VM to non-atomic mode and dumping all that's done inside callbacks.
Operators overloading is divided into the follow:
- mathematical operators overloads.
- comparison overloads.
- accessor overloads.
- call overloads (functors).
Mathematical operator overloading
They are unary or binary operators overloading, working quite alike. Binary operators overloads gets a single parameter (the second operand) and are usually, but not necessarily, expected to return a value of the same type of self, or of the second operand:
- __add Overloads "+" operator.
- __sub Overloads "-" operator.
- __mul Overloads "*" operator.
- __div Overloads "/" operator.
- __mod Overloads "%" operator.
- __pow Overloads "**" operator.
Binary operator overloading works also when used as self assignment. For example, += operator calls the __add overloader and then stores the return value in the same variable that was used as self. For example:
class OperOver( num ) val = num function __add(v): return self.val + v end o = OperOver( 10 ) o += 5 inspect(o) // a numeric 15 value
Unary operators overloads receive no parameters; they are the following:
- __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.
Comparison overloading
Comparison operators, namely <, >, <=, >=, == and != all refer to the same overloaded method: compare. Notice the absence of the "__" prefix. This is both because of historical reasons and because compare doesn't exactly overload operators, but serve a more complex purpose with a different semantic.
The compare method is bound to return a number less than zero, zero or greater than zero if the self item is respectively less than, equal to or greater than the comparand item, passed as parameter. Contrarily to mathematical operators, the compare method should not raise an error in case the items are not comparable: Falcon VM prefers to have a strategy to sort all the items, even when sorting has no physical reason. When the compare function hasn't any mean to determine a sorting order, it should return nil; this informs the virtual machine that the overload gave up, and that the default ordering algorithm should be applied: items of different kinds are ordered based on the value of their typeId() methods, and items of the same kind are checked based on the place they occupy in memory.
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]
The compare method is not used just to resolve the comparison operators; it's used also when a default ordering method is needed, for example, in dictionary key insertions and searches, or in arraySort() call.
Important: as the compare method is invoked only on the first item of pair ordering checks, all the members of a collection to be sorted. For example:
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
In this example, the items stored in the dictionary as (ordered) keys are different, but they all agree on the way they must be ordered (by sharing the same ordering function).
Subscript overloading
Access through the array subscript accessor () can be overloaded through the following methods:
- __getIndex Overloads in read mode. Will receive 1 parameter (index), and should return an item.
- __setIndex Overloads in write mode. Will receive 2 parameters (index, value), and it is supposed to return the stored item.
Be careful about the fact that the index parameter is not necessarily just a number; it may be any Falcon item, as the overloader may wish not just to implement an array semantic.
The following example implements a self-growing array, that enlarge itself when accessed out-of-bound.
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 )
Call overloads and functors
In Falcon, a function call is actually considered an expression with two operands: the called item and the parameter list. Objects can provide a __call method to overload the call operator (...). Objects implementing a __call method are called functors. Functors are function objects, that is, objects that have their own state and internal methods, but that can be called as regular functions.
In the following example, we use a functor to iterate through a vector, returning an oob(0) (functional break) when the scan is complete.
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
The interesting part, the gn() call, is actually resolved into the invocation of the __call method in the functor.
Parameters passed to the call functor are translated into parameters for the __call method:
object callTest function __call( a, b ,c ) > "A: ", a > "B: ", b > "C: ", c end end callTest( 1, 2, 3 )
And of course, it's also possible to access the parameters through the variable parameter convention:
class promptPrinter( prompt ) prompt = prompt function __call( /*var params */ ) for i in [0:paramCount()] > self.prompt, parameter(i) end end end pp = promptPrinter( "::: " ) pp( "Hello", "world", "there!" )
Automatic string conversion
It is often useful to turn an object into a string representation; this is done also by the virtual machine in several occasions, as when adding the object to a string, or when printing through the ">" fast-print operator. To provide a suitable string representation, an instance may provide a toString method, returning a string.
For example:
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
Notice that
> Test + "..."
wouldn't work as expected: in that case, the __add overload is used instead.
Object initialization sequence
Objects, or better, items automatically created with the object keyword, are actually instantiated from a class that is not accessible to the script. This class is synthetically created and stored in the module where the object resides; as the module is linked in the Virtual Machine, an instance is created as if the script called the constructor. So this code:
class my_class property = some_function() ... end my_object = my_class()
and this code:
object my_object property = some_function() ... end
are nearly equivalent. Nearly... but not quite, because the constructor of the stand alone objects, and the complex initialization involving their property, is completely managed by the Virtual Machine before the first instruction of the main script is executed.
In other words, even if a module does not provide a main code, and even if it's not the main script, the VM may execute some code from it to initialize the objects it declares.
This provides a powerful and easy way to configure Falcon modules as they are loaded in the virtual machine. However, this may actually bring two orders of problems.
The first problem is that the VM must be correctly configured for debugging, limiting script execution time, controlling script memory consumption and so on before the script you actually want to launch is launched; at link time, VM may execute some code, or even a lot of code, so the VM must be set up correctly before the link sequence is initiated. But this is a problem that involves the embedders, and it's a bit outside the scope of this guide.
The second order of problem is that the order of object initialization is undefined. Of course, objects declared in a module A that is loaded by a module B are all initialized before the first object from B gets the chance to be initialized, but the order by which the objects in A are initialized is undefined.
Usually, this is not relevant for the user, unless some of the objects in the code refer to some other object in the module. Consider the following example:
object first list = [ " one " , " two " , " three " ] end object second sum = "" init for elem in first.list self.sum += elem end end end
This code may work or not. By the time second is initialized, the list in first may have already been initialized or not. It's a 50% guess.
Luckily, the VM does some work for us, in the forecast that an item may reference some other item in the initialization step.
First of all, before the first initialization is performed, all the items of the module are correctly created, and their methods are readied. In this way, accessing properties of an arbitrary object is possible, and its content will either be a valid method, nil, or the fully readied value, in case the object has already been initialized. Filling all the non-method properties with nil allows eventual callers, or the object itself, to understand if the initialization routine has been called or not.
The second thing the VM does for us is avoid re-initialization. So, if some property of some object is initialized externally before the VM has the chance to call the automatic initializer, the already provided value will be saved.
Given these two services, what the implementer must do to solve this situation is called initialization on first use idiom (this is actually C++ terminology, but also works great here).
The init method of second, referencing first, should be kind on the first object by calling some method of it that can set up the object in place of the automatic initializer. Even better, if possible access to the required property should be performed only via a method that is meant only for this reason. Those special methods are called accessors.
Here we see an accessor using the initialization on first use idiom in action.
object first list = nil init // forces initialization if this has not still happened self.getList() end // accessor... function getList() // ... using init on first use idiom. if self.list = nil : self.list = [ " one " , " two " , " three " ] return self.list end end object second sum = "" init // Reading first.list via the accessor for elem in first.getList() self.sum += elem end end end printl( "second.sum: ", second.sum )
The elegant part of this idiom is that the necessity to use it is present only during the initialization step, that is, in the init block of objects, or in their property declaration clauses. Once the link step is performed, (provided the init block and/or the property declarations do their jobs correctly, as in the example for object first), the properties are correctly set up, and there isn't the need to go on using the accessors to read the properties from an object.
The correct initialization of objects can be performed by following these simple rules:
As said, the main module and objects in other modules don't need to access other object properties via accessors with init on first use idiom; however, be careful, as init routines and property initialization clauses may call functions that in turn may reference uninitialized objects.
So, if possible, avoid creating objects that need other objects being declared in the same module during initialization phase. If you have to, if possible, isolate them in a separate module so that you know that those object are correctly initialized, or be prepared to use the accessor instead of the property within all the functions in the same module where the target object is located.
Classes in functional sequences
When evaluated in functional contexts, classes generate instances as if they were directly called. For example, you may fill an array of instances through a functional loop like the following:
class ABC( ival ) id = ival end arr = [] 1.upto(10, .[ arr.add .[ABC &1]] ) inspect( arr )
Stateful classes
Since 0.9.6.2
Some kind of problems are better solved through "agents" or "machines" which are invoked to respond to certain events or perform certain tasks, and provide a different behavior under different situations.
A scared bird
For example, let's take the example of a bird that needs to eat and is scared from people. The bird can be in one of three states: famished, quiet and scared.
Initially, the bird is "quiet".
class Bird famine = 0 init self.setState( self.quiet ) end //...
The bird can chose, each time, to be idle, eat or flee.
function eat() > "The bird eats." self.famine = 0 end function idle() > "The bird is idle" ++ self.famine end function flee() > "Flap flap flap..." ++ self.famine end
We present the current situation to the bird via an "onEvent" callback, in which we'll tell the bird if there's food and/or people around.
When the bird is quiet, it will ignore food, and get scared if a person approaches. After three turns, it will get famished again.
[quiet] function onEvent( food_nearby, people_nearby ) if people_nearby > "The bird is getting nervous." self.setState( self.scared ) else self.idle() if self.famine > 3: self.setState( self.famished ) end end end
When it's scared, it will flee when people is around; otherwise it will return to be quiet or famished depending on the famine level.
[scared] function onEvent( food_nearby, people_nearby ) if people_nearby self.flee() else self.setState( self.famine > 3 ? self.famished : self.quiet ) end end end
when it's famished it will eat and get quiet, if there is no people around. Otherwise it will get scared.
[famished] function onEvent( food_nearby, people_nearby ) if people_nearby > "The bird is getting nervous." self.setState( self.scared ) elif food_nearby self.eat() self.setState( self.quiet ) else self.idle() end end end // end class: end
We can now test this code with a simple sequence, like the following:
quail = Bird() for i in [0:12] food = random( true, false ) people = random( true, false ) > @ "Step $i: Food=$food, People=$people" quail.onEvent( food, people ) > "The quail is in state ", quail.getState() end
States definition
As we intuitively seen in the previous paragraph, states are subsets of alternative methods that can be active at any time on an object. Fromally...
class ... [class declaration] [<state name>] [state declaration] end ... [<state name>] [state declaration] end end
Each state declaration consists of zero or more function definition. It is not necessary that all the states declare the same functions. If a state doesn't declare a method that is declared in another state, when entering it the undeclared method is left unchanged. So if state A declares method one and two, while state B declares only method two, when moving from state A to B, method one is left untouched.
Method names declared in states can be present also in the class definition. When a state is not yet applied, they are reachable and behave normally. Once a state redefining them is applied, the methods declared in the generic class part are shaded (not anymore reachable in the host object), but they are still available if accessed statically or through the base class name.
States are actually represented as string properties; so an expression like className.state_name where state_name is the name of a state, resolves exactly in a string "state_name".
The BOM methods setState and getState are normal methods. This allows to create synthetically state names and apply them at runtime.
Transition functions
Two special functions, called __leave and __enter, are called back respectively before a state is applied (with the previous state still active), and after entering the new state. State transition is performed as indicated in this pseudocode:
function setState( new_state ) value = nil if self.__leave is callable value = self.__leave( new_state ) end old_state = current state apply new_state if self.__enter is callable value = self.__enter( old_state, value ) end return value end
Both methods are optional and both can return a value (which is then returned by the setState method). __leave is called in the previous state, and receives the target state name as the parameter, while __enter is called after the new state is applied, and careceives the states that was previously active.
If both the methods are provided, __enter receives also the value returned by __leave as second parameter; otherwise, if only __leave is provided, its return value is returned by setState to the caller.
In our bird example, we wanted to notify the user about the fact that the bird is getting nervous when it passes into the scared state. Adding this method to the scared state:
... [scared] function __enter( origin, lr ) > "The bird is getting nervous" end ... end ...
We can now be sure that this code is executed each time the state is entered.
Notice that controlling the return value of the setState() function it is possible to implement Mealy automata models (a mathematic definition of a finite state machine where output values obtained from input sequences are bound to state transitions).
State inheritance
State definitions are are inherited subclasses. Subclasses can override parent classes states as a whole; an overridden state will allow the subclass to define a new set of functions, or none at all, in the given state.
In the following example the child inherits the A state, overrides the B state changing its functions, empties the C state and declares a new D state:
class Base [A] function callme(): > "A from base" end [B] function callme(): > "B from base" end [C] function callme(): > "C from base" end end class Child from Base [B] function callme(): > "B from derived" end [C] end [D] function callme(): > "D from derived" end end c = Child() c.setState( "A" ) c.callme() c.setState( "B" ) c.callme() c.setState( "C" ) // still the same as B c.callme() // as C state is deleted c.setState( "D" ) c.callme()
Multiple inheritance is applied to states with the same rules as it is applied to method and properties: the rightmost child values override the left ones.
The init state
A special state, called "init", can be declared to be applied immediately after the instantation of an object, and exactly before the object is returned to the user.
Substantially, it's like setting the state at the end of the last executed init block in the topmost child.
class HavingInit init > "Instance created" end [init] function callme(): > "from initial state" end end h = HavingInit() a.callme() // already in init state
It is possible to re-enter the init state after having abandoned it via instance.setState( "init" ).
Init state and __enter method
The __enter method is in the init state is not useful just to manage transitions re-entering in the init state after having left it. As __enter is called automatically when a state is applied, and the init state is applied to an instance before returning it to the caller, the __enter method in the init state is automatically called after an instance is completely prepared and setup.
This is interesting because it provides a callback that can be set by base classes to provide common post-initialization code for a whole hierarcy.
Consider the following example: instances are automatically stored in a global dictionary under a key that is filled by child classes. Normally, it is necessary to call a virtual function of the base class from the client code after initialization, but whit __enter from init state you can set this behavior in the base class:
globdict = [=>] class Base init > "Base init" end [init] function __enter() global globdict > "Enter method called for ", self.type if self.type notin globdict globdict[self.type] = [self] else globdict[self.type] += self end end end end class TypeA from Base type = "Type A" init > "TypeA init" end end class TypeB from Base type = "Type B" init > "TypeB init" end end TypeA() TypeA() TypeB() inspect( globdict)
As you can see from the sample, the base initializer is called before the child one, but then the __enter method is applied after child initialization.