Classes and Methods

This feature is under heavy development at the moment, to be finished for version 10 of MillScript. This page documents the current state of the code in CVS.

The define keyword is used to introduce new classes, in a similar way to new functions. To define a new class you write:

define class C S enddefine;
      

But as with functions, classes are likely to be commonly defined, so there is a slightly shorter version for this too:

class C S endclass;
      

The class name C can be any normal name. The body of a class definition can contain slots, functions and methods. At the time of writing only slots are supported.

Constructors, construction and initialisation

MillScript does not have a new keyword like other object oriented languages. Instead constructors are just like other functions, which happen to return a new instance of their enclosing class.

Unlike Java, constructors cannot have the same name as their enclosing class; you must give them a different name. Also, unlike Java, you do not get a default no-argument constructor. So if you forget to provide a constructor your class will not be very useful. Generally, I would expect most constructors to be called by the name of the class, prefixed with the word "new".

Now, constructors are defined using the standard define syntax, using the keyword init. e.g. the following example defines a new class with a zero-argument constructor:

;-) class Sample
;-)   define init newSample() => enddefine;
;-) endclass;
There are 0 results
        

Unlike Java, a class doesn't introduce a new scope so the constructor has been defined at the top level(almost as if we hadn't included the extra class definition). So you can now call the new constructor just like any other function, e.g.

;-) newSample();
There is 1 result
;-) <object Sample>
        

Constructing a new instance of a class will cause it's slots to be initialised with their default values, then the constructor can adjust it's instances slots as required. e.g.

;-) class Sample
;-)   slot a = "hello";
;-)   define init newSample() => enddefine;
;-)   define init newSampleAlt( x ) =>
;-)     ^a := x;
;-)   enddefine;
;-) endclass;
There are 0 results
;-) var sample = newSample();
There are 0 results
;-) sample.a;
There is 1 result
"hello"
;-) var othersample = newSampleAlt( "a value" );
There are 0 results
;-) othersample.a;
There is 1 result
"a value"
        

You may be wondering why we introduce a constructor using the keyword init, rather than something which suggests a constructor. This is because these functions do not perform the construction of an instance of their enclosing class(e.g. Sample in the earlier examples), they mearly initialise the provided instance. The construction of the new instance of a class is done by the MillScript engine on your behalf.

As a result of this, you can use a MillScript initialiser, as I shall now call them, to re-initialise an existing object. The exact method for doing this hasn't been finalised yet, but will be in a subsequent minor release after version 10.

Inheritance

MillScripts class system currently supports single inheritance. Multiple inheritance will be introduced in subsequent versions. A class can inherit from another class using the extends keyword, e.g.

;-) define class One
;-) enddefine;
There are 0 results
;-) define class Two extends One
;-) enddefine;
There are 0 results
        

Now each class can have its own initialisers, as follows

;-) define class One
;-)   define init newOne() => enddefine;
;-) enddefine;
There are 0 results
;-) define class Two extends One
;-)   define init newTwo() => enddefine;
;-) enddefine;
There are 0 results
        

But imagine that class One has a large number of slots that require complex initialisation. This would require you to duplicate that complex intialisation in the initialiser for class Two. Fortunately, you can call the initialiser of a parent class from within an initialiser of a child class. e.g.

;-) define class One
;-)   define init newOne() =>
;-)     # Some complex initialisation goes here...
;-)   enddefine;
;-) enddefine;
There are 0 results
;-) define class Two extends One
;-)   define init newTwo() =>
;-)     # Avoid repeating the complex initialisation
;-)     # by calling One's intialiser as follows:
;-)     init newOne();
;-)   enddefine;
;-) enddefine;
There are 0 results
        

You can use this method to call the initialisers for each parent class. You can also include any number of expressions before and after calling a parent classes initialiser(unlike Java).

Slots

Slots are introduced inside a class definition using the slot keyword. e.g.

;-) define class NewClass
;-)   slot <em>N</em> = <em>E</em>;
;-) enddefine;
        

The slot name N can be any normal name and the initial value E for the slot can be any expression that returns a single result(just like in a variable assignment). You can leave out the initial value expression and the slot will have an initial value of absent.

One important point is that classes do not introduce a new scope, so the slot is defined at the same level as the class, e.g. as a global in these examples. This means that within a package no two classes can use the same slot name. Although you could make the same slot available via inheritance.

Slots are initialised when you construct a new instance of a class, creating an object. If you don't specify an initial value for a slot, it will default to absent, otherwise it will take the value you specified.

Slots are accessed in a familiar way, if you are already familiar with other object oriented languages. e.g. if we define the following class:

;-) define class Sample
;-)   slot front;
;-)   slot back = "back";
;-) enddefine;
        

We can access the slot values as follows:

;-) var s = Sample();
There are 0 results
;-) s.front;
There is 1 result
<absent>
;-) s.back;
There is 1 result
"back"
        

And we can update the value of a slot by assigning a value to it as follows:

;-) s.front := "new front";
There are 0 results
;-) s.back := "new back";
There are 0 results
;-) s.front;
There is 1 result
"new front"
;-) s.back;
There is 1 result
"new back"
        

Methods(a.k.a. single dispatch functions)

Methods are really just single dispatch functions, i.e. ones that dispatch on the type of its first parameter. A method could be introduced at any point in a program, currently they must be introduced in the body of the class definition. The general syntax is the same as for a function with a couple of exceptions. Methods are introduced inside a class definition using the define keyword. e.g.

;-) define class NewClass
;-)   define <em>^amethod</em> => <em>E</em> enddefine;
;-) enddefine;
        

The caret is a shorthand for a first parameter called this, so the equivalent would be

;-) define class NewClass
;-)   define <em>amethod( this :- NewClass )</em> => <em>E</em> enddefine;
;-) enddefine;
        

You can introduce overrides for methods using the override keyword. If you forget to use the override keyword, MillScript will redefine the method, forgetting all previous definitions(a warning will be issued). As MillScript doesn't have multiple dispatch functions yet, an override only makes sense when introducing a method override inside a different class.

;-) define class One
;-)   define ^amethod => "method One" enddefine;
;-) enddefine;
There are 0 results
;-) define class Two
;-)   define override ^amethod => "method Two" enddefine;
;-) enddefine;
There are 0 results
;-) define class Three extends Two
;-)   define override ^amethod => "method Three" enddefine;
;-) enddefine;
There are 0 results
;-) define class Four extends Two
;-) enddefine;
There are 0 results
        

In the above examples we've defined four classes and one method(with three bodies). We can call the method using an instance of any of the classes as follows:

;-) var one = One();
There are 0 results
;-) var two = Two();
There are 0 results
;-) var three = Three();
There are 0 results
;-) var four = Four();
There are 0 results
:-)
:-) one.amethod;
There is 1 result
"method One"
:-) two.amethod;
There is 1 result
"method Two"
:-) three.amethod;
There is 1 result
"method Three"
:-) four.amethod;
There is 1 result
"method Two"