An in-depth look into the MoonScript class implementation

MoonScript’s class system is great balance of functionality and brevity. It’s simple to get started with, doesn’t impose many restrictions, and is incredibly flexible when you need to do advanced things or bend the rules.

Even if you have no intention of using MoonScript, understanding the class system implementation is a good exercise for understanding some of the more complicated parts of Lua.

A simple example

Lets start with a typical class in MoonScript:

class Player
  new: (@x, @y) =>

  say_hello: =>
    print "Greetings! I'm at #{@x}, #{@y}"

And take a look at the generated Lua: (Warning: there’s a lot going on, scroll past for analysis of each component)

local Player
do
  local _base_0 = {
    say_hello = function(self)
      return print("Greetings! I'm at " .. tostring(self.x) .. ", " .. tostring(self.y))
    end
  }
  _base_0.__index = _base_0
  local _class_0 = setmetatable({
    __init = function(self, x, y)
      self.x, self.y = x, y
    end,
    __base = _base_0,
    __name = "Player"
  }, {
    __index = _base_0,
    __call = function(cls, ...)
      local _self_0 = setmetatable({}, _base_0)
      cls.__init(_self_0, ...)
      return _self_0
    end
  })
  _base_0.__class = _class_0
  Player = _class_0
end

Lets go from the outside in. The result of the class expression is a new local variable called Player. Nothing else is made available on the calling scope.

The class’s internal objects are created inside of a Lua do end block, this ensures that they are scoped to just the class in question. The two internal objects are _class_0 and _base_0.

The resulting local, Player is assigned _class_0.

The numbers at the end of these variables are not fixed, they come from MoonScript’s local name generator. They will increment if you nest classes. You should never write code that depends on their names.

The class object

The class object, aka _class_0 in the generated code, is a Lua table that represents the class. To create a new instance we call the class object as if it were a function. We can see here that it’s not actually a function.

In order to make a Lua table callable it must implement the __call metamethod.

Here’s the extracted class object’s creation:

local _class_0 = setmetatable({
  __init = function(self, x, y)
    self.x, self.y = x, y
  end,
  __base = _base_0,
  __name = "Player"
}, {
  __index = _base_0,
  __call = function(cls, ...)
    local _self_0 = setmetatable({}, _base_0)
    cls.__init(_self_0, ...)
    return _self_0
  end
})

The Lua function setmetatable sets the metatable of the first argument to the second argument. It then returns the first argument. This means the value of _class_0 is the modified version of the first table.

The table _class_0 is very basic. It has the constructor we created (with new) stored in __init, the base object stored in __base and the name of the class stored in __name.

Unlike the generated names, these names are unchanging and safe to use in your code. Because they are stored directly on the class object we can access them with dot syntax:

print(Player.__name) --> prints "Player"

Two metafields are provided on the class objects metatable: __index and __call.

The __call function is what is called when we create a new instance: Player() It’s responsible for creating a new table to be the instance, providing it with a metatable, then calling the constructor.

You can can see how the _base_0 is used directly as the metatable of the object.

Additionally, the class object has an __index metafield set to the base. This has a lot of implications. The most important is you can access any fields from base directly on the class object, assuming they haven’t been shadowed by any fields directly on the class object.

The base object

local _base_0 = {
  say_hello = function(self)
    return print("Greetings! I'm at " .. tostring(self.x) .. ", " .. tostring(self.y))
  end
}
_base_0.__index = _base_0
_base_0.__class = _class_0

The base object, __base_0 is a regular Lua table. It holds all the instance methods of the class. Our example from above implemented a say_hello method which is compiled directly into the base.

The base object has a circular reference to itself in the __index field.

This lets us use the base object directly as the metatable of instances. The __index property is where instance methods are fetched from. Since it points to itself, the instance methods can be pulled directly from the metatable without any indirection.

Likewise, this also lets us implement other metamethods directly as instance methods of the class. I'll have an example below.

It’s a very cool concept, and definitely worth taking a moment to understand.

Lastly, a reference to the class placed on the base object with the name __class. This is how the @@ operator accesses the class object.

Classes with inheritance

Super invocation has changed a bit in MoonScript 0.4.0

Classes that inherit from other classes in MoonScript introduce a few more ideas. The extends keyword is used for inheritance:

class SizedPlayer extends Player
  new: (@size, ...) =>
    super ...

  say_hello: =>
    super!
    print "I'm #{@size} tall"

Here’s the resulting Lua:

local SizedPlayer
do
  local _parent_0 = Player
  local _base_0 = {
    say_hello = function(self)
      _parent_0.say_hello(self)
      return print("I'm " .. tostring(self.size) .. " tall")
    end
  }
  _base_0.__index = _base_0
  setmetatable(_base_0, _parent_0.__base)
  local _class_0 = setmetatable({
    __init = function(self, size, ...)
      self.size = size
      return _parent_0.__init(self, ...)
    end,
    __base = _base_0,
    __name = "SizedPlayer",
    __parent = _parent_0
  }, {
    __index = function(cls, name)
      local val = rawget(_base_0, name)
      if val == nil then
        return _parent_0[name]
      else
        return val
      end
    end,
    __call = function(cls, ...)
      local _self_0 = setmetatable({}, _base_0)
      cls.__init(_self_0, ...)
      return _self_0
    end
  })
  _base_0.__class = _class_0
  if _parent_0.__inherited then
    _parent_0.__inherited(_parent_0, _class_0)
  end
  SizedPlayer = _class_0
end

The majority of the generated code is the same as a regular class. Here are the differences:

local _parent_0 = Player

There’s a new local variable inside the do end block called _parent_0 that holds a reference to the parent class.

local _base_0 = {
  -- ...
}
_base_0.__index = _base_0
setmetatable(_base_0, _parent_0.__base)

The metatable of the base is set to the base of the parent class. This establishes the inheritance chain for instances. If a method can’t be found on the class’s base, then the parent class’s base is automatically searched due to how __index works.

There’s a slight disadvantage to this. Metamethods are fetched with rawget, so metamethod inheritance does not work by default. We can work around this with the __inherited callback discussed below.

local _class_0 = setmetatable({
  -- ...
  __parent = _parent_0
}, {
  -- ...
}

The parent class is stored on the class object in a field called __parent. This gives you an easy way to reference the parent class object.

{
  __index = function(cls, name)
    local val = rawget(_base_0, name)
    if val == nil then
      return _parent_0[name]
    else
      return val
    end
  end,
  -- ...
}

The __index metafield on the class object is now a function, instead of a reference to the base (which is a table). rawget is used control the precedence of the properties. If the field can’t be found directly on the base then the parent class is searched.

Remember that class objects also pull fields from their bases, so this has the effect of searching both the parent class object and the parent class’s base. Even though we've used rawget on the base, we can still get access to the parent class’s base.

if _parent_0.__inherited then
  _parent_0.__inherited(_parent_0, _class_0)
end

Lastly, we now have a class callback. When a subclass is created and the parent class has a method __inherited then it is called with the class object that has just been created.

The __inherited method works directly with class objects, no instances are involved.

local _base_0 = {
  say_hello = function(self)
    _parent_0.say_hello(self)
    return print("I'm " .. tostring(self.size) .. " tall")
  end
}

In the example I included a method that calls super. All MoonScript does is provide sugar for calling the method of the same name on the parent class.

Class tips and tricks

Now that you have an understanding of how a class in MoonScript is implemented, it’s easy to see how we can work with the internals to accomplish new things.

Adding __tostring and other metamethods

If you want your instances to have a string representation you can implement a __tostring method in the metatable.

As we saw above, the metatable has an __index field set to itself, we just need to implement metamethods as instance methods:

class Player
  new: (@x, @y) =>

  __tostring: =>
    "Player(#{@x}, #{@y})"

print Player(2, 8) --> "Player(2, 8)"

All of Lua’s metamethods work (except __index, see below). Here’s an example of a vector class with overloaded operators:

class Vector
  new: (@x, @y) =>

  __tostring: =>
    "Vector(#{@x}, #{@y})"

  __add: (other) =>
    Vector @x + other.x, @y + other.y

  __sub: (other) =>
    Vector @x - other.x, @y - other.y

  __mul: (other) =>
    if type(other) == "number"
      -- scale
      Vector @x * other, @y * other
    else
      -- dot product
      Vector @x * other.x + @y * other.y

print Vector(1,2) * 5 + Vector(3,3) --> Vector(8, 13)

I mentioned above that metamethod inheritance does not work:

class Thing
  __tostring: => "Thing"

class BetterThing extends Thing

print BetterThing! --> table: 0x1057290

We can work around this by using the __inherited callback:

class Thing
  __tostring: => "Thing"
  __inherited: (cls) =>
    cls.__base.__tostring = @__tostring

class BetterThing extends Thing

print BetterThing! --> Thing

Adding a new method to a class after declaration

Now that we know about __base it’s easy to add new methods to classes that don’t have them.

class Player
  new: (@name) =>

-- add the new method
Player.__base.jump = =>
  print "#{@name} is jumping!"

Player("Adam")\jump! --> Adam is jumping!

We can extend this concept even further to dynamically generate methods:

class Player
  new: (@name) =>

  for dir in *{"north", "west", "east", "south"}
    @__base["go_#{dir}"]: =>
      print "#{@name} is going #{dir}"

Player("Lee")\go_east! --> Lee is going east

Converting an existing table to an instance

Sometimes you might already have a table that you'd like to convert to an instance of a class without having to copy it. Now that we know how the __init method works we can use setmetatable to accomplish a similar result:

class Rect
  area: => @w * @h

some_obj = { w: 15, h: 3 }

-- apply the metatable
setmetatable(some_obj, Rect.__base)

print some_obj\area! --> 45

This same method can be used to convert on object from type to another.

Adding __index metafield to an instance

MoonScript uses the __index metafield on class instances in order to allow instance properties to be looked up. If we just replace __inde with another implementation without any consideration we would break the instance. We'll have to chain our custom __index with the old one.

Here’s how we might implement getter methods:

class Thing
  getters: {
    age: =>
      os.time! - @created_at
  }

  new: =>
    @created_at = os.time!

    mt = getmetatable @
    old_index = mt.__index

    mt.__index = (name) =>
      -- for completion we handle when index is function
      if type(old_index) == "function"
        if gs = old_index @, "getters"
          if gs[name]
            return gs[name] @

        old_index @, name
      else
        if g = old_index.getters and old_index.getters[name]
          g @
        else
          old_index[name]

t = Thing!
print t.age

Its’s important that you don’t try to access self (without rawget) within the __index metamethod, otherwise you'll cause an infinite loop.

Writing that massive implementation in the constructor isn’t ideal. Here’s a base class that automatically upgrades anyone who inherits with getter functionality:

class HasGetters
  getters: {}
  __inherited: (cls) =>
    old_init = cls.__init
    cls.__init = (...) =>
      old_init @, ...

      mt = getmetatable @
      old_index = mt.__index

      mt.__index = (name) =>
        if getter = old_index.getters[name]
          getter @
        else
          if type(old_index) == "function"
            old_index @, name
          else
            old_index[name]

class BetterThing extends HasGetters
  getters: {
    age: =>
      os.time! - @created_at
  }

  new: =>
    @created_at = os.time!

t = BetterThing!
print t.age

The clever part here is replacing the __init method on the base class with a custom one that automatically injects support for getters.

Future improvements

The class system is far from perfect. Here are some future improvements that I'd like to add:

  • There’s no way to determine which order methods are added to a class. If you're going to be triggering side effects from method creation then your options are limited.
  • The MoonScript class meta-properties use double underscore just like Lua. If Lua ever decides to use any of the same names then there will be conflicts.

Closing

Not all of the functionality of MoonScript classes was covered in this guide. You can learn more on the Object Oriented Programming section of the MoonScript documentation.

Related projects

A programming language that compiles to Lua, inspired by CoffeeScript and written in MoonScript.