[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Deep __newindex on assignment?
- From: Peter Cawley <lua@...>
- Date: Tue, 22 Dec 2009 21:13:48 +0000
On Tue, Dec 22, 2009 at 8:46 PM, Todd Berkebile
<todd_berkebile@hotmail.com> wrote:
> Hum, I guess there are three reasons I can think of, though one is perhaps mistaken. The perhaps mistaken reason is that it seems like the normal Lua behavior is for a stack variable to be a copy, not a reference. Consider the following code:
>
> local a = 10
> local b = a -- 'b' is a new number with the same value as 'a'
> b = b + 1 -- this only changes 'b', not 'a', because its a copy
>
> Normal tables work the same way:
>
> local a = { member=10 }
> local b = a.member -- again 'b' is a copy, not a reference
> b = 5 -- only 'b' is modified, not 'a'
Some people feel it is wrong to say so, but if it helps you to
understand the Lua model, then you can think that complex objects
(tables and userdata) are always passed/copied by reference, whereas
simple objects (numbers, strings, etc.) are always passed/copied by
value.
A different way to think of things is slightly more removed from how
things are in C. Consider the set of values, and the set of variables
to be separate, and think of variables as binding to values. In your
first example, a is bound to the value 10, then b is bound to 10, then
b is bound to 11. In your second example, In your second example, you
create a table value, bind the "member" field of this table to 10,
then bind the variable a to this table. Then you bind b to the value
bound to a.member, i.e. you bind b to 10. Then you bind b to 5.
Consider an example of mine:
a = {}
b = a
b.member = 10
print(a.member) --> 10
Here a new table is created, a is bound to said table, then b is bound
to the same table. Hence a and b are names referring to the same
thing. The statement "b.member = 10" takes the value which b is bound
to, and binds the "member" field to 10. Hence "a.member" is also 10,
as the expression "a.member" takes the value bound to the "member"
field of the value bound to a.
> As such, I would expect my userdata to work exactly the same as the plain Lua types and tables in the example above:
>
> local a = GetMyUserData()
> local b = a.member -- b is a new object with the same value as 'a.member'
> b = 5 -- I expect this to only change 'b', not 'a'
>
> I'm a C++ guy, not a Lua guy, so perhaps my expectations are incorrect due to language bias? Do userdata types normally act differently?
Userdata act normally, though they have no predefined semantics, so
metamethods are called on them more often. Your userdata example is
working in exactly the same way as it would work if there was a normal
Lua table in place of the userdata. After the second line, b will have
the same value as a.member, but it will not be a new object.
Regardless of the value of b, the line "b = 5" just rebinds b to the
value 5, and has no effect on the previous binding of b.
> The final reason is that to have a consistent API I would end up needing to wrapper every type of property in order to perform the back assignment, even the properties that are plain Lua string or number types. Most properties are not compound values, they are simple strings or numbers. For example, if 'Count' returns a plain Lua number for consistency I'd still need the wrapper for cases like this:
>
> local c = propBag.Count -- 'c' is a proxy in order to implement reference semantics
> if type(c) == "number" then -- doesn't work anymore, type is always 'proxy'
> c = c + 5 -- proxy makes this back assignment update propBag
> print tostring(c + 5) -- ugh, c doesn't act like a number anymore
> end
>
> If I make 'c' a proxy rather than just a number now it behaves very different, simple things like "c + 5" and "tostring(c)" now require complex metatables to make my proxy act like a number. If the returned value is only a proxy when the property type is a userdata then the API is inconsistent and confusing as compound types and simple types have completely different usage patterns.
If 'Count' is a plain Lua number, then I do not see why it would need
any wrapping. Assigning propBag.Count to any variable will just bind
the the value of said variable to the value of propBag.Count.
Regardless of what you do, "c = c + 5" will not update propBag, as it
will calculate c + 5 (via metamethods if necessary) and then bind c to
this new value.
I guess it comes down to the two different kinds of assignment:
temp = object.field
temp = 4 -- Totally different object.field = 4
temp = object.complicated_field
temp.sub_field = 4 -- The same as object.complicated_field.sub_field = 4
A simple assignment like "temp = 4" just rebinds a variable. A member
assignment like "temp.sub_field = 4" or
"object.complicated_field.sub_field = 4" rebinds (invoking metamethods
if necessary) the value of the "sub_field" member of
"object.complicated_field".