Oforth Tutorial : Classes

I - Oforth classes

Oforth is an object oriented language : everything is an object and an object has a class. Oforth implements a single inheritance model and an object's class can be retrieved using #class on it.

Of course, it is possible to create new classes. Syntax is :

>parentClass Class new: class_name([mutable] att1, [mutable] att2, ..., [mutable] attn)


This will create a new class with class_name as class name, parentClass as class parent and att1, ... attn as class attributes.

Default behavior is immutability but, if a mutable keyword is specified for an attribute, the attribute, the entire class and all instances of this class will be handled as mutables. See "Immutability" chapter for more informations and restrictions on mutables/immutables objects

Once a class is created, it can be used at once :

>Object Class new: Customer(name, age)
ok
>Customer
ok
>Customer superclass
ok
>Customer name
ok
>Customer new .s
[1] (Customer) aCustomer
[2] (Symbol) Customer
[3] (Class) Object
[4] (Class) Customer
ok


Custumer is now an object of class Class and can be pushed on the stack. It responds to all methods of Class class and can be used as factory to create instances using #new. #new method creates an empty object (with all attributes set to null value) and then call #initialize method on this object.

Unlike many other object oriented languages, an Oforth class does not have class attributes.

Once a class is created, it is possible to :

  • Add method implementation for this class.
  • Add properties for this class.

Even if not required, a file, which name is class_name.of, holds class definition, methods implementation and properties for this class. But, this doesn't means that new implementations can not be added outside of this file. For instance, lang/Object.of holds implementations at Object level and new implementations are added to Object class in other files (Integer.of adds #isInteger implementation to Object class, which returns false).

II - Attributes

Attributes declared for a class allow to store of retrieve objets into a class instance. Syntax is :

  • @attribute_name : push attribute value on the stack
  • := attribute_name : removes the top of the stack and store it into attribute_name.

With Customer class example, we can write :

>Customer method: initialize(aName, aAge)
aName := name
aAge := age ;
ok
>Customer method: name @name ;
ok
>Customer method: << @name << " - " << @age << ;
ok
>Customer new("John", 24) .s
[1] (Customer) John - 24


#initialize method sets the two attributes with parameters, #name just returns name attribute value and #<< method: get the two attributes value.

III - Immutability

Immutability is the default behaviour. If a class is created without using mutable keyword into its attribute list, all instances of the class will be immutable objects. If an attribute is not declared as mutable, some contraints apply to this attribute. These constraints are checked at runtime.

  • An immutable attribute value can only be stored during #initialize method (or a method called by #initialize).
  • And this value must be an immutable object.

If one of those two contraints are not met, an exception is raised.

If an attribute is declared as mutable, those constraints does not apply and are not checked. On the other hand, all instances of the class will be treated as mutable objects. In this case :

  • These instances can't be the value of an immutable attribute.
  • These instances can't be the value of a constant.
  • These instances can't be shared with others tasks (see Parallelism tutorial).

All these rules are also checked at runtime.

IV - Oforth properties

When a class is created, it will be inserted into the class hierarchy. This implements a "is a" relation between the parent class and this class. Number "is a" Object, Integer "is a" Number, Channel "is a" Resource, ...

A property allows to implement a "is" relation, outside the class hierarchy. For instance, if we define the Comparable propertie, we can declare that Integer "is" Comparable.

A property is similar to a class :

  • A property can have attributes.
  • A property can implement methods.

There are three differences between a property and a class :

  • A property does not have a parent.
  • A property does not have class methods.
  • A property does not respond to #new : a property can't have instances.

Let's see how Comparable property is implemented (see lang/Comparable.of for the full version) :

Property new: Comparable
Comparable requires: <=

Comparable method: > self <= not ;
Comparable method: <(c) c self <= c self == not and ;
Comparable method: >= self < not ;
Comparable method: min(a) self a <= ifTrue: [ self ] else: [ a ] ;
Comparable method: max(a) self a <= ifTrue: [ a ] else: [ self ] ;
Comparable method: between(a, b) a self <= self b <= and ;


Line 1 creates the property.

Line 2 declares that, in order to be Comparable, a class must implement #<=

After this, Comparable property declares various methods. When a class is declared to be Comparable, instances will respond to all these methods as if they were declared into the class itself.

If we try to declare Customer class as Comparable, we will raise an exception :

Customer is: Comparable
[1/341f4] ExRuntime : Class does not implement required method <#<=>
ok
>


We didn't define #<= for Customer so it is not possible to declare it as Comparable.

Let's declare it, for instance, using its name : Customer1 will be <= to Customer2 if name1 <= name2... Why not.. :)

>Customer method: <= (c) c name self name <= ;
ok
>Customer is: Comparable
ok
>Customer new("John", 24) Customer new("Suzan", 20) max .s
[1] (Customer) Suzan - 20
ok


V - Polymorphism

A class is into a hierachy of classes and properties can be declared at each level of the hierarchy.

Some rules are required to choose the right implementation of a method for the object on top of the stack.

The search order for a method implementation is :

  1. If the receiver's class has declared an implementation of this method, this implementation is chosen.
  2. If not, search for an implementation is done into properties declared for this class into reverse order (the last one is checked first).
  3. If not, the superclass of this class is retrieved and the search loops to step 1) until the superclass is null.
  4. If no implementation is found during this process, an ExDoesNotUnderstand exception is raised.

So priority is given to object's class, then its properties, then next level into the class hierarchy, and so on until an implementation is found or the top of the hierachy (null) is reached.

VI - Class tests

Into a file, it is possible to declare tests to check assertions. This is done using test: syntax. For instance, into lang/Collection/String.of, some tests are defined. Let's take three of them :

test: [ "abcde" first 'a' == ]
test: [ "abcde" last 'e' == ]
test: [ "abcde" indexOf('c') 3 == ]


All instructions into a test must answer true. If the result is not true an exception is raised.

Default is to not run those tests, but if you run oforth using --t command line option, all tests are checked and an exception is raised if a test fails.

Tests are not limited to simple instructions. Functions, methods, ... can be declared into a test. If --t option is not set, those declarations are not processed.

Oforth Documentation

Current available documentation :

I - Tutorials

  • Oforth basics : here
  • Oforth syntax : here
  • Writing Oforth programs : here
  • Oforth classes : here
  • Oforth parallelism : here
  • Oforth node : Work in progress...

II - Reference

  • Lang package reference : here