On Sat, 09 May 2009 19:15:59 -0400, Derek Parnell <de...@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

Reply via email to