Classes
Classes are MiniD's mechanism for declaring user-defined types. They are in some ways simpler than classes found in static languages such as C++, D, and Java, but are more dynamic and can emulate much of the behavior of more complex object models.
Classes, like everything else in MiniD, are first-class values. You can put them in variables or other data structures, pass them to functions, return them, etc.
Whereas a class defines a type, an instance is an instantiation of that type. Instances are associated with a single class and inherit all the data and behaviors defined in that class.
A Simple Example
Here's a very simple class, based on the 'makeCounter' function shown in the last section:
class Counter { value = 0 this(from: int = 0) :value = from function next() { local ret = :value :value++ return ret } } local c1 = Counter() // creates a counter that starts from 0 local c2 = Counter(10) // creates a counter that starts from 10 writeln(c1.next()) // prints 0 writeln(c1.next()) // prints 1 writeln(c2.next()) // prints 10 writeln(c2.next()) // prints 11
OK, there's a lot going on here.
First we declare a class named Counter. Class declarations are similar to those found in other languages. And just like function declarations, they are syntactic sugar for variable declarations. This code is the same as declaring a variable named "Counter" and assigning the result of a class literal into it.
Inside the class declaration are the members. "value = 0" declares a data field named "value" with a default value of 0. You don't have to give data fields default values; if you don't, they'll default to 'null'.
Next is the constructor. The "this(...)" syntax is borrowed from D and is syntactic sugar for the longer "function constructor(...)". That is, a class's constructor is just a method named "constructor". The constructor is called when the class is instantiated.
Inside the constructor, you'll notice the strange-looking ":value = from". What is that colon? It's a shortcut. Remember when we talked about the 'this' parameter that is implicitly passed to all functions? Well now we're starting to use it. Similarly to Lua or Python, MiniD requires you to explicitly access members contained in 'this'. But typing out "this.x", "this.value", "this.func()" all the time gets tiresome, so MiniD introduces the colon as a shortcut for "this.". That is, ":value" is exactly the same as writing "this.value". The same applies for functions.
Last in the class is the class's only other method, 'next'. This gets the next number in the sequence. Again, we're using the colon syntax as a shortcut for "this.". Another thing to note is that class methods are another kind of syntactic sugar. Class methods are no different from data fields - methods are just data fields which happen to hold functions. A class method declaration is the same as declaring a data field with a function literal as its initializer.
After the class declaration we make two instances of the class. Notice that instantiation of a class just looks like a function call. What this does is it creates an instance and then calls the "constructor" method on that instance, if any is defined, with all the parameters that were passed to the class. We then use the two instances to print out some values, and see that they work as we expect.
Seeing Through the Sugar
I've mentioned a lot of syntactic sugar going on here. Sometimes it can be helpful to see what is actually happening behind the scenes. The above code is semantically equivalent to the following:
global Counter = class Counter : Object {} Counter.value = 0 Counter.constructor = function constructor(from: null|int) { from ?= 0 this.value = from } Counter.next = function next() { local ret = this.value this.value++ return ret } local c1 = Counter() // creates a counter that starts from 0 local c2 = Counter(10) // creates a counter that starts from 10 writeln(c1.next()) // prints 0 writeln(c1.next()) // prints 1 writeln(c2.next()) // prints 10 writeln(c2.next()) // prints 11
I've removed all the syntactic sugar. You can see how the default parameter is handled in the constructor (as well as the fact that it's named "constructor"), and all the colons have been replaced by explicit references to 'this'. Another thing to notice is the ": Object" when the class is created. That's coming up in the section on inheritance.
Looks pretty awful, huh! Good thing we have sugar ;)
Some Details on Fields
The above example looks easy enough, and, well, it is. Most of the time, you can use MiniD's classes as you would in any other language and they'll just work like you expect. But it's good to know some of the nitty-gritty details.
MiniD's classes and instances use what is called differential inheritance. What this means is that when you create an instance of a class, the instance initially has no data in it and therefore takes up very little memory. When you get the value of a field out of such an instance, the value will come from the class, since the instance does not have any data. Only when you set the value of a field in an instance does it gain data and start growing in size. This behavior leads to a very memory-efficient object model.
It can also lead to some surprises. There is a function in the base library called "fieldsOf" which takes a class or instance and returns the namespace which holds its fields. Consider the following code:
class C { x = 5 y = 10 } local c = C() writeln(c.x) // prints 5 dumpVal(fieldsOf(c)) // prints...?
Notice that c.x is legal, and gives 5, as you would expect. But what does the last line print? dumpVal is a function in the base library which prints out representations of complex types; for example, given a namespace, it will print out the name of the namespace as well as the names of all its members. But in fact, this code prints "namespace C { }". That is, there are no fields in c. How are we able to access x, then?
It's because x is in C, not in c. When you access a field out of an instance or class, and that field does not exist, the lookup then continues on with its parent - either the class that the instance was created from (like in this case), or the base class of a class. So we can get the value of c.x, even though c doesn't have any fields!
Once we write to c.x, however, c will gain a field:
c.x = 15 // prints "namespace C { x } dumpVal(fieldsOf(c))
But notice there is still no 'y' field in c.
In order to get all the fields that are accessible from an object, you can use the "allFieldsOf" function defined in the base library.
Inheritance
Many times you'll have a set of object types which have some kind of relationship, such as common behavior. This is handled using inheritance in most object models, and MiniD has inheritance too. It's very simplistic: there is only single inheritance, and there are no interfaces. This isn't actually much of a problem in practice. MiniD's classes are very dynamic, and you can implement or emulate multiple inheritance a few ways.
Furthermore, interfaces are largely unnecessary, since most of the time you'll be using duck typing (which comes from the phrase "if it walks like a duck and it quacks like a duck, it must be a duck"). This is a form of typing which does not use the identity of an object to determine what capabilities it has, but rather just using those capabilities. If the object doesn't support an operation, you'll get a runtime error. That being said, you can still come up with some simple checking systems to do some cursory tests to ensure that given types support certain capabilities.
Almost all classes in MiniD inherit from one other class. Class inheritance is specified similarly to in C++ and D - by writing the base class after a colon after the class's name.
If you don't explicitly inherit from any class, the default is to inherit from Object. Object is a special class defined by the standard library and which is in the global namespace. Object is the only class in MiniD which has no base class (which is why I said that "almost" all classes inherit from one other class). There is no way to create another no-base-class class. This can be useful - if you want to provide some functionality to all classes or instances, you can just put it in Object.
Okay, enough rambling. Here's an example of inheritance:
class Widget { x y text this(x: int, y: int, text: string) { :x = x :y = y :text = text } function setPos(x: int, y: int) { :x = x :y = y } function getPos() return :x, :y function setText(text: string) :text = text function getText() = :text function draw() throw "Unimplemented draw method!" } class Button : Widget { // don't bother declaring a ctor; the base class's will suffice // Override the draw function function draw() { // some code for drawing a button here! } } class TextBox : Widget { multiline this(x: int, y: int, text: string, multiline: bool = false) { super(x, y, text) :multiline = multiline } function isMultiline() = :multiline function draw() { if(:multiline) { // Draw it one way! } else { // Draw it another way! } } } local t = TextBox(30, 20, "", false) local b = Button(30, 50, "OK")
So here we're creating a simple hierarchy of GUI controls. GUIs are usually pretty object-oriented, and they have a lot different types of objects which have common behavior and data, so they're a good example of OO programming.
You'll see we have a base class, Widget (which implicitly inherits from Object). This defines some basic data fields and methods for manipulating them. Then we have two subclasses - Button and TextBox? - which extend the basic capabilities of the Widget class. They both implement a "draw" method, which is used to draw the widget. Furthermore, the TextBox? class defines a new field, "multiline".
Notice the constructors. The Button class does not define a constructor of its own. Because of the way field lookup works, when you instantiate Button, it will just use the constructor defined in Widget. Button doesn't need to do anything extra. But the TextBox? class has an extra parameter for its constructor. Inside the constructor, you'll see that it does "super(x, y, text)". What's going on there?
"super(x, y, text)" is sugar for "super.constructor(x, y, text)". This is a supercall. This is a special variety of method call which can only be used within a class method. What this does is it looks up the base class's implementation of the given method (here, "constructor"), and calls it with 'this' as the context, as well as any given parameters. This kind of stuff comes up a lot when you inherit classes. Often a base class will define some basic functionality, and a derived class can make use of it by using supercalls.
The .super Expression
Related to supercalls is the ".super" expression. Sometimes you want to get the class that an instance was created from. To do so, you just use "inst.super" - this returns the class that the instance was instantiated from. Similarly, you can get the base class of a class by using "cls.super". As you might expect, "Object.super" returns 'null', since Object has no base class.
Although supercalls and the ".super" expression look similar, don't mix them up. Most importantly, something like "this.super.foo()" is not a supercall. What this code does is it gets "this.super" - that is, usually the class that 'this' was instantiated from - and calls the method "foo" on it, which is very different behavior.
Static Fields and Methods
Static fields and methods are data and functions which belong to a class instead of to an instance. MiniD does not have explicit support for static class members, but since MiniD's classes are first-class and modifiable, you can easily emulate them.
A static method is nothing more than a function which doesn't use its 'this' parameter. It's as simple as that. If you want to be careful, you can even restrict a static method to only being called on a class by using a special kind of parameter type constraint on the 'this' parameter. The following class shows an example:
class A { function staticMethod(this: class) writeln("I'm a static method!") } A.staticMethod() // prints "I'm a static method!" local a = A() a.staticMethod() // error, invalid type for 'this' parameter
A static field is one which always lives in the class and is never copied into its instances. Since the class is passed as the 'this' parameter to static methods, you can just use "this" to access the static fields. To access the static fields in instance methods, you'll have to either explicitly specify the class name, or use "this.super.fieldName" (or ":super.fieldName") to access them.
class A { staticField = 0 function staticMethod(this: class) { :staticField++ // using 'this' to access the static field writeln("My static field is now ", :staticField) } function instanceMethod() { A.staticField++ // or "this.super.staticField++" or ":super.staticField++" writeln("The class's static field is now ", A.staticField) } } A.staticMethod() // prints "My static field is now 1" local a = A() a.instanceMethod() // prints "The class's static field is now 2" A.staticMethod() // prints "My static field is now 3"
One of the cool things about "static" methods in MiniD is that, unlike in many statically-typed languages, you can have "virtual static methods". That is, static methods can be overridden in derived classes, and since classes are first-class objects, you can pass a class somewhere, call its static methods, and they will be dispatched dynamically.
class Base { function foo(this: class) writeln("Base foo!") } class Derived1 : Base { function foo(this: class) writeln("Derived1 foo!") } class Derived2 : Base { function foo(this: class) { write("Derived2 foo, calling base foo: "); super.foo() // even supercalls work! } } function test(c: class) c.foo() test(Base) // prints "Base foo!" test(Derived1) // prints "Derived1 foo!" test(Derived2) // prints "Derived2 foo, calling base foo: Base foo!"
Hm. Classes and instances seem to have a lot of similarities, it seems. It's true; in fact, during the development of MiniD, a prototype-based object model, where there is no distinction between the two, was tried. However, prototype-based objects introduce a lot of type safety issues, make it easy to create degenerate object hierarchies, and are a bad match for interacting with a host language that uses class-based objects, so the idea was dropped. However, MiniD's classes and instances still do retain many of their similarities, which makes them a bit easier to learn and deal with more generically.
Then what distinguishes classes and instances? For one, you can't instantiate an instance, you can only instantiate a class. But a more important difference is the topic of the next section: metamethods.
