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