Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Extending type() for class name reflection, and other helpful class reflection extensions

edited July 2013 in Examples Posts: 170

Over the past couple of days I have been experimenting a little bit with reflection in Codea after coming across a situation where I thought I needed to resolve the exact type of an object (which in the end i worked around anyway but it got me thinking...).

I remembered I had seen this function posted previously:

function typeOf(x)
    if x==nil then return 'nil' end
    if type(x) == 'table' and x.is_a then return('class') end
    local txt 
    if typeTable==nil then
    typeTable = {
    [getmetatable(vec2()).__index  ] = 'vec2', 
    [getmetatable(vec3()).__index  ] = 'vec3',
    [getmetatable(color()).__index ] = 'color', 
    [getmetatable(image(1,1)).__index ] = 'image', 
    [getmetatable(matrix()).__index] = 'matrix', 
    [getmetatable(mesh()).__index  ] = 'mesh' ,
    }
    end
    local i = getmetatable(x)
    if i then txt = typeTable[i.__index] end    
    if txt then return txt end
    txt = type(x)
    return txt
end

But this wasn't what I was looking for nor what I am used too in the .net framework where there is an extensive reflection library. What I was looking for at the time was a way to resolve the exact type, class name and possibly even its hierarchy.

So here's the end result of my experimentation three examples in one: How to extend the type function (or create your own typeOf function) to give you almost any objects exact type and name if it has one (including userdata), how to create a simulated base or super keyword for classes and how to get a objects base class or entire class hierarchy :D

Extending the type() function for improved reflection

Extending the type function and reflection may sound a little complex but its quite simple really. Reflection is all about your program or game being able to look inside itself from inside itself (almost like looking in a mirror hence the name reflection :D ).

This method works by exploiting the global table _G and meta tables to allow us to resolve most object type names by looking them up in _G by their meta table. Unfortunately this method requires the code is placed in Main file to ensure all types are available at the time of execution and that userdata is added manually (but this is the same for any other method :) ).

I've added all the user data types I possibly could including, physics.body, physics.joint, touch, buffer and soundbuffer. Tween was not possible without wrapping the table because it returns anonymous tables and physics.collision needs to be done separately when a collision occurs so i thought it overkill.

The actual implementation is quite simple essentially you shallow copy _G into a local at a tactical time, whilst inverting the keys and their values so that the keys become values and the values become keys. Which is an extremely handy and powerful technique to keep in mind for general table tasks as well ;). This then allows us to use an objects meta table to lookup it's string name.

Then just replace the type() function with a new version which returns the base value (so its original functionality is preserved) followed by its friendlier type name if its userdata followed by its class name if it is a class.

--userdata format: specify userdata function for empty ctor, key and default for anything requiring default
local _btype = type
local _getmt = getmetatable

local _iG=(function()
    local _bd={physics.body(CIRCLE,25),physics.body(CIRCLE,25)}

    local _uD={vec2,vec3,vec4,matrix,mesh,shader,color,{"image",image(1,1)},{"soundbuffer",soundbuffer("",0,0)},
    {"rigidbody",_bd[1]},{"touch",CurrentTouch},{"buffer",mesh():buffer("position")},
    {"joint",physics.joint(WELD,_bd[1],_bd[2],vec2())}}

    -- generate inverted _G and add userdata
    local ig = {} for k,v in pairs(_G) do ig[v]=k end 

    -- add userdata as this cant be looked up automatically 
    for k,v in pairs(_uD) do if _btype(v) == "table" then ig[_getmt(v[2])]=v[1] else ig[_getmt(v())]=ig[v] end end

    return ig 
end)()

--real type function, looks up the types meta tabe in the inverted _G table
rtype=function(x) return _iG[_getmt(x)] end

-- overidden type function, returns base type and real type or base type, "class", classname or nil
type=function(x) local t,r = _btype(x),rtype(x)
if t =="table" and x.is_a then return t,"class",r else return t,r or t end end

Note: Code must be outside of setup function and after the last named class type is created (place at the very bottom of main and forget about it perhaps :D)

Usage:

   --identify lua primitives
    local num =10
    local float=1.11
    local bool=false
    local func = function() end
    local tbl = {}
    local str =""
    local cr =coroutine.create(function() end)

    print("Lua Primitives: ")
    print("num: ",type(num))
    print("float: ",type(float))
    print("bool: ",type(bool))
    print("func: ",type(func))
    print("tbl: ",type(tbl))
    print("str:", type(str))
    print("cr: ",type(cr))

    -- identify codea user data types
    local v2 = vec2()
    local v3 = vec3()
    local v4 = vec4()
    local mt = matrix()
    local col =color()
    local img = image(1,1)
    local ms = mesh()
    local msbuf =ms:buffer("position")
    local bdt ={physics.body(CIRCLE,25), physics.body(CIRCLE,25)}
    local bd = bdt[1]
    local jt = physics.joint(WELD,bdt[1],bdt[2],vec2())

    local sb = soundbuffer("",0,0)

    print("Codea Userdata: ")
    print("v2: ",type(v2))
    print("v3: ",type(v3))
    print("v4: ",type(v4))
    print("col: ",type(col))
    print("img: ",type(img))
    print("mt: ",type(mt))
    print("ms: ",type(ms))
    print("msbuf: ",type(msbuf))
    print("CurrentTouch",type(CurrentTouch))
    print("bd: ",type(bd))
    print("jt: ",type(jt))
    print("sb: ",type(sb))
    print("nil: ",type(nil))

    local sc = SomeClass()
    local t,n,cn = type(sc)

    print(t,n,cn)

-- prints: table class SomeClass

Simulating a base or super keyword for classes

Simulating a base keyword has been something I have been thinking would be useful for a while to avoid renaming calls to base classes within my classes when I inevitably change the classes name :D

It suddenly clicked after i stumbled upon this post: http://www.twolivesleft.com/Codea/Talk/discussion/96/class-inheritance/p1 where @Simeon has posted the code behind the class function :D

As you will see the meta table of objects created by the class methods has a _base field added to it on its creation. So a simulated base keyword is as simple as get the meta table for self then return the base from _base field and then chain an instance call to the base function :D ( you could also hold onto the base meta table after getting it the first time ;) )

ClassBase = class()

function ClassBase:base()

-- get meta table of self
    local c = getmetatable(self)

-- check if there's a base type
    if c~= nil and c._base ~= nil then

-- return the base type
        return c._base
    end

end

-- test function
function ClassBase:print(a)
    print(a)
end

ClassA = class(ClassBase)
function ClassA:init()
end

ClassB = class(ClassA)

Usage:


function ClassB:print(tbl) for i,v in ipairs(tbl) do self:base():print(v) end end local cb = ClassB() cb:print({"test1","test2","test3"}) -- prints: test1 test2 test3

Combing the two to find an objects class hierarchy

This then lead me to using the elements of both to implement some handy extra reflection methods.

classname - returns the name of the class or typed passed to it

classbase - returns the base class name of a given instance and its class table which can be used to dynamically access functions or create new instances :)

classinfo - returns the full hierarchy of given object. The given object is always item[1]. The name is accessed with item[1].name then the type is item[1].type.

-- returns the types "classname"
classname=function(x) return _rtype(x) or _btype(x) end

-- returns the base type of a class or the class type
classbase=function(x) local c = _getmt(x) 
if c~=nil and c._base then return _iG[c._base],c._base else return _iG[c],c end end

-- returns the heirachy of a class (listed top down) to the base
classinfo=function(x) local c,b={},_getmt(x) repeat if b ~= nil then table.insert(c,{name=_iG[b],type=b}) 
if b._base then b=b._base else b= nil end end until b == nil return c end

Usage:

    local c2 = ClassB()

    -- get base class name and reference
    local c2basename,c2base=classbase(c2)

    -- dynamically instantiate base class
    local c2dyn=c2base()

    -- get full class heirarchy
    local ch = classinfo(c2)

Comments

  • Jmv38Jmv38 Mod
    Posts: 3,271

    Interesting. Thanks for sharing.

  • IgnatzIgnatz Mod
    Posts: 5,396

    Now I remember why I found .net difficult :-?

  • Posts: 108

    I had an error that table index is nil When ig[_getmt(v[2])]=v[1], I changed it to ig[_getmt(v[1])]=v[1] and it works I think

  • Posts: 170

    @AxiomCrux If you paste the code into the bottom of setup or put it inside a function and call that from setup it should work without modification.

    I'd probably do a lot of this differently now. What are you thinking of using it for? I'd be happy to help if I can :)

Sign In or Register to comment.