Skip to the content.

Classes and objects

Well, maybe not the way most people would expect, but yes, we can call Til’s type system “OOP”, although the language is not oriented towards objects, necessarily, you know…

What we have now is a way to define new types in Til and give them some custom methods.

All types “inherit” from some other previously existing type. And since most people are very much used to having attributes in their objects, I believe the vast majority of “classes” will inherit from dict.

What makes me happy is that, in order to implement this classes-and-inheritance system, nothing was changed in the language. Actually, every native type was already a form of class/object, since they could implement their own commands.

I’ll explain.

Let’s say you have two values, s and d, being s a string and d a dictionary. Like this:

set s "x"
set d [dict]

If you use the command set again with s, you would be using the generic set command:

set $s 10
# Now `x` is 10
print $x
# 10

But using set with a dict, you would be using a dict “method”:

set $d (key "the-value")
print <$d key>
# the-value

So, as you can see, native types already behaved as objects, having their own specific methods. There was already a mechanism to make this work. Then all I had to do was to create a new command that would execute some SubProgram and overwrite the returned value (or “object”) methods/commands list (.commands attribute, internally).

Of course, I had to provide some way for the user code to still access “methods” from the inherited type, so every time you instantiate a new object, Til copies all original methods into versions prefixed with base..

The end result is something like this:

type vehicle {
    proc init (wheels_count) {
        return [dict (wheels_count $wheels_count) (engine_status off)]
    }
    proc turn_on (self) {
        base.set $self (engine_status on)
        print "Engine on!"
    }
}

set one_wheeler [vehicle 1]
set bike [vehicle 2]
set car [vehicle 4]

turn_on car
# Engine on!

As you can imagine, init is the constructor and should return an instance of the “base type” (I’ll probably make this method optional in the future and make types inherit from dict by default, probably). It receives some parameters, just like constructors usually do, so that you can initialize a base object properly.

Other methods always receive the object itself as the first argument. It’s not something coming from Python, but instead is a fruit of that generic versus specific commands system I mentioned before: whatever the way your command is called, it can expect the same arguments to be present at the stack.

Notice the base.set being called. In this case, it’s not mandatory, since the vehicle type does not overwrite set - but it seems a good practice to call the prefixed version nonetheless. Good code is code that works now and is forever modifiable, right?

I’m calling the first argument self because it helps me to be aware that, although I could think in terms of the base object, I should always remember that all of its methods might be overwritten.

And that’s it for now. I was hesitant about the best approach to this but in the end I believe everything went very well. I liked the end result very much.