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.
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.
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 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 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"