Object Oriented Programming in Ankoku

OOP is implemented into Ankoku, in a mix of prototypal and traditional styles, with the goals of being fast without too much magic. When defining a class, you define all the methods like this:

class Animal {
	fn make_sound() {
		print("*crickets*");
	}
}

class Cow: Animal {
	fn make_sound() {
		if (this.loud) {
			print("MOOOO!!");
		} else {
			print("Mooo");
		}
	}
}

but they actually desugar into normal functions:

fn Animal.make_sound(this) {
	print("*crickets*");
}

fn Cow.make_sound(this) {
	if (this.loud) {
		print("MOOOO!!");
	} else {
		print("Mooo");
	}
}

(this is prepended as the first argument unless the function is a static fn)

And then it also creates constructor functions and a type:

type Animal(Object);

fn Animal.new() {
	// user code goes here...
	Object.new(Animal) // create a new object of type Animal
}

type Cow(Animal);

fn Cow.new() {
	Object.new(Cow) // create a new object of type Cow
}

Object is at the top of the inheritance hierarchy and includes static methods to create new objects.

Anyways, each object has a type:

let object = {}; // empty object, desugars to Object.new(Object)

print(object is Object); // true

let animal = Object.new(Animal); // animal

print(animal is Animal); // true

The type is accessible by calling typeof:

let object = {}; // empty object

print(typeof(object) == Object); // true
print(typeof(object) == Animal); // false

let animal = Object.new(Animal); // animal

print(typeof(object) == Animal); // true
print(typeof(animal) == Object); // false

However, typeof only gives the direct type, so use is for comparisons to allow inherited types.

Anyways, Object.new(type) will set properties on the object to the associated non-static functions (fn Animal.*):

let animal = Object.new(Animal); // finds all functions for animal (Animal.*)

print(animal.make_sound); // [function]
print(animal); // { make_sound: [function] }

But it will set it to bound versions of them (using .bind on Function), so the first parameter (this) is set to the created object.

type Example(Object);
fn Example.assoc_types(this) {
	return this;
}

let example = Object.new(Example);

print(example.assoc_types() == example); // true

And you can call associated functions yourself as well, if you'd like to use a specific version:

let cow = Cow.new();

Animal.make_sound(cow); // prints "*crickets*"

This is a reasonably elegant system that is sort of confusing, but combines some of the dynamicity of prototypes with the rigidity of OOP.