Weird __newindex behaviour

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Weird __newindex behaviour

Scott Morgan
Seeing the following behaviour on various version of Lua:

Lua 5.2.2  Copyright (C) 1994-2013 Lua.org, PUC-Rio
~ mt = { __newindex = function(t,k,v)
~~ print("Setting :", k, "to", v)
~~ rawset(t,k,v)
~~ end }
~ tab = setmetatable({},mt)
~ return tab
table: 0x671df0
~ return tab.test
nil
~ tab.test = 123
Setting : test to 123
~ tab.test = nil
~ tab.test = nil
Setting : test to nil
~ tab.test = nil
Setting : test to nil
~ tab.test = 321
Setting : test to 321
~ return tab.test
321
~ tab.test = nil
~ return tab.test
nil
~ tab.test = nil
Setting : test to nil

Why isn't the first `tab.test = nil` event being handled by the
metatable? As mentioned, I'm seeing this across Lua versions, 5.1, 5.2
(as above) and 5.3 (and different OS's/compilers)

It's weird because, surely, if this is a bug, it has already been seen.
It's such a common pattern. I must be doing something wrong here?

Scott

Reply | Threaded
Open this post in threaded view
|

Re: Weird __newindex behaviour

Duncan Cross
On Tue, Sep 22, 2015 at 4:41 PM, Scott Morgan <[hidden email]> wrote:
> Seeing the following behaviour on various version of Lua:
(...)
> It's weird because, surely, if this is a bug, it has already been seen.
> It's such a common pattern. I must be doing something wrong here?

The __newindex metamethod is only triggered when the given index does
not already exist in the table. I believe the generally recommended
solution for catching every assignment is to use a proxy table, i.e.
keep the actual table empty and set up the __index and __newindex
metamethods to redirect accesses to another, hidden table.

-Duncan

Reply | Threaded
Open this post in threaded view
|

Re: Weird __newindex behaviour

Oliver Kroth
In reply to this post by Scott Morgan
Because __newindex handles assignments.
You are doing a lookup; for this __index() would be relevant.

--
Oliver

Am 22.09.2015 um 17:41 schrieb Scott Morgan:

> Seeing the following behaviour on various version of Lua:
>
> Lua 5.2.2  Copyright (C) 1994-2013 Lua.org, PUC-Rio
> ~ mt = { __newindex = function(t,k,v)
> ~~ print("Setting :", k, "to", v)
> ~~ rawset(t,k,v)
> ~~ end }
> ~ tab = setmetatable({},mt)
> ~ return tab
> table: 0x671df0
> ~ return tab.test
> nil
> ~ tab.test = 123
> Setting : test to 123
> ~ tab.test = nil
> ~ tab.test = nil
> Setting : test to nil
> ~ tab.test = nil
> Setting : test to nil
> ~ tab.test = 321
> Setting : test to 321
> ~ return tab.test
> 321
> ~ tab.test = nil
> ~ return tab.test
> nil
> ~ tab.test = nil
> Setting : test to nil
>
> Why isn't the first `tab.test = nil` event being handled by the
> metatable? As mentioned, I'm seeing this across Lua versions, 5.1, 5.2
> (as above) and 5.3 (and different OS's/compilers)
>
> It's weird because, surely, if this is a bug, it has already been seen.
> It's such a common pattern. I must be doing something wrong here?
>
> Scott
>


Reply | Threaded
Open this post in threaded view
|

Re: Weird __newindex behaviour

Javier Guerra Giraldez
In reply to this post by Scott Morgan
On Tue, Sep 22, 2015 at 10:41 AM, Scott Morgan <[hidden email]> wrote:
> Why isn't the first `tab.test = nil` event being handled by the
> metatable?


__newindex is a fallback, not an override.  that means that it's only
called when the table doesn't have that index already.  that is, when
it's a new index.

if you want to get all settings, you do a "proxy table", i.e. don't
store in the object table itself, but on an different, internal table.

--
Javier

Reply | Threaded
Open this post in threaded view
|

Re: Weird __newindex behaviour

Liam Devine
In reply to this post by Scott Morgan
On 22/09/15 16:41, Scott Morgan wrote:

>
> Seeing the following behaviour on various version of Lua:
>
> Lua 5.2.2  Copyright (C) 1994-2013 Lua.org, PUC-Rio
> ~ mt = { __newindex = function(t,k,v)
> ~~ print("Setting :", k, "to", v)
> ~~ rawset(t,k,v)
> ~~ end }
> ~ tab = setmetatable({},mt)
> ~ return tab
> table: 0x671df0
> ~ return tab.test
> nil
> ~ tab.test = 123
> Setting : test to 123
> ~ tab.test = nil
> ~ tab.test = nil
> Setting : test to nil
> ~ tab.test = nil
> Setting : test to nil
> ~ tab.test = 321
> Setting : test to 321
> ~ return tab.test
> 321
> ~ tab.test = nil
> ~ return tab.test
> nil
> ~ tab.test = nil
> Setting : test to nil
>
> Why isn't the first `tab.test = nil` event being handled by the
> metatable? As mentioned, I'm seeing this across Lua versions, 5.1, 5.2
> (as above) and 5.3 (and different OS's/compilers)
>
> It's weird because, surely, if this is a bug, it has already been seen.
> It's such a common pattern. I must be doing something wrong here?
>
> Scott
>
Nothing weird or even a bug present here, it is your understanding of
__newindex which is incorrect.

""newindex": The indexing assignment table[key] = value. Like the index
event, this event happens when table is not a table or when key is not
present in table. The metamethod is looked up in table. "

At the first call to setting the key's associated value to nil, the key
already exists and has an associated value of 123.

--
Liam


signature.asc (836 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Weird __newindex behaviour

Oliver Kroth
In reply to this post by Scott Morgan
Oh,

now I see better. You were talking about
tab.test = nil, not return tab.test (which is nil)
But the anwser is simple too:
You set tab.test = 123, calling your metatable's __newindex.
Then you set it to nil, as it existing, __newindex isn_t called.
Becasue it got nil, it disappeared from the table.
The next table.test = nil calls __newindex() as tab.test is now new.

--
Oliver


Am 22.09.2015 um 17:41 schrieb Scott Morgan:

> Seeing the following behaviour on various version of Lua:
>
> Lua 5.2.2  Copyright (C) 1994-2013 Lua.org, PUC-Rio
> ~ mt = { __newindex = function(t,k,v)
> ~~ print("Setting :", k, "to", v)
> ~~ rawset(t,k,v)
> ~~ end }
> ~ tab = setmetatable({},mt)
> ~ return tab
> table: 0x671df0
> ~ return tab.test
> nil
> ~ tab.test = 123
> Setting : test to 123
> ~ tab.test = nil
> ~ tab.test = nil
> Setting : test to nil
> ~ tab.test = nil
> Setting : test to nil
> ~ tab.test = 321
> Setting : test to 321
> ~ return tab.test
> 321
> ~ tab.test = nil
> ~ return tab.test
> nil
> ~ tab.test = nil
> Setting : test to nil
>
> Why isn't the first `tab.test = nil` event being handled by the
> metatable? As mentioned, I'm seeing this across Lua versions, 5.1, 5.2
> (as above) and 5.3 (and different OS's/compilers)
>
> It's weird because, surely, if this is a bug, it has already been seen.
> It's such a common pattern. I must be doing something wrong here?
>
> Scott
>


Reply | Threaded
Open this post in threaded view
|

Re: Weird __newindex behaviour

Scott Morgan
In reply to this post by Javier Guerra Giraldez
On 22/09/15 16:50, Javier Guerra Giraldez wrote:
> __newindex is a fallback, not an override.  that means that it's only
> called when the table doesn't have that index already.  that is, when
> it's a new index.
>
> if you want to get all settings, you do a "proxy table", i.e. don't
> store in the object table itself, but on an different, internal table.

Ah, I follow, knew I was missing something simple. Misunderstood
newindex. Proxy tables for general stuff like this.

Would this also work for globals? Copy the contents of _G (using Lua 5.1
in this example) into a private table, nil out _G's entries and set it's
metatable to reference the private table.

Scott


Reply | Threaded
Open this post in threaded view
|

Re: Weird __newindex behaviour

Dirk Laurie-2
2015-09-22 18:18 GMT+02:00 Scott Morgan <[hidden email]>:

> On 22/09/15 16:50, Javier Guerra Giraldez wrote:
>> __newindex is a fallback, not an override.  that means that it's only
>> called when the table doesn't have that index already.  that is, when
>> it's a new index.
>>
>> if you want to get all settings, you do a "proxy table", i.e. don't
>> store in the object table itself, but on an different, internal table.
>
> Ah, I follow, knew I was missing something simple. Misunderstood
> newindex. Proxy tables for general stuff like this.
>
> Would this also work for globals? Copy the contents of _G (using Lua 5.1
> in this example) into a private table, nil out _G's entries and set it's
> metatable to reference the private table.

In 5.2/5.3, rather do this in a script [1]:

_ENV=setmetatable({},{__index=_G})

This give you full flexibility. You can make the original
value inaccessible (assign something to `_G.whatever`)
or keep it lurking in the wings (assign something to `whatever`,
thus changing _ENV.whatever but not affecting _G.whatever.).

[1] It does not work that way in an interactive session: what you do
to _ENV on a line then only remains in effect until the end of that
line (or group of lines if you entered a multiline chunk).