6

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.

1 Answer 1

9

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
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for the great explanation, it didn't occur to me this would happen, I had assumed it would be like some other languages where something such as f = lambda n: f(n) is valid. cheers :)
@HennyH: f = function(n) return f(n) end is fine in lua for the same reason. The equivalent failing python is d = {"__index": d}, but python will NameError
@Eric I suppose any recursive function follows the same pattern.
It would actually work if you set __index to a function accessing A, which is similar to your python example, instead of using A directly: A = { __index = function(t,k) return A[k] end }. Because the function will look up A by name when it's called, and by that time it will have the right value.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.