assignment: left-to-right or right-to-left evaluation?

Steven Schveighoffer schveiguy at yahoo.com
Mon May 11 04:34:38 PDT 2009


On Sat, 09 May 2009 19:15:59 -0400, Derek Parnell <derek at psych.ward> wrote:

> On Sat, 09 May 2009 11:43:09 -0500, Andrei Alexandrescu wrote:
>
>> Consider:
>>
>> uint fun();
>> int gun();
>> ...
>> int[] a = new int[5];
>> a[fun] = gun;
>>
>> Which should be evaluated first, fun() or gun()? It's a rather arbitrary
>> decision. C/C++ don't even define an order. Python chooses
>> left-to-right, EXCEPT for assignment, which is right-hand side first.
>> Lisp and C# choose consistent left-to-right. I don't like exceptions and
>> I'd like everything to be left-to-right. However, this leads to some odd
>> cases. Consider this example in TDPL:
>>
>> import std.stdio, std.string;
>>
>> void main() {
>>    uint[string] dic;
>>    foreach (line; stdin.byLine) {
>>      string[] words = split(strip(line));
>>      foreach (word; words) {
>>        if (word in dic) continue; // nothing to do
>>        uint newID = dic.length;
>>        dic[word] = newID;
>>        writeln(newID, '\t', word);
>>      }
>>    }
>> }
>>
>> If we want to get rid of newID, we'd write:
>>
>>        writeln(dic.length, '\t', word);
>>        dic[word] = dic.length;
>>
>> by the Python rule, and
>>
>>        writeln(dic.length, '\t', word);
>>        dic[word] = dic.length - 1;
>>
>> by the C# rule.
>>
>> What's best?
>
> I'm sure about 'best', but I'd prefer the Python method.

Think you meant 'not sure' :)

>
> The example is similar to ...
>
>     array = array ~ array.length;
>
> in as much as the result of the assignment is that the array length
> changes, but here it more easy to see that the pre-assignment length is
> being used by the RHS.
>
> In COBOL-like syntax ...
>
>    move dic.length to dic[word].
>
> it is also more obvious what the coder's intentions were.
>
> In assembler-like syntax (which is what eventually gets run, of course)  
> ...
>
>    mov regA, dic.length
>    mov dic[word], regA
>
> It just seems counter-intuitive that the target expression's side-effects
> should influence the source expression.
>

This reasoning makes the most sense, but let's leave COBOL out of it :)

I vote for the Python method too.  It's how my brain sees the expression.

Also consider like this:

uint len;

mydic[x] = len = mydic.length;

Now, it's even more obvious that len = mydic.length should be immune to  
the effects of mydic[x].  Longer chained assignment expressions seem like  
they would make the problem even harder to understand if it's all  
evaluated left to right.  You may even make code more bloated because of  
it.

For example:

mydic[x] = mydic[y] = mydic[z] = mydic.length;

if evaluating right to left, this looks like:

1. calculate mydic.length, store it in register A.
2. lookup mydic[z], if it doesn't exist, add it.  Store register A to it.
3. lookup mydic[y], if it doesn't exist, add it.  Store register A to it.
4. ditto for mydic[x]

If evaluating left to right, this looks like:

1. lookup mydic[x], if it doesn't exist, add it.  Store a reference to it  
on the stack.
2. lookup mydic[y], if it doesn't exist, add it.  Store a reference to it  
on the stack.
3. lookup mydic[z], if it doesn't eixst, add it.  Store the reference to  
it in register B.
4. calculate mydic.length, store it in register A.  Store the result in  
the reference pointed to by register B.
5. pop register B from the stack, store register A to the value it  
references.
6. Repeat step 5.

Two extra steps, and I have to use a stack.  Maybe 3 chained assignments  
would be easy to store without a stack, but try 10 chained assignments.

I'd think the compiler code to evaluate right to left would be simpler  
also, because you can reduce the expression at every assignment.

-Steve



More information about the Digitalmars-d mailing list