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.