lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


--[[
**** Summary:
****
**** How do I create an inheritance chain that can use __index
**** getter methods for some properties, while passing in
**** the original calling table to the function?
**** 
**** Details:
****
**** I've written my own class system in Lua, with single
**** inheritance and shared properties and methods. Primary goals
**** are lookup speed and sharing metatables where possible, while
**** providing a specific set of features. It's working well, leaning
**** heavily on __index=table for no-function-call inheritance.
**** 
**** To use it to mimic an old interface, I now need to add getter
**** methods tied to properties, so that asking for "foo.bar" will
**** run a function to calculate the value of 'bar', using properties
**** set on the foo object/table.
**** 
**** I'm having trouble coming up with a way to do this that passes
**** the foo table along as necessary.
**** 
**** What follows is a simplified version of my current setup,
**** showing the problem in the end.
--]]

  -- Each 'class' has its own prototype table for properties
  -- specific to its instances.
  Rectangle = {}
  Rectangle.prototype = {
    class   = Rectangle,
    polygon = true
  }

  -- A shared metatable for subclasses to use
  Rectangle.protometa = { __index = Rectangle.prototype }

  -- A shared metatable for instances to use prototype lookup
  -- (In practice, this is has additional properties different
  -- from protometa; that's why they're different.)
  Rectangle.instancemeta = { __index = Rectangle.prototype }

  
  
  -- An instance
  myRect = { width=10, height=20 }
  setmetatable( myRect, Rectangle.instancemeta )
  
  -- Sanity check
  assert( myRect.width   == 10 )
  assert( myRect.class   == Rectangle )
  assert( myRect.polygon == true )

  
  
  -- A subclass
  Square = { superclass=Rectangle }
  
  -- Properties that all the square instances inherit...
  Square.prototype = { class = Square }
  -- ...and thence on to the Rectangle.prototype
  setmetatable( Square.prototype, Rectangle.protometa )

  -- Shared metatable for all square instances
  Square.instancemeta = { __index = Square.prototype }


  
  -- Another instance
  mySquare = { width=15, height=15 }
  setmetatable( mySquare, Square.instancemeta )
  
  -- More sanity checks, including Rectangle.prototype
  assert( mySquare.width   == 15 )
  assert( mySquare.class   == Square )
  assert( mySquare.polygon == true )

  

  -- So far so good.
  -- Now I want to add some getter methods masquerading as properties
  local theMeta = { }
  theMeta.getters = {
    ploygon = function( ) return "I think you mean 'polygon'" end,
    area    = function( object )
      print( "...Calculating area on", object )
      return object.width * object.height
    end
  }
  theMeta.__index = function( obj, property )
    print( "...Looking for '"..property.."' on ", obj )
	 local theFunc = theMeta.getters[ property ]
	 if theFunc then
	   return theFunc( obj )
	 end
  end
  setmetatable( Rectangle.prototype, theMeta )
  
  

  -- Let's test it out!
  
  print( myRect.ploygon )
  --> ...Looking for 'ploygon' on 	table: 0032B550
  --> I think you mean 'polygon'
  
  print( mySquare.area )
  --> ...Looking for 'area' on 	table: 0032B550
  --> ...Calculating area on	table: 0032B550
  --> ...Looking for 'width' on 	table: 0032B550
  --> ...Looking for 'height' on 	table: 0032B550
  --> Lua50.exe: tmp.lua:59: attempt to perform arithmetic on field
`width' (a nil value)

  -- Bummer. What is the 'obj' passed to the getter then?
  print( myRect )
  --> table: 0032BA68
  
  print( mySquare )
  --> table: 0032BF70

  print( Rectangle.prototype )
  --> table: 0032B550
  
  
  --> Ah.
  
--[[
**** The problem, thus, is that when __index is used on table A to
**** look at parent table B, and table B has its own __index function,
**** it is table B that is passed to the function, not table A.
****
**** If it was only one level of inheritance, I would change the
**** __index on table A (Square.instancemeta in the above) to perform
**** the lookup, and all would be well.
****
**** Is my only choice to write my own lookup mechanism, then?
**** Perhaps use __index on a chain of getter function hierarchy?
--]]