Metatables, attempt to call method 'rename' (a nil value) - lua

This is my first time using metatables, I did a simple script to test in Lua demo but it always give me "attempt to call method 'rename' (a nil value)", why?
peds = {}
function peds.new ( name )
local tb = { name = name }
setmetatable ( tb, { __index = peds } )
return tb
end
function peds.rename ( name )
self.name = name
return self.name == name
end
local ped = peds.new ( "max" )
ped:rename ( "randomname" )

There's two (possible) problems in your code, depending on how you are setting things up.
If you are just typing the above into a REPL, then when you declare local ped = ... it immediately goes out of scope and becomes inaccessible. So the expression ped:rename is invalid, although it should report "ped is nil" not "rename is nil".
If you are saving the above to a script and loading it using load_file or something, you will still get a problem, because this function signature isn't right:
function peds.rename ( name )
should be:
function peds.rename ( self, name )
Similar to how it works in C++, in lua, when you make an object method, you have to take the hidden self parameter first, and when you call ped:rename( "random name" ) that's just syntactic sugar for ped.rename(ped, "random_name"). If the self parameter doesn't exist then it's not going to work, or may even say "function not found / rename is nil" because the signatures don't match up.

Related

lua - differece of : and . when define function?

hope you having great day , i'm test some stuff with lua, and i got some error
i have this codes in Object.lua
local Object = {name = "Object"}
Object.__index = Object
function Object.new()
local t = setmetatable({}, Object)
return t
end
function Object:keys(table)
local keyset={}
local n=0
for k,v in pairs(table) do
n=n+1
keyset[n]=k
end
return keyset
end
return Object
and i call Object.keys in "Other" script like this
local Object = require("lua.app.common.utils.Object")
local t = {
[1] = a,
[2] = b,
[3]= c
}
Object.keys(t)
and this cause error with
Exception has occurred: bad argument #1 to 'for iterator' (table expected, got nil),
because the parameter 'table' is passed as nil ( i don't know why thuogh debug mode just says its a nil)
on the other hand, if i fix object.keys(table) to object:keys(table), everything works fine, why this error happening?
Declaring the method keys with a colon adds an implicit self parameter as the first argument which requires you to call the method in the same way, using the colon.
Calling the method with the period notation expects you to supply the self parameter but because you only pass in t this is interpreted as self and then nil is added for your table parameter.
Declaring the method with a colon you actually have this
function Object:keys(self, table)
but calling it with the period means you have done this
Object.keys(t, nil)
hence the "table expected, got nil" error

Dynamic reference to Lua table method, param passed in is null

I'm creating a dynamic table method reference and trying pass a single param to the method. The dynamic method reference does work and the table method is called just as expected, however the completely not nil param I'm passing to the method is nil inside the method. Can you point out my error in these 2 lines?...
Here is a small working example that demonstrates. On first line in the Consider:Move method, mons is nil
local Consider = {}
function Consider:Move( mons )
print( 'Mons ' .. mons.type .. ' considering Move...')
actionChosen.score = 0
return actionChosen
end
local mons = { type = 'Blue' }
local actionPref = 'Move'
local considerAction = Consider[actionPref]
print( 'MonsterAI:chooseAction mons: ', mons.type )
local actionTest = considerAction( mons )
Functions defined using the colon operator hides an additional first self argument. function Consider:Move(mons) is syntactic sugar for function Consider.Move(self, mons).
Calling the function like considerAction(mons) sets the hidden self argument instead of the desired one.
You might want to pass the Consider table as self:
considerAction(Consider, mons)
Or, alternatively, define the function using the dot operator if you don't need self:
function Consider.Move(mons)
print('Mons ' .. mons.type .. ' considering Move...')
end

How "self" gets to access the key of a local table?

I am new to Lua, I am studying this video. At 38:56, a code is presented as below:
Sequence = {}
function Sequence:new()
local new_seq = {last_num = 0}
self.__index = self
return setmetatable(new_seq, self)
end
function Sequence:next()
print(self.last_num)
end
My understanding is that self is equivalent to Sequence, and self is set as a metatable of new_seq, and this metatable's __index is also self.
And last_num is one key of table new_seq but not one key of self, how come in the definition of next function you can write self.last_num as treating last_num is one key of self?
Moreover, before calling setmetatable, there is self.__index = self, I thought only metatable has __index as a special key, but before calling
setmetatable, Sequence is just a regular table and it is not a metatable yet, how come it has __index?
Short version:
My understanding is that self is equivalent to Sequence…
self is an implicit argument resulting from the method-style function definition. Within the function, it will refer to whatever value gets passed in as the first argument to that function.
(The rest of your questions around self arise from that same confusion.)
I thought only metatable has __index as a special key…
A metatable is just a table. __index is just a key like any other, you can define a field with that name on any table. Only when a lookup on a table fails and Lua notices that this table has an attached metatable, the metatable's field named __index has a special meaning – because that's where Lua will look for a handler.
__index containing a table is just another special case of a handler (because it's so common), __index = some_other_table is roughly equivalent to __index = function( table, key ) return some_other_table[key] end – i.e. "go look over there in some_other_table if table[key] was empty". (It may help to use the long version and print something from there if you have trouble following what happens.)
Long version, de-sugaring the code and walking through the details:
A definition function foo:bar( ... ) is the same as function foo.bar( self, ... ) (the name self is automatically chosen, roughly like this in other languages). Additionally, function foo.bar( ... ) is the same as foo.bar = function( ... ). Which means the above code is the same as…
Sequence = {}
Sequence.new = function( self )
local new_seq = { last_num = 0 }
self.__index = self
return setmetatable( new_seq, self )
end
Sequence.next = function( self )
print( self.last_num )
end
…which is equivalent to…
Sequence = {
new = function( self )
local new_seq = { last_num = 0 }
self.__index = self
return setmetatable( new_seq, self )
end,
next = function( self )
print( self.last_num )
end,
}
So, in essence, what this defines is a table that contains two functions, each taking a single parameter. The second of the two functions, next, is pretty simple: it just prints the content of the field last_num of whatever table it's passed (using the name self to refer to it).
Now, just as for definitions, there's some :-syntax sugar for calls. A call foo:bar( ... ) translates to foo.bar( foo, ... ), so when you have some_sequence and say some_sequence:next( ), what happens is a call some_sequence.next( some_sequence ) – the :-syntax for definitions introduces an extra hidden parameter and the :-syntax for calls fills in that extra parameter. In this way, the function that you're treating as a method has access to the table that you're treating as an object and everything works out nicely.
The new function is a bit more involved -- I'll rewrite it into yet another equivalent form to make it easier to read:
function Sequence.new( self )
self.__index = self
return setmetatable( { last_num = 0 }, self )
end
So for whatever table gets passed in, it assigns that table to the field __index of that same table and returns a new table with that old table set as the metatable. (Yes, this thing is confusing… don't worry, just keep reading.) To see why and how this works, here's an example:
If you say some_sequence = Sequence:new( ), you'll have the following structure:
some_sequence = { last_num = 0 } -- metatable:-> Sequence
Sequence = { new = (func...), next = (func...), __index = Sequence }
Now, when you say some_sequence:next( ), this translates to the call some_sequence.next( some_sequence ). But some_sequence doesn't have a field next! Because some_sequence has a metatable, Lua goes and looks at that – in this case, the metatable is Sequence. As a lookup (or "index") operation "failed" (it would have returned nil), Lua looks for a handler in the metatable's field __index, finds a table (Sequence again) and re-tries the lookup on that one instead (finding the next function we defined).
Which means in this case we could have equivalently written Sequence.next( some_sequence ) (but in general you don't want to – or can't – manually resolve these references). As described above, next just prints the value of the field last_num of the table it received -- in this case it got some_sequence. Again, everything works out nicely.
Some more remarks (and yet another example):
For an introductory example, the code is much more mind-bending and brittle than necessary. Here's yet another version (that's not identical and actually behaves differently, but should be easier to understand):
Sequence = { }
Sequence.__index = Sequence
function Sequence.new( )
return setmetatable( { last_num = 0 }, Sequence )
end
function Sequence:next( )
print( self.last_num )
end
Both the version that you have and this version will print 0 when you run the following:
some_sequence = Sequence:new( )
some_sequence:next( )
(I've described above what happens under the hood when you do this for your code, compare and try to figure out what happens with my version before reading on.)
This will also print 0 for both versions:
sequences = { [0] = Sequence }
for i = 1, 10 do
local current = sequences[#sequences]
sequences[#sequences+1] = current:new( )
end
local last = sequences[#sequences]
last:next( )
What happens under the hood differs significantly for both versions. This is what sequences will look like for your code:
sequences[0] = Sequence -- with __index = Sequence
sequences[1] = { last_num = 0, __index = sequences[1] } -- metatable:->Sequence
sequences[2] = { last_num = 0, __index = sequences[2] } -- metatable:->sequences[1]
sequences[3] = { last_num = 0, __index = sequences[3] } -- metatable:->sequences[2]
...
and this is what it will look like with my version:
sequences[0] = Sequence -- __index = Sequence, as set at the start
sequences[1] = { last_num = 0 } -- metatable:->Sequence
sequences[2] = { last_num = 0 } -- metatable:->Sequence
sequences[3] = { last_num = 0 } -- metatable:->Sequence
...
(If you'd instead say sequences[#sequences+1] = Sequence:new( ) in the loop above, your code would also produce this.)
With my version, the call last:next( ) fails to find next, looks at the metatable (Sequence), finds an __index field (again, Sequence) and finds next, then proceeds to call it as described above.
With your version, the call last:next( ) fails to find next, looks at the metatable (sequences[9]), finds an __index field (sequences[9]), fails to find next and therefore looks at the metatable (of sequences[9], which is sequences[8]), finds an __index field (sequences[8]), fails to find next and therefore looks at the metatable ... (until we reach sequences[1]) ... fails to find next, looks at the metatable (Sequence), finds an __index field (Sequence) and finally finds next, then proceeds with the call. (This is why I said it's quite hard to follow...)
The code that you have implements prototype-based OOP, with all the pros and cons. As you've seen, the lookup traverses the whole chain, which means that you could define a function sequences[5].next to do something else and subsequently sequences[5] through sequences[10] would find that other function. This can be really useful – no need for all the boilerplate to define a new class to change some functionality, just adjust one object and use it like a class. (This can also be annoying if you accidentally do this.)
My version implements something a bit closer to the class-based OOP seen in many other languages. (You can't accidentally override methods for more than one object at once.) What both of these (and many other approaches to OOP in Lua) have in common is that defining a field of an object with the same name as a method will hide that method and make it inaccessible. (If you define some_sequence.next, saying some_sequence:next( ) or some_sequence.next( some_sequence ) will immediately find the next you defined and Lua won't bother to look at the metatable and so on.)

Setting __index of current environment in Roblox

In Roblox Studio, I have a ModuleScript object that implements an analogous class to the one shown in chapter 16 of the 1st edition of Programming In Lua, as shown below:
local h4x0r = { }
local function setCurrentEnvironment( t, env )
if ( not getmetatable( t ) ) then
setmetatable( t, { __index = getfenv( 0 ) } )
end
setfenv( 0, t )
end
do
setCurrentEnvironment( h4x0r );
do
h4x0r.Account = { };
setCurrentEnvironment( h4x0r.Account );
__index = h4x0r.Account;
function withdraw( self, v )
self.balance = self.balance - v;
return self.balance;
end
function deposit( self, v )
self.balance = self.balance + v;
return self.balance;
end
function new( )
return setmetatable( { balance = 0 }, h4x0r.Account )
end
setCurrentEnvironment( h4x0r );
end
end
return h4x0r
I then attempted to use the following script to access the Account class, assuming that all of the members of the 2nd do-end block would be assigned to h4x0r.Account:
h4x0r = require( game.Workspace.h4x0r );
Account = h4x0r.Account;
account = Account.new( );
print( account:withdraw( 100 ) );
The above script fails with the error Workspace.Script:5: attempt to call method 'withdraw' (a nil value), so it must be an issue regarding the line where I set the __index field of h4x0r.Account.
Can someone please explain to me where I went wrong?
Try using getfenv(2) and setfenv(2, t) instead of getfenv(0) and setfenv(0, t). You essentially want to change the environment of the encapsulating function, which would be stack level 2.
0 is a special argument that would instead get or set the environment of the thread, which is used as a default environment in some cases, but that does not affect the individual closures that have already been instantiated in the thread, hence it doesn't work in this case.

Attempt to index global 'self' (a nil value) _

I ran this code and it gave me an error attempt to index global 'self' (a nil value)
hook.Add("Think", "Cooking", function()
local Position = self:GetPos() + self:GetAngles():Up()*20
local rawFood = ents.FindByName("my_raw_food")
for k, v in pairs(rawFood) do
if(Position:Distance(v:GetPos()) <= 25) then
v:Remove()
timer.Create(self:EntIndex() .. "my_food", 5, 1, function() self:createFood() end)
end
end
end )
It's hard to say without seeing more of the code, especially the scope around your code.
But it sounds like "self" doesn't exist in the scope. Either it should be supplied as a parameter to the function:
hook.Add("Think", "Cooking", function(self)
print(self) -- uses the 'self' parameter
end)
or it should be available in the scope of declaring the function, and it'll be part of the closure:
function MyClass.addHook(self) -- same as MyClass:addHook()
hook.Add("Think", "Cooking", function()
print(self) -- uses the 'self' in scope (la MyClass instace)
end)
Though, self can of course be nil even if it's declared in the scope. Calling MyClassInstance.addHook() instead of MyClassInstance:addHook() is the most common.
self is used when using object oriented programming as described in the documentation 16 - Object-Oriented Programming.
In order to use self you must implicitly pass it as the first argument or not.
I mean...
myObject = { id = 1 }
function myObject:hello( name )
print( "hello " .. name .. " I'm object id : " .. tostring( self.id ) )
end
// Using the . char the object must be the first argument
myObject.hello( myObject, "world" )
// Using the : char the obect is automatically set as the first arg
myObject:hello( "world" )
So in your code, I guess you should use the : char.
hook:add(...)

Resources