Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lua Metatable Inconsistency

I'm having trouble understanding why there is a difference in behavior of the __index metamethod between these to examples:

A = { __index = A }   
function A:speak()
    print("I'm an A")
end
An_A = setmetatable({},A)
An_A:speak()

Will raise the following error: lua: l.lua:8: attempt to call method 'speak' (a nil value)

Whilst

B = { __index = function(t,key)  return B[key] end }
function B:speak()
    print("I'm an B")
end
An_B = setmetatable({},B)
An_B:speak()

Will perform as expected, outputting I'm an B.


In trying to understand why this was the case I read this section of PiL. It states that:

The use of the __index metamethod for inheritance is so common that Lua provides a shortcut. Despite the name, the __index metamethod does not need to be a function: It can be a table, instead. When it is a function, Lua calls it with the table and the absent key as its arguments. When it is a table, Lua redoes the access in that table.

My understanding of this is that in the snippet involving 'A', __index = A cause the access to be done in the table A (as per the boldened segmenet of the above quote). If this is the case I don't understand why the function associated with the key "speak" isn't found. In an attempt to try fix this, I decided to implement the function approach in the B snippet, which returns the value associated with key in B, and it worked. Surely __index = A and (adapted from B) __index = function(t,key) return A[key] end have the same effect.

Any clarification would be greatly appreciated.

like image 897
HennyH Avatar asked May 11 '13 01:05

HennyH


1 Answers

What's happening in your first example is that A.__index == nil. When you created 'A' on your first line here:

A = { __index = A }

The right-hand side of the the assignment 'A' evaluates to nil since it doesn't exist yet at this point. As a result, later on when you set the metatable here:

An_A = setmetatable({},A)

it really ends up doing something akin to this:

An_A = setmetatable({}, {__index = nil} )

To get it to work the way you want, you have to make sure __index isn't nil. For example, assign it after table construction:

A = {}
A.__index = A

function A:speak()
  print("I'm an A")
end
An_A = setmetatable({},A)
An_A:speak()              --> outputs I'm an A
like image 61
greatwolf Avatar answered Sep 19 '22 23:09

greatwolf