Lua Metatable Inconsistency
Asked Answered
P

1

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.

Piton answered 11/5, 2013 at 1:59 Comment(0)
C
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
Conducive answered 11/5, 2013 at 2:49 Comment(4)
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 :)Piton
@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 NameErrorPya
@Pya I suppose any recursive function follows the same pattern.Piton
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.Agiotage

© 2022 - 2024 — McMap. All rights reserved.