Many people don't understand the true complexities of programming. There is more to code than just writing code. Programming is a unique field, a crossroads that few other fields dare to travel: It's where mathematics meets engineering meets art. A good programmer must know the mathematics of what they're doing, of how to analyze algorithms to understand what works best for them. A good programmer must be a knowledgeable engineer, able to write robust, reliable, efficient code in a way, and documented in a way, that is precise, concise, and clear. A good programmer must be an artist, able to write code that is elegant, extensible, maintainable, and usable. The first and second are clear to many users. But what about the art of programming?
I am impatient. People who do not understand good design principles, people who can not write elegant code, extensible code, maintainable code, useable code. They annoy me. People who can not design good code. And, thus, I grow weary of helping them, sometimes going so far as to just outright stop. But this shouldn't be so. There should be no reasons why the average user of BYOND, the average DM programmer, can not write code that implements good design principles. The Art of Code is my attempt to help users with this, to understand the art of programming, and the beauty of code. These tutorials will be aimed at a slightly more-advanced audience than my So You Want To Program tutorials, but will be written as clear as possible nonetheless. So, let's begin, shall we?
My first tutorial on this series is going to focus on namespaces and inheritance as applied to DM. These two language features are dinstinct in most object-oriented programming language, but this isn't so in DM. In fact, the language muddles the two so much that few people recognize the difference that exists. Nonetheless, the distinction is still present, and not even that far from the surface.
Inheritance
First off, let's discuss inheritance. To most DM programmers, this should be a topic you are already familiar with. Inheritance is one of the main features of object-oriented programming languages. Certain types of programming constructs, called classes, can inherit their features from other classes. The inheriting classes are called subclasses, and the inherited class is called a superclass. For example, below is a typical example of classes and inheritance.
animal
/*
* This defines the animal superclass. We define a few variables and procs for the
* subclasses to use.
*/
var
hunger
thirst
bathroom
proc
Speak()
// How the animal 'speaks', or vocalizes. Some animals just don't.
Eat()
// Some animals eat meat, others eat plants. Others do both.
hunger ++
Drink()
// How the animal drinks. Some sea animals basically do it when they eat.
thirst ++
Bathroom()
// How the animals uses the bathroom.
bathroom --
Move()
// How the animal moves.
Die()
// Called when the animal dies.
del src
animal/dog
/*
* A dog is a type of animal, so we'll use it.
*/
Speak()
world << "[src] barks!"
Eat(animal/a)
world << "[src] eats [a]."
a.Die()
..()
Move()
world << "[src] walks."
animal/fish
/*
* A fish is a type of animal, but one quite different from a dog.
*/
Speak()
// Fish don't really have a vocalization, so nothing happens.
return null
Eat(animal/a)
// We have a carniverous fish here.
world << "[src] eats [a]."
a.Die()
Drink() // It drinks when it eats.
..()
Move()
world << "[src] swims."
animal/bird
/*
* Another type of animal, different from a fish and a dog.
*/
Speak()
world << "[src] chirps."
Eat(plant/berry)
// This is a berry-eating bird.
world << "[src] eats [berry]"
berry.Eaten()
..()
Bathroom()
world << "[src] goes to the bathroom on a nice, clean car."
..()
And there we have a small example of how inheritance can work in DM. This is the way most people work with, but as we'll see later, this isn't the only option available to DM programmers. It's still simple, and intuitive, but not always the kind of thing we want.
Namespaces
At first glance, namespaces are a feature that does not appear to be present in DM. Before I go any further, let me explain what a namespace is. Think of a namespace as an abstract kind of 'container' that holds a lot of similar things together. For example, if you have a Gun, maybe you'll have a few different things associated with it. There might be the Gun itself, ammunition, and maybe Magazines or Clips. You could hold all of this together in a Weapons or Guns namespace. Why is this useful? Maybe there is ambiguity in your code. Maybe your code also has other types of magazines, like the kind that you can read, or other kinds of clips, the kind that you can use to hold two things together.
Without a namespace, this becomes ambiguous. Obviously the Magazine or Clip can't inherit from a Gun. That makes no sense, as they don't share any features with a gun. Thus, what are your options? You could rename them, but that's not a good choice. With namespaces, you can store the Clip and Magazine away under the Guns or Weapons namespace, and you have absolutely no conflict. Your readable Magazine and your holds-things-together Clip can exist and there doesn't have to be any conflict whatsoever.
But as I said, it doesn't appear that BYOND has namespaces. So, why am I talking about this at all, then? Well, the thing is, BYOND does have them. In fact, you use them all the time. It's just that they are not immediately obvious. In most other programming languages, the separation between inheritance and namespaces are immediately apparent, because the way they are both handled is entirely different. BYOND, though, combines both of these things in its typepath system. A typepath, by default, indicates inheritance, but it is not the same inheritance. In fact, inheritance in BYOND can be overwritten. How?
The parent_type Variable
You may have used the parent_type variable before. Most people use it to see what the typepath of the parent is. For example, in our example above, the parent_type of each /animal/dog, /animal/fish, and /animal/bird is just /animal. When your code is compiled, this variable is automatically set to what the superclass is. The thing is, this isn't the only way to use this variable, and in fact this is more of a minor way of using it.
Here's the best thing about the parent_type variable: It can be set by the programmer at compile time, and this changes what the class inherits from. Thus, the programmer has the ability to entirely change the default method of inheritance in DM. Why is this useful? Because it can simultaneously be used to create namespaces. As I mentioned above, the notions of inheritance and namespaces in DM are tied together through the typepath system. Without explicitly overriding it, when you have something like /foo/bar, by default /bar.parent_type = /foo. But, by using the parent_type variable it is possible to turn /foo into a namespace. In fact, unlike any other programming language that I know of, /foo simultaneously becomes both a class and a namespace.
Implementing Namespaces
Now, back to the gun example I mentioned earlier. How would you implement this in DM? Below is one possible way.
gun
/*
* Here we implement the gun namespace/class. It inherits from /obj, but it becomes a top-
*level class. Thus, to create a new gun object we just have to wrote new /gun. It is
* not necessary to know that the /gun
* is a type of /obj, and thus this information can be left out, if a user
* so wishes.
*/
parent_type = /obj
var
// The name of the gun.
name
// The type of ammunition the gun uses.
ammunition_type
// The type of magazine the gun uses.
magazine_type
// The magazine currently being used. May be switched out.
gun/magazine/magazine
// The mob holding the gun.
mob/holder
proc
Load(magazine/m)
if(!istype(m))
// If it's not at all a magazine type, just return.
return 0
if(!istype(m, src.magazine_type))
// If it's a magazine, but the wrong kind, temporarily Jam() the gun.
src.Jam()
return 0
if(!istype(m.ammunition, src.ammunition_type))
// If it's the wrong type of ammunition, jam the gun.
src.Jam()
return 0
// If everything goes well, load the magazine.
src.magazine = m
src.magazine.gun = src
return 1
Jam()
// Not actually implementing this, as the way it jams could really vary, and
// it isn't really necessary to actually implement it.
Fire(angle)
// Attempts to fire a bullet in the given angle. This can be overwritten to,
// for example, result in an inaccurate or unreliable gun.
return src.magazine.Fire(angle)
magazine
/*
* Now we implement the magazine.
*/
// As with /gun, /gun/magazine derives from /obj.
parent_type = /obj
var
// The type of ammunition the magazine stores. Just store a
// typepath here.
gun/ammunition/ammunition
// The gun the magazine is loaded into.
gun/gun
// The amount a magazine holds. Should be an integer greater than zero.
max_size
// The amount currently being held.
size
New()
// Create a new ammunition, as indicated by src.ammunition, and set
// src as its magazine.
src.ammunition = new src.ammunition(src)
return ..()
proc
Reload()
// Reloads the magazine.
if(size < max_size)
src.size ++
return 1
else
return 0
Fire(angle)
if(src.size > 0)
// The amount of ammunition in the magazine.
src.size --
// The bullet fires.
return src.ammunition.Fire(angle)
else
return 0
ammunition
/*
* And now we implement the ammunition.
*/
// A type of datum. Ammunition only says how to model individual bullets, rather
// than being something that exists in the world.
parent_type = /datum
var
// The damage the bullet does.
damage
// The stability of the bullet. How much it wobbles in the air. A percentage.
stability
// The magazine the gun is attached to.
gun/magazine/magazine
New(gun/magazine/magazine)
src.magazine = magazine
proc
Fire(angle)
// Whatever happens here. Presumably, a bullet object would be
// created (perhaps as a /gun/bullet, or an /obj/bullet, or whatever
// solution works best).
And here we have a very basic namespace for guns, with a top-level /gun object and a few classes in the namespace that handle actually firing and etc. the gun. Note that neither /gun/magazine or /gun/ammunition are subclasses of /gun: /gun/magazine is a sublcass of /obj and /gun/ammunition is a subclass of /datum. While it may be confusing at first, and is an example of BYOND's muddling of the difference between inheritance and namespaces in its typepath system, it can result in far better organization of code. Below is one potential example of how we could utilize this namespace to implement a weapon.
gun/RPGLauncher
/*
* This implement a Rocket Propelled Grenade launcher.
*/
name = "SP4NK3R"
ammunition_type = /gun/ammunition/RocketPropelledGrenade
magazine_type = /gun/magazine/RPGMagazine
Fire(angle)
if(prob(5))
// The gun is kind of unreliable. There is a 5% chance that the gun
// will explode rather than fire an RPG.
src.Explode()
return 0
else
return ..()
proc
Explode()
// Do explosion stuff here.
gun/magazine/RPGMagazine
max_size = 2
ammunition = /gun/ammunition/RocketPropelledGrenade
gun/ammunition/RocketPropelledGrenade
damage = 50
// 85% stable.
stability = 0.85
Fire(angle)
// Fires a new rocket in the angle indicated.
new /bullet/rocket(angle, src.magazine.gun.holder.loc, damage, stability)
And, with this model would could extend the /gun/magazine/RPGMagazine class, to create subclasses that are special types of RPGs. For example, maybe there is a Napalm RPg or an Acid RPG. These would, presumably, still exist in the gun namespace to avoid any conflict outside it.
Summary
In conclusion, BYOND's typepath system is simply a highly-complex namespace system. The system can influence inheritance and such hierarchy, but this is only a default, overwriteable behavior, albeit one that simplifies things a great bit, but one that is not at all necessary. Sometimes, overwriting this behavior is useful, and even necessary, to write elegant, extensible, and easily-maintainable. Namespaces and inheritance are both powerful language features, even more powerful when used together, and while they can be confusing, they can also be one of the most powerful tools for defeating ambiguity and increasing clarity when designing code.
Beyond The Tutorial
As an aside, note that the typepath system makes it simple for their to be namespaces within other namespaces, allowing there to be a hierarchal system that breakdown differently at each level. This can be useful for extremely complex systems, where things are tied together and where collisions or ambiguity are a very conceivable and real possibility.
Namespaces are also very useful in libraries. They allow for the possibility of a number of different classes to be packaged under one namespace to avoid annoying and difficult-to-resolve collisions between a library and a user's own code. When a collision occurs, typically the only real solution is for a user to rewrite all of their own code to fix it, which is often more difficult than just implementing the library themselves! This makes namespaces a very useful tool in library development.