logo
The Falcon Programming Language
A fast, easy and powerful programming language
Location: Home page >> Falcon wiki
User ID:
Password:
 

Falcon Wiki - Survival:Objects and 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.


Navigation
Go To Page...

Loading

Elapsed time: 0.082 secs. (VM time 0.072 secs.)