lights FAQ Forum

Standard Libraries


fancy object system

local oo = require'oo'

Object system with virtual properties and method overriding hooks.

In a nutshell

  • single, dynamic inheritance by default:
    • Fruit = oo.Fruit()
    • Apple = oo.Apple(Fruit)
    • apple = Apple(...)
    • apple.super -> Apple
    • Apple.super -> Fruit
  • multiple, static inheritance by request:
    • Apple:inherit(Fruit[,replace]) - statically inherit Fruit, optionally replacing existing properties.
    • Apple:detach() - detach from the parent class, in other words statically inherit self.super.
  • virtual properties with getter and setter:
    • reading calls Apple:get_foo() to get the value, if apple.get_foo is defined.
    • assignment to calls Apple:set_foo(value) if Apple.set_foo is defined.
    • missing the setter, the property is considered read-only and the assignment fails.
  • method overriding hooks:
    • function Apple:before_pick(args...) end makes apple:pick() call the code inside before_pick() first.
    • function Apple:after_pick(args...) end makes apple:pick() call the code inside after_pick() last.
    • function Apple:override_pick(inherited, ...) lets you override Apple:pick() and call inherited(self, ...).
  • virtual classes: nested inner classes which can overriden in subclasses of the outer class (more below).
  • events with optional namespace tags:
    • apple:on('falling.ns1', function(self, args...) ... end) - register an event handler
    • Apple:falling(args...) - default event handler for the falling event
    • apple:fire('falling', args...) - call all falling event handlers
    • apple:off'falling' - remove all falling event handlers
    • apple:off'.ns1' - remove all event handlers on the ns1 namespace
    • apple:off() - remove all event handlers registered onapple`
  • introspection:
    •, class|classname) -> true|false - check instance/class ancestry
    • self:is(class|classname) -> true|false - check instance/class ancestry
    • self:allpairs() -> iterator() -> name, value, source - iterate all properties, including inherited and overriden ones.
    • self:properties() -> get a table of all current properties and values, including inherited ones.
    • self:inspect([show_oo_fields]) - inspect the class/instance structure and contents in detail (requires glue).
  • overridable subclassing and instantiation mechanisms:
    • Fruit = oo.Fruit() is sugar for Fruit = oo.Object:subclass()
    • Apple = oo.Apple(Fruit) is sugar for Apple = Fruit:subclass()
    • apple = Apple(...) is sugar for apple = Apple:create(...)
      • Apple:create() calls apple:init(...)
    • Fruit.__install['^prefix_(.*)'] = function(self, k, v) ... end - add a new meta-method installer. Getters, setters and overriding hooks are implemented this way (the matches being ^get_(.*), ^set_(.*), ^before_(.*), ^after_(.*) and ^override_(.*)).

Inheritance and instantiation

Classes are created with oo.ClassName([super]), where super is usually another class, but can also be an instance, which is useful for creating polymorphic "views" on existing instances.

local Fruit = oo.Fruit()

You can also create anonymous classes with oo.class([super]):

local cls = oo.class()

Instances are created with cls:create(...) or simply cls(), which in turn calls cls:init(...) which is the object constructor. While cls is normally a class, it can also be an instance, which effectively enables prototype-based inheritance.

local obj = cls()

The superclass of a class or the class of an instance is accessible as self.super.

assert(obj.super == cls)
assert(cls.super == oo.Object)

Inheritance is dynamic: properties are looked up at runtime in self.super and changing a property or method in the superclass reflects on all subclasses and instances. This can be slow, but it saves space.

cls.the_answer = 42
assert(obj.the_answer == 42)

You can detach the class/instance from its parent class by calling self:detach() -> self. This copies all inherited fields to the class/instance and removes self.super.

assert(obj.super == nil)
assert(cls.super == nil)
assert(cls.the_answer == 42)
assert(obj.the_answer == 42)

Static inheritance can be achieved by calling self:inherit(other[,override]) -> self which copies over the properties of another class or instance, effectively monkey-patching self, optionally overriding properties with the same name. The fields self.classname and self.super are always preserved though, even with the override flag.

other can also be a plain table, in which case it is shallow-copied.

local other_cls = oo.class()
other_cls.the_answer = 13

assert(obj.the_answer == 13) --obj continues to dynamically inherit cls.the_answer
                             --but statically inherited other_cls.the_answer

obj.the_answer = nil
assert(obj.the_answer == 42) --reverted to class default

assert(cls.the_answer == 42) --no override

cls:inherit(other_cls, true)
assert(cls.the_answer == 13) --override

In fact, self:detach() is written as self:inherit(self.super) with the minor detail of setting self.classname = self.classname and removing self.super.

NOTE: Detaching instances or final classes helps preventing LuaJIT from bailing out to the interpreter which can result in 100x performance drop. Even in interpreter mode, detaching instances can increase performance for method lookup by 10x (see benchmarks).

You can do this easily with:

--detach instances of (subclasses of) myclass from their class.
--patching myclass or its subclasses afterwards will not affect
--existing instances but it will affect new instnaces.
function myclass:before_init()

--detach all new subclasses of myclass. patching myclass or its
--supers afterwards will have no effect on existing subclasses
--of myclass or its instances. patching final classes though
--will affect both new and existing instances.
function myclass:override_subclass(inherited, ...)
   return inherited(self, ...):detach()

NOTE: Static inheritance changes field lookup semantics in a subtle way: because field values no longer dynamically overshadow the values set in the superclasses, setting a statically inherited field to nil doesn't expose back the value from the super class, instead the field remains nil.

To further customize how the values are copied over for static inheritance, override self:properties().

Virtual properties

Virtual properties are created by defining a getter and a setter. Once you have defined self:get_foo() and self:set_foo(value) you can read and write to and the getter and setter will be called to fulfill the indexing. The setter is optional: without it, the property is read-only and assigning it fails with an error.

function cls:get_answer_to_life() return deep_thought:get_answer() end
function cls:set_answer_to_life(v) deep_thought:set_answer(v) end
obj = cls()
obj.answer_to_life = 42
assert(obj.answer_to_life == 42) --assuming deep_thought can store a number

Virtual properties can be generated in bulk given a multikey getter and a multikey setter and a list of property names, by calling self:gen_properties(names, getter, setter). The setter and getter must be methods of form:

  • getter(self, k) -> v
  • setter(self, k, v)

Overriding hooks

Overriding hooks are sugar to make method overriding more easy and readable.

Instead of:

function Apple:pick(arg)
   print('picking', arg)
   local ret = Apple.super.pick(self, arg)
   print('picked', ret)
   return ret


function Apple:override_pick(inherited, arg, ...)
   print('picking', arg)
   local ret = inherited(self, arg, ...)
   print('picked', ret)
   return ret

Or even better:

function Apple:before_pick(arg)
   print('picking', arg)

function Apple:after_pick(arg)
   print('picked', arg)
   return ret

By defining self:before_<method>(...) a new implementation for self.<method> is created which calls the before hook and then calls the existing (inherited) implementation. Both calls receive all arguments.

By defining self:after_<method>(...) a new implementation for self.<method> is created which calls the existing (inherited) implementation, after which it calls the hook and returns whatever the hook returns. Both calls receive all arguments.

By defining self:override_<method>(inherited, ...) you can access self.super.<method> as inherited.

function cls:before_init(foo, bar) = foo or default_foo = bar or default_bar

function cls:after_init()
  --allocate resources

function cls:before_destroy()
  --destroy resources

If you don't know the name of the method you want to override until runtime, use cls:before(name, func), cls:after(name, func) and cls:override(name, func) instead.

Virtual classes

Virtual classes are a powerful mechanism for extending composite objects which need to instantiate other objects and need a way to allow the programmer to extend or replace the classes of those other objects. Virtual classes come for free in languages where classes are first-class entitites: just make the inner class a field of the outer class and instantiate it inside the outer's constructor or method with self:inner_class(). this is cool because:

  • it allows subclassing the inner class in subclasses of the outer class by just overriding the inner_class field.
  • using self:inner_class() instead of self.inner_class() passes the outer object as the second arg to the constructor of the inner object (the first arg is the inner object) so that you can reference the outer object from inside the inner object.
  • theinner_class field is seen as a method of the outer class so it can be made part of its public API without any additional wrapping, and it can also be overriden with a normal method in subclasses of outer.


Events are for associating actions with functions. Events facts:

  • events fire in the order in which they were added.
  • extra args passed to fire() are passed to the event handlers.
  • if the method obj:<event>(args...) is found, it is called first.
  • returning a non-nil value from a handler interrupts the event handling call chain and the value is returned back by fire().
  • all uninterrupted events fire the event meta-event which inserts the event name as arg#1.
  • events can be namespace-tagged with 'event.ns' or {event, ns}: namespsaces are useful for easy bulk event removal with obj:off'.ns' or obj:off({nil, ns}).
  • multiple handlers can be added for the same event and/or namespace.
  • handlers are stored as self.__observers[event] = {handler1, ...}.

Last updated: 2 days ago | Edit on GitHub

Pkg type:Lua
Version: r5-10-gacee653
Last commit:
License: PD
Requires: none
Required by: ui