[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: why not ++ in lua?
- From: Rici Lake <lua@...>
- Date: Tue, 29 Nov 2005 14:51:40 -0500
On 29-Nov-05, at 11:34 AM, Lisa Parratt wrote:
Surely this makes the handling of + and - metamethods an absolute 
nightmare?
That depends on the semantics, which I think is the core problem. Also 
the fact that introducing side effects into an expression means that 
evaluation order becomes critical. And finally, the whole knotty 
question of what mutation means, which I won't go into again :) -- just 
a few remarks on semantics:
Lua doesn't have any equivalent to C's comma operator (or gcc's 
extensions to that) but it is trivial to define with a macro (of 
course, Lua doesn't have macros either :( ). Let's say that:
  begin <block> end ==>  (function() <block> end)()
just to make the exposition a little nicer.
Now, it's pretty clear how to macro-expand <lvalue>++ :
  <lvalue>++ ==>
     begin local v = <lvalue>'; <lvalue>' = v + 1; return v end
  where <lvalue>' is:
     <lvalue>
        if <lvalue> is a variable (whether local or global)
  and
     temp1[temp2] where temp1 = <table> and temp2 = <key>
        if <lvalue> is of the form <table>[<key>]
  and otherwise an error
(The intent is to only evaluate the table and key expressions once.)
However, ++<lvalue> and (<lvalue> += <expr>) allow for two possible 
expansions. In both cases, we assume:
   ++<lvalue> ==> (<lvalue> += 1)
1) <lvalue> += <expr>  ==>
     begin local v = <lvalue>' + <expr>
           <lvalue>' = v
           return v
     end
2) <lvalue> += <expr>  ==>
     begin <lvalue>' = <lvalue>' + <expr>
           return <lvalue>'
     end
These differ in that the second one does the lookup of <lvalue>' twice; 
they are identical in the case where <lvalue> is a local, but may 
differ if <lvalue> is a gettable operation (or a global variable, which 
is the same thing) and the indexed object (that is, the table or 
whatever) has an __index metamethod.
Which of these expansions you prefer depends on how you view 
metamethods. Option 1 is, on the face of it, more "efficient" but it 
has the disadvantage that it may return a value which the __index 
metamethod would never return.
For example, suppose I have an object which represent an array of 
vectors, and furthermore that __add is overridden for vectors to do 
something sensible. No problems yet, but now I make my array-of-vectors 
object automatically intern [Note 1] any inserted vector. Now, 
formulation (1) returns an uninterned vector, whereas formulation (2) 
correctly returns the interned vector.
As another example, I have an implementation of multi-valued tables (a 
version of it can be found in my recent message on the iteration 
protocol) which has the following semantics:
  mvtab[key]          ==>  returns the first pair <key, value> which 
matches key
  mvtab[key] = value  ==>  adds the pair <key, value> to the table
  mvtab[key] = nil    ==>  removes all pairs <key, *> from the table
  for k, v in mvtab:_pairs(key)
                      ==>  iterates over all pairs <key, *>
  for k, v in mvtab:_pairs(nil)
                      ==>  iterates over all pairs <*, *>
This might be considered an unfortunate interface, but it "works for 
me" :) The intent of this object-type is to be able to handle things 
like HTTP headers and LDAP entries which have multivalued attributes.
Now, one of the disadvantages of this object type is that you cannot 
just write:
  mvtab[key] = mvtab[key] + 1
unless you really wanted to add a new <key, value> pair to the table. 
The following works, but it takes advantage of an unspecified 
evaluation order:
  mvtab[key], mvtab[key] = mvtab[key] + 1, nil
Furthermore, the two formulations of += would provide different 
results; option 1 would return the value of the new <key, value> pair 
whereas option 2 would return the value of the first pair in the table 
whose key was key.
An intermediate proposal with simpler semantics (although its arguably 
a lot uglier) is to allow pseudovariables of the form $<integer> to 
appear in assignment statements; $<i> refers to the rvalue 
corresponding to the i'th lvalue, and, for convenience, the <integer> 
defaults to the current position in the expression list. This would 
allow things like:
  a[i] =$+ 1  -- increment a[i], or whatever a's metamethods make of it.
  a[i], a[j] = $2, $1 -- swap a[i] and a[j]
The first example is only one keystroke longer than += (and two 
keystrokes longer than ++ if you leave out the space), so it might 
satisfy the need. The second example could be handy if i and j were 
actually expressions.
[Note 1]:
Interning refers to the action of ensuring that equal objects are 
unique in memory, which is what Lua does with strings. It would be 
possible for the vector's __add operator to do the interning but this 
might be horribly inefficient for temporary results; moving the 
interning operation to the __newindex metamethod of the array of 
vectors could be considered an optimization. Or it could be considered 
a hack.