Type-Checking in Lua

A recent discussion about using getters and setters in Lua led into a chat about the merits of type-checking. Lua does not provide the kind of type-safety you find in some other popular programming languages. In particular, Lua does not provide static type-checking, meaning you do not and cannot declare the types of variables like you can in Java or Haskell. That does not mean that Lua will not detect type errors, only that it will catch them later rather than sooner. Lua is similar to Python and Ruby in that regard.

You can perform type-checking on your own in Lua via the type() function. But how often do you really need to manually check types in Lua?

Not That Often

My personal experience with Lua has shown that type() is often unnecessary. For example, one project I’ve been working on has nearly three-thousand lines of Lua. There are exactly fifteen uses of type() throughout that entire code-base. Personally I prefer the static, stronger typing of a language like Haskell. But methodical documentation and testing practices have adequately filled the gap left by Lua’s lack of type-checking relative to such languages—besides, writing clear documentation and tests are valuable no matter what programming language you are using.

Type-checking for the purposes of sanity, i.e. making sure functions receive the correct types of arguments, has not been something I frequently need. It is true that this causes some type-related errors to slip through and not manifest until later on, where static typing would have caught the problem earlier. But again, clearly documented, concise and focused functions have helped keep these types of errors to a minimum.

Where I Do Use Type-Checking

Here are the two most common scenarios where I find myself performing manual type-checking.

Accepting Multiple Types

Sometimes I write functions that accept an argument which may be one of many types. The code for LNVL has an example of this practice:

if type(properties.font) == "string" then
    character.font = love.graphics.newFont(properties.font)
elseif type(properties.font) == "table" then
    character.font = love.graphics.newFont(unpack(properties.font))
end

The LÖVE function I’m calling can accept optional arguments. The code above reflects this and supports it by letting me use properties.font as a string naming a font, or as a table providing multiple arguments to pass along.

Note: The unpack() function, renamed table.unpack() in Lua 5.2, is useful for treating a table as a list of function arguments, e.g.

-- Call foo() with its three required parameters.
foo(10, "Lobby", bar)

-- This accomplishes the same thing.
argumennts = { 10, "Lobby", bar }
foo(unpack(argumennts))

Testing Object Types

Lua supports object-oriented programming but it does not provide language features for declaring classes or objects like you will find in languages like C++ or Java. As you will see from that link, there are a variety of ways to approach OOP in Lua. But the overwhelming majority of developers use metatables as the foundation for their object-oriented code.

I also use approaches where I use metatables to represent classes. So the second form of type-checking I use is to discriminate objects, i.e. checking the metatable (my classes) of tables (my objects). Here is an example, again from LNVL, of a function that changes the image of an object based on its class:

action = function (arguments)
    local targetType = getmetatable(arguments.target)

    if targetType == LNVL.Character then
        -- If the target is a Character then we change their
        -- 'currentImage' to the new one in the instruction.
        arguments.target.currentImage = arguments.image
    elseif targetType == LNVL.Scene then
        -- If the target is a Scene we change its background
        -- image.  However, first we need to assign the 'scene'
        -- parameter of the argument to the 'target'.  The
        -- commentary for LNVL.Opcode.Processor["set-scene-image"]
        -- explains why we must do this here.
        arguments.target = arguments.scene
        arguments.target.backgroundImage = arguments.image
    else
        -- If we reach this point then it is an error.
        error(string.format("Cannot set-image for %s", targetType))
    end
end

This is the most common from of type-checking that I use since it provides me with some measure of polymorphism. Object-oriented programming naturally encourages this style of type-checking since it is useful to group logically-related chunks of code which must behave slightly differently based on the objects they receive. Testing the value of metatables in Lua is the most common way to perform this type of discrimination.

Conclusion

All that said, I still do not perform much type-checking in Lua. Even my two use-cases above are not the norm; they represent uncommon situations. When writing Lua code I try to keep in mind its weak typing compared to other popular programming languages. So instead of looking for ways to introduce the stricter type-checking of a language like C#, I instead ask myself, “What is the most simple and clear way to write this code without any concern for types?”

Consistently asking myself that question along with documenting the answers has helped me avoid a lot of type-related errors in Lua. So I will end this article with some advice that I gave to another developer:

Every program demands a different amount of type checking but my own experience has led me to the conclusion that you’re in better off in Lua by beginning with no type checking and then introducing it only as needed. What constitutes ‘as needed’ for your project is a balance that you will discover over time.

Advertisements

Add Your Thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s