Type Introspection |
|
First, why do we need type introspection? It may be that you don't even need to use type introspection at all. Duck typing [2] and [polymorphism] can dispatch based on type without programmer intervention. There are reasons you may still want to inspect type, such as for debugging purposes [3] or type checking (LuaTypeChecking). You may also want to dispatch code in a particular custom way, such as how [string.gsub] behaves differently depending on whether repl
is a string, table, or function. Unfortunately, the lines are sometimes blurred, such as FuncTables, which are tables with a call operator allowing them to behave like functions. Also, maybe you want to define a function that loads data from either a string or a file specified by filename, but both are strings, so you need some other way to distinguish them (e.g. two separate functions like [loadstring] and [loadfile]).
The [type] function returns the basic type of value as a string. There are only eight basic types in Lua ("nil"
, "number"
, "string"
, "boolean"
, "table"
, "function"
, "thread"
, or "userdata"
).
The [lua_type] function in the C API is similar to type
but returns an integer constant. It also differentiates between the two types of userdata: LUA_TUSERDATA and LUA_TLIGHTUSERDATA (see LightUserData). There is also a lua_typename
function that converts this integer back to a string in the form returned by type
.
The [io.type] function determines whether the given object is a file handle (as well as whether it is open or closed). A number of user libraries follow a similar pattern by providing their own function that checks whether the given object is of a type defined by the library, either returning true/false
or a class name string. This function is implemented using methods described below. This style can, however, go against duck typing and can prevent (LuaVirtualization).
The equality operator, or more precisely the [rawequal] function can check if two values have the same identity (and by implication are the same objects since objects have unique identity). If all objects in a class are stored as keys in a table, then you check for identity by indexing the table (preferably weak keyed table, although prior to Lua 5.2 that has a caveat--GarbageCollectingWeakTables):
local isfoo_ = setmetatable({}, {__mode='k'}) function newfoo() local self = {} isfoo_[self] = true return self end function isfoo(o) return isfoo_[o] end
If all objects in a class, and only those objects, have a certain metatable (or value returned by a __metatable
metamethod), then you can check that metatable:
local foo_mt = {} function newfoo() local self = setmetatable({}, foo_mt) return self end function isfoo(o) return getmetatable(o) == foo_mt end
To obtain the name of a user-defined type as a string, you might first obtain the type of the object and then obtain the name from the type. You might do to this via tostring
on the type, but some consider tostring
only for debugging. Or you might store a _NAME
field in the type or even in the object itself [9][4] (TypeOf). The reason for this choice of field name is that a module's _NAME
field is set by the [module] function, and a module is often used as a type, and even those not using the module function (LuaModuleFunctionCritiqued) may still follow the convention of using _NAME
. Unfortunately, indexing an object might raise an error rather than return nil
(duck typing can also have this problem).
Some have modified the type
function to support user-defined types, possibly by querying a __type
metamethod [5][6]. This may break existing code that expects only basic types returned or add some overhead globally. You could, however, swap in a custom type
function locally: local type = mytype
. Others have made user-defined types (or subtypes) be returned only as a second return value of type
[7][8].
Some object-oriented frameworks (ObjectOrientedProgramming) implement their own introspection capabilities, but these may not be compatible with objects not created with that framework.
Not all these type introspection methods are fool-proof against intentional abuse (see SandBoxes). An object's identity, however, cannot be forged in normal Lua usage. Also, by restricting access to the debug library and providing a __metatable
metamethod, can can prevent public access to an object's metatable, as well as public knowledge of the metatable's identity. These can hinder abuse from untrusted code.
Note: for a relatively simple function that will allow you to print
diverse objects without Lua crashing, see IntrospectionFunctionsLua.