 Technical Note 8
Technical Note 8
The non-caching implementation is rather simple. It uses a _parents = {} member in a table to create an inheritance chain.
-- ********************************************************
-- index tag method AdvProtoIndex(t,f)
--
-- This is a 'simple' version of the multiple inheritance
-- tag method. It does not cache and thus it is quite slow.
-- However, if you think something strange is happening, you
-- can fall back to this version and see if the strangeness
-- goes away.
function AdvProtoIndex (t,f)
  
  if f == '_parents' then -- to avoid loop
    if (OldIndex) then
	    return OldIndex(t,f)
	else
		return nil;
	end
  end
  local p = t["_parents"];
  if (type(p) == 'table') then
     local cur_index = 1; -- start at 1
	 local cur_data;
	 repeat
	   cur_data = p[cur_index];
	   if (type(cur_data) == 'table') then
	       local result = cur_data[f];
	       if (result ~= nil) then
		       return result;        -- we found a match
		   end
	   else
	       if (OldIndex) then
		      return OldIndex(t,f);
		   else
		      return nil;
		   end
	   end
	   cur_index = cur_index + 1; -- do next parent
	 until (cur_data == nil);
	 return nil; -- we didn't find a match
  else 
     return nil;
  end
end
I normally setup this fallback tag method for all-tables, which means
that creating inheritance is as simple as creating tables with the
appropriate members:
a_base = {
  a_number = 1
}
b_base = {
  b_number = 2
}
ab_class = {
  _parents = { a_base, b_base }
}
print(ab_class.a_number); -- yields "1"
print(ab_class.b_number); -- yields "2"
Using the simple implementation above, it's easy to create inheritance
chains which severely impact run-time performance, because an
inherited method call or instance data access can result in
2n lookups, where n is the number of base classes
inherited from in the whole chain.
A performance optimized implementation is provided which functions the mostly same but is drastically faster.
----------------------------------------------------------
-- AdvProtoIndexWithCache
--
-- This inheritance tag method handles multiple inheritance via a
-- "_parent = {}" table. It caches information in the top-level table
-- to make lookups fast.
--
-- Example:
--
-- This tag method is applied to all tables, so all you have to do to
-- get a magic inheritance table is to do this:
--
-- BaseObj1 = { a_value = "a" }
-- BaseObj2 = { b_value = "b" }
-- MyClass  = { _parents = { BaseObj2, BaseObj1 } }
-- MyInstance = { _parents = { MyClass }
--
function setupMultipleInheritenceForTag(a_tag) 
    -- I like to setup my tag methods within a function because
    -- then stuff like these private declarations can be
    -- referenced with upvalues and disappear. :)
    local NIL_OBJECT = { magic_nil_object = 1}
    local SLOT_REF_TAG = newtag()
    local OldIndex = gettagmethod(tag({}),"index")
    local debug_mode = nil
    AdvProtoIndexWithCache = function (t,f, instance, depth)
      if (f == '_parents') or (f == '_slotcache') then -- to avoid loop
	if (%OldIndex) then
		return %OldIndex(t,f)
	    else
		return nil;
	    end
      end
      if instance == nil then
	-- we are the instance!
	instance = t 
      end
      if depth == nil then
	depth = 0
      end
      -- get out the parent table
      local p = rawgettable(t,"_parents")
      local cache = rawgettable(instance,"_slotcache");
      if cache then
	 local item = rawgettable(cache,f)
	 if item then
	   if item == %NIL_OBJECT then
	     return nil
	   elseif tag(item) == %SLOT_REF_TAG then
	     return item.obj[f]
	   else
	     return item
	   end
	 end
      else
	 -- if we are the instance AND we had a _parents
	 -- slot, then create the slot cache!
	 if (instance == t) and (p) then
	   cache = {}
	   rawsettable(t,"_slotcache",cache); -- make the slot cache!
	 end
      end
      if (type(p) == 'table') then
	 local cur_index = 1; -- start at 1
	     local cur_data;
	     repeat
	       cur_data = p[cur_index];
	       if (%debug_mode) then
		 print("---------", cur_index, depth)
	       end
	       if (type(cur_data) == 'table') then
		   if (%debug_mode) then
		     printTables(cur_data)
		   end
		 -- local result = cur_data[f];
		   local result = rawgettable(cur_data,f);
		   if (%debug_mode and (result ~= nil)) then
		     print("value: ", result)
		   end
		   -- if we found the slot in us, then we need
		   -- to do the caching, because after we return
		   -- it's not possible to make a SLOT_REF
		   if ((result ~= nil) and (cache)) then
                     rawsettable(instance,f,result);
		   end
		   if (result == nil) then
		     result = AdvProtoIndexWithCache(cur_data,f,instance, depth + 1)
		   end
		   if (result ~= nil) then
		     if (%debug_mode and (result ~= nil)) then
		       print("value: ", result) 
		     end
		     return result;        -- we found a match
		   end
	       else
		   local result 
		   if (%OldIndex) then
		     result = %OldIndex(t,f);
		   else
		     result = nil;
		   end
			   if cache then
			     if (type(result) == "function") then
			       rawsettable(cache,f,result);
			     elseif result == nil then 
			       rawsettable(cache,f,%NIL_OBJECT)
			     else
			       local slot_ref = { obj = cur_data }
			       settag(slot_ref,%SLOT_REF_TAG);
			       rawsettable(cache,f,slot_ref);
			     end
			   end
			   return result;        -- we found a match
	       end
	       cur_index = cur_index + 1; -- do next parent
	     until (cur_data == nil);
	     if cache then
	       rawsettable(cache,f,%NIL_OBJECT)
	     end
	     return nil; -- we didn't find a match
      else 
	 return nil;
      end
    end
    settagmethod(a_tag,"index",AdvProtoIndexWithCache);  -- Lua 3.1 method
end
In the cache, functions are pointed to directly, and other items are pointed to using something called a "SLOT_REF". A SLOT_REF is a special kind of table which is a cache by reference instead of by value. It contains a reference to the table and index of the original value so that if the original data value changes, this SLOT_REF will correctly point to the new data-value. This is not done for methods, because it was decided that performance of method lookups is more important than retaining the ability to change methods in base-classes.
Another implementation of this machinery could be even faster by doing away with SLOT_REF, and instead using some method to invalidate slotcaches (such as maintaining a reverse-slotcache dependency list). Whenever a base-class method or function was changed, the reverse dependency chain's slotcaches would be invalidated. This would probably result in only moderate extra data-keeping if the "_slotcache" were changed to occur at the "class" level of the object instead of the "instance" level as it does now.
It's important to recognize that because the caching version stores information directly in a single flattened table, changes to the base class methods may be ignored if they are already in the cached table. In practice, changing methods of a base class is an infrequent operation. When using this machinery, one should avoid this practice entirely.
Because Lua (IMO mistakenly) overrides nil as meaning both false and an empty data element, there is no way to override an inherited object member with the nil value. This is because setting self.a = nil will result in the removal of the "a" member, thus causing the missing-index tag method to fire, which consults the _parents or _slotcache tables to find an inherited element "a". I've not yet found a workaround for this problem.
The full code for the solution, including some helpful utility functions, is provided here.