Moon - TetraSource/OC-programs GitHub Wiki

About

Moon is an API that brings OOP based on Pythons OO to OC Lua 5.3. It uses a C3 linearization algorithm like python does and supports multiple inheritance therefore. Additionally, it is contains properties, operator overloading and metaclasses.

Before getting started

When using Moon you should already know how basic OO works (in dynamic typed languages). However, I will try my best to cover all major differences between a static typed and dynamic typed language with regard to Moon.

I also want to stress the difference between the purpose of the terms class, class instance and object in this documentation. While the first one can generate class instances, the latter can be a class as well as a class instance. Don't confuse these terms with the classes moon.Class and moon.Object.

Last, note that I use the camelCase for the names of variables, functions and class instances and PascalCase for the names of classes.

Getting Started

Attributes are supposed to be accessed like this always:

	instance.attribute
	class.attribute

Methods are supposed to be called like this always:

	instance:method(...) -- equal to instance.method(instance, ...)
	class:method(...) -- equal to class.method(class, ...)

New classes are defined like this:

	local C = moon.class("C")

	-- define static attributes
	C.attribute = "world"

	-- define methods
	function C:getText()
		return tostring(self.value) .. " " .. tostring(self.attribute)
	end

	-- define the constructor that is always called __init
	function C:__init(value)
		self.value = value
	end

New class instances are created like this:

	local c = C("hello")

	print(c.attribute) -- world
	print(c:getText()) -- hello world

And now an example with inheritance:

	local B = moon.class("B", C)

	-- overwrite text
	function B:getText()
		-- check out its documentation for more information about super.
		return moon.super(B, self).getText(self):upper() .. "!"
	end

	local b = B("hello world") -- uses constructor of A

	print(b:getText()) -- HELLO WORLD!

There are no private or protected attributes or methods. If you don't want something to be accessed externally, put a _ in front of it (e.g. object.name becomes object._name). Doing so is like saying: "Don't access this attribute/method except you really know what you are doing".

This approach counts on the programmer to not break anything by giving her/him the freedom to access all attributes. This might sound a little weird to you if you come from languages such as Java. However, it is really just another approach with more emphases on flexibility and trust in the programmer.

Features

Multiple inheritance and method resolution order

Moon uses a method resolution order (MRO) just like in Python or Perl 5.9.5 or higher. A MRO is a (linear) list that determines in which order the namespaces of the base classes of a class C are checked when a lookup is performed on C. You can access it with C.__mro.

If you are already familiar with the C3 linearization you can skip the rest of the paragraph. Otherwise I try my best in explaining it here shortly.

With simple inheritance this is rather simple:

	local A = moon.class("A", Object)
	local B = moon.class("B", A)

	print(table.unpack(B.__mro)) -- MRO is {B, A, Object}

However, when it comes to multiple inheritance we need some rules in order to determine a unambiguous MRO. Since this API uses a C3 linearization we have to consider three factors:

  1. the monotony: If a class C has the MRO that contains a class B prior to A, all classes that inherit from C have B prior to A in their MROs.
  2. the local precedence order: If a class B inherits from A, the MRO of B has to satisfy the MRO of A.
  3. the consistent extension: Equal combinations of derivations result in equal MROs. This is archived by always choosing the first possible class in the MRo of the first possible base class.

Here is a simple example that demonstrates how it works:

	local A = moon.class("A", Object)
	local B = moon.class("B", Object)
	local C = moon.class("C", B, A)

	print(table.unpack(C.__mro)) -- MRO is {C, B, A, Object}

	local D = moon.class("D", Object)
	local E = moon.class("E", D, B)

	print(table.unpack(E.__mro)) -- MRO is {E, D, B, Object}

	local F = moon.class("F", C, E)

	print(table.unpack(F.__mro)) -- MRO is {F, C, E, D, B, A, Object}

Note that there are combinations of derivations that can not be linearized by this rules. In this case the class creator raises an error:

Cannot create a consistent method resolution order (MRO) for bases

Look here for a thorough example how complex inheritance trees are linearized.

Properties and data descriptors

In moon data descriptors are values that allow you to control how they are accessed when they are in an object (e.g. object.x = value; value = object.x). Formally they are tables (so they can also be objects) that have a __get or a __set field (or both). Unlike Python they don't need to have both. The __get(self, object) -> result field realizes reading access, the __set(self, object, value) -> nil field writing access whereby self is the data descriptor and object the object of the class it is used in. Of course they are just called if they exist.

Moon already provides a data descriptor class namely Property (capitalized since it is a class). A property is a mean to ensure data encapsulation that is more clear than the setter/getter approach. Besides, they can allow you to control the accessing of variables that used to be "public" without changing their old using code.

The constructor of Property takes two arguments namely a getter function and an optional setter function. If the setter is omitted, an assignment to the property raises an error. Otherwise assignment leads to a call to the setter with the object and the value as parameter (setter(object, value)). The returned value is ignored. Subscripting leads to a call to the getter with the object as parameter (getter(object)) and returns its results. Here is an example that also uses the setter/getter approach:

	local C = moon.class("C")

	function C:__init(value)
		self.value = value -- or self:setValue(value)
	end

	function C:getValue()
		return self._value
	end

	function C:setValue(value)
		-- Here you could sync with something,
		-- check if the value is allowed, ...
		self._value = value
	end

	-- You can also directly define the call to moon.Property
	C.p = moon.Property(C.getValue, C.setValue)

	local c = C(42)
	print(c.value)

In this example it actually totally unnecessary to use a property but it is just for demonstration purposes.

A property decorator (@property) as in Python is due to Lua syntax not supported though.

Operator overloading

Every object has a metatable that implements all possible metamethods. Each of them checks whether it can find a method that has the same name as the metamethod. If that's the case, it is called with the same parameters as the metaclass. So they are using a pattern (__xxx) that is similar to the one of Python (__xxx__).

__index and __newindex

When index operations are performed on an object this does not result in a direct call to __index or __newindex but in a call to __getfield or __setfield.

You can overwrite __getfield and __setfield. However, note that this breaks a lot of the usual features as depict below such as data descriptors, calling of __index and __newindex and redirecting reading and writing to the namespaces of the objects. But for the most purposes you should be perfectly fine with using __index and __newindex for access operator overloading.

Also note there is no difference between what is called __getattr__ / __setattr__ and __getitem__ / __setitem__ in Python.

Here is what __getfield(object, index) -> result does exactly

  1. Check the namespace of the class of the object and its bases following the MRO. If there is something and it is a data descriptor (has a __get field), call it with object and return it. Otherwise move to 2.
  2. If the object is an class instance, just check its namespace. If it is a class, check its and the namespace of its bases following its MRO. If it finds something, return it. Otherwise move to 3.
  3. Check the namespace of the class of the object and its bases following the MRO (similar to 1.)
  4. Lookup the __index method in the namespace of the class of the object and its bases following its MRO. If it is a function, call it with object, index and return the result. Otherwise move to 5.
  5. Return nil

Here is what __setfield(object, index, value) -> nil does exactly

  1. Check the namespace of the class of the object and its bases following the MRO. If there is something and it is a data descriptor (has a __set field), call it with object, value. Otherwise move to 2.
  2. If the object is a class instance, just check its namespace. If it is a class, check its and the namespaces of its bases following its MRO. If it finds something, move to 3. Otherwise move to 4.
  3. Lookup the __newindex method in the namespace of the class of the object and its bases following its MRO. If it is a function, call it with object, index, value. The results are not returned. Otherwise move to 4.
  4. Set the index index of the namespace of the object object to the value value.

Binary operator overloading

When overloading binary operators such as +, it is possible that the first passed parameter is not the object of its class or it might even not be an object at all. This also hold true for their respective Lua metamethods (e.g. 42 + object leads to object.__add(42, object)) and is just derived from this behavior. However, with moon.isinstance you can discern whether the first parameter is the object:

	function C.__add(par1, par2) -- C is the class
		if moon.isinstance(par1, C) then
			-- par1 is the object
		else
			-- raise an error / adopt to the situation
		end
	end

Metaclasses

In Moon metaclasses have a similar relationship with classes what classes have with class instances. Meaning a metaclass can define operator overloading for its class and determines how objects are initialized.

When initializing a class with moon.class, you use the moon.Class as metaclass except you define another one. If you write a custom metaclass, I recommend to derive it from moon.Class since it provides all necessary fields for a metaclass and provides the default item access. However, it uses __call already in order to create new objects.

Every class has a metaclass. The metaclass of moon.Class is moon.Class. This is archived by some tricks in the implementation.

Since metaclasses are involved in process, I am going to explain here how an new class instance is created. Assume C is a class and M its metaclass.

  1. Call the class C(...). This results in all call to M:__call(...).
  2. M.__call calls C.__new(C, ...). If C inherits from Object, this creates and returns the new object obj without using any of the other parameters.
  3. M.__call calls the constructor C.__init(obj, ...)
  4. Return obj.

Classes are created similarly. Assume M is the metaclass and MM its meta-metaclass.

  1. Call the metaclass M(name, bases, namespace). Theoretically you could feed it with any parameters but those three are required in order that it works with the default metaclass moon.Class similar to type of Python. The function moon.class gets the new namespace by a call to M.__prepare(M, name, bases) by the way. The call results in a call to MM.__call(name, bases, namespace).
  2. MM.__call calls M.__new(M, name, bases, namespace). If M inherits from moon.Class, this creates a new class cls using the given parameters.
  3. MM.__call calls the constructor M.__init(cls, ...) which is by default empty though.
  4. Return cls.

API Documentation

moon.class([metaclass:table,] name:string [,base1:table, ...]):table

Returns a new class with an optional metaclass and bases. If no base classes are specified, the classes inherits from moon.Object. Thus every class hierarchy initialized by this function heirs from moon.Object. The default metaclass is moon.Class. name is the name of the created class.


moon.super(class:table, object:table):table

Returns a proxy of the object object that allows calls to overwritten methods. This is done by starting to search after the position of class in the MRO of the class of object.

This class does not just work with class instances but also with classes in general. In this case the MRO of its metaclass is modified. Additionally, it can also handle multiple inheritance since the MRO is linear.

Nevertheless, this class can be used for modified item access only. It does not work with other types of operator overloading.

Here is a code snippet that demonstrates how it works:

	function C:method(parameter)
		moon.super(C, self).method(self, parameter)
		...
	end

Note that method is called without colon and the object self is passed manually. This has do be done like this as you don't want to pass the super proxy normally.

You can also use this function with data descriptors such as properties for getting and setting data:

	C.value = moon.Property(
		function(self)
			return super(C, self).value -- use property of base class
		end,
		function(self, value)
			super(C, self).value = value
		end
	)

This function is basically nothing but a wrapper of moon.Super.


moon.issubclass(class1:table, class2:table):boolean

Returns whether the class class1 is class class2 or derives from it.


moon.isinstance(instance:table, class:table):boolean

Returns whether the class of the object instance is a subclass of the class class.


moon.rawget(object:table, index:any):any

Indexes the namespace of the object object at the index index which bypasses operator overloading. Thus being ideal for operator overloading methods just like the rawget function of Lua with the __index metamethod.

If obj is a class it checks its namespace first and checks then the namespaces of its base classes following its MRO until it succeeds. With class instances just their namespace is checked.


moon.rawset(object:table, index:any, value:any):nil

Directly puts value at the index index into the namespace of the object object which bypasses any operator overloading. Thus being ideal for operator overloading methods just like the rawset function of Lua with the __newindex metamethod.


moon.classmethod(method:function):function

Similar to Python's classmethod decorator. Returns a wrapper of the method method that makes sure the first parameter, the called object, is never a class instance. If the method is called with a class instance, it converts it to its class. Thus the two following code snippets call method the same way:

	class:method(...)
	object:method(...) -- converts object to class

moon.staticmethod(method:function):function

Similar to Python's staticmethod decorator. Returns a wrapper of the method method that eats the first parameter which is the called object. Thus you can call them from anyway (class:method(...), object:method(...) work both). Instead of using this function you can also simply not use the first parameter. In this case you should name it _.

Class documentation

Class

The default metaclass (for all classes created by moon.class).

Class:__init():nil

Empty constructor so that it can safely be omitted in sub classes since they inherit it.

Class:__getfield(index:any):any

Provides the default reading access for the class.

Class:__setfield(index:any, value:any):nil

Provides the default writing access for the class.

Class:__call(...):table

Lets the class initialize and return a new object when being called.

Class:__new(name:string, bases:table, namespace:table):table

Creates and returns a new class with the given name name and namespace namespace. It also calculates the MRO and depending on the base classes bases that have to be classes. The namespace is the structure all new methods and static attributes of the class are stored in.

classmethod: Class:__prepare():table

Returns a new namespace, by default an empty table.


Object

The default base class (for all classes created by moon.class).

Object:__init()

Empty constructor so that it can safely be omitted in sub classes since they inherit it.

Object:__getfield(index:any):any

Provides the default reading access for the class instance.

Object:__setfield(index:any, value:any):nil

Provides the default writing access for the class instance.

classmethod: Object:__new():table

Merely creates and returns a new class instance without calling its constructor.


Super

The proxy object used by moon.super. Check out its documentation if you want to know how it works.

Super:__init(class:table, object:table):nil

Initializes the proxy object of object whereby the MRO of its class is limited to all classes that come after class in the MRO.

Super:__getfield(index:any):any

Provides the modified reading access for the object.

Super:__setfield(index:any, value:any):nil

Provides the modified writing access for the object.

Super:__new(class:table, object:table):table

Creates the proxy object for Super.__init.


Property

The default data descriptor provided by Moon.

Property.fget

The getter function. You can access but there is no check whether it is really a function.

Property.fset

The setter function. You can access but there is no check whether it is really a function.

Property:__init(fget:function [,fset:function]):nil

Initializes the property with the getter function fget and the optional setter function fset.

Property:__get(object:table):any

Call the getter with the the object of its class object.

Property:__set(object:table, value:any):nil

Call the setter with the given value value and the the object of its class object.

Structure of an object

Structure of a class instance

	instance = {
		__class = C, -- The class of the class instance
		__table = { -- The namespace of the class instance
			attribute1 = xyz,
			...
		}
	}

When not accessing one of the given field, the access operation is delegated to the namespace.

Structure of a class

	Cls = {
		__name = "Cls", -- The name of the class
		__class = M, -- The metaclass of the class
		__table = { -- The namespace of the class
			staticAttribute1 = xyz,
			method1 = function() ... end,
			...
		},
		__bases = {A, ..., Z} -- The base classes of the class.
		__mro = {Cls, ..., Object} -- The MRO of the class.
	}

When not accessing one of the given field, the access operation is delegated to the namespace (of it or its base classes).

Final notes

Performance

Moon is rather optimized on flexibility than performance. Thus you should consider not using it in time critical code. But consider the benefits of its flexibility.

Localizing

For better readability it might be worth localizing all required classes and functions of Moon. This would also accelerate your code. This way you can also use the lowercase of moon.Property for example.

Alternative class definitions

Classes can also be defined differently than the way I used in the documentation. You could structure them with do-blocks for example and if you have a very long class name you could create an alias for it within this block:

	local VeryLongClassName = moon.class("VeryLongClassName") do
		local Self = VeryLongClassName -- use a static alias

		function Self:__init(...)
			...
		end

		...
	end

You can even take this a step further and define a function for the class definition:

	local function class(...)
		local cls = moon.class(name, ...)
		_ENV[cls.__name] = cls -- make global within chunk
		_ENV._Self = cls
	end

	class("VeryLongClassName") do
		local Self = _Self -- = ENV._Self

		...
	end

Anonymous classes

You can create a anonymous class that can be with the base class Base like this

	local object = moon.Class(
	 Base.__name .. "*", -- Due to debugging, you should give it a name
	 {Base}, {
		-- overwrite method in Base
		method = function(...)
			...
		end;

		...
	})() -- directly create an object