May 25, 2015
On Monday, 25 May 2015 at 12:38:29 UTC, kinke wrote:
> On Monday, 25 May 2015 at 08:00:15 UTC, Jonathan M Davis wrote:
>> It might be completely well-defined and consistent, but it may not be what you expect, and even if it is, a slight change to the code could change the order.
>
> If the behavior isn't what I expect (and I don't think that's often case for left-to-right order), then the language should force me to express the intention differently. If it's not well-defined, I may not be aware of such issues until I use a different compiler.

It seems like you still don't understand. Yes, defining the order of evaluation within an expression as being left-to-right makes it easier to deal with what is directly inside the expression, and the compiler could be make to do that (and from the sounds of it likely will). It could also be made to not be fixed about the order of evaluation but give an error when it detects that the order of evaluation matters, so that expressions like

foo(++i, ++i);

give an error. But even so, the compiler _cannot_ be made to catch all such problems for you, because all it takes is starting to bury the problem within other function calls within the expression, and while the order of evaluation in such cases may very well be defined, whether it's going to do what you expect is a completely different matter. For instance, what if you had

int bar()
{
    return ++i;
}

foo(bar(), bar());

Now, because ++i is inside a call to another function, the compiler can no longer see that the arguments that you're using depend on one another. The results _are_ well-defined, but whether it's what you expect is another matter. With one extra layer like this, you'll probably see it, and it'll be doing what you want, but what if you have an expression like

auto f = foo(bar() + baz(bop(), beep() + boop()));

and 4 levels down into the call stack bar() and beep() both mutate a static or global variable - or some other shared resource. Then the result of this expression ends up depending on an order of evaluation issue that you can't see without really digging into the code, and the compiler sure isn't going to see for you. You might see that swapping the arguments around in the expression results in a different result when you think that it shouldn't, but just as likely, you wouldn't catch that, and a small change to the code later could change the results unexpectedly, which you might or might not notice.

Now, that sort of thing is all the more reason to avoid using static or global variables, and it's the sort of thing that I would hope good code would avoid. But defining the order of evaluation as left-to-right, doesn't make those problems go away. At best, it makes them consistent, and that may be worth it, but it's not a silver bullet. And it takes optimization opportunities away from the compiler, since in many cases, it can reorder how the expression is evaluated to make better use of the registers and whatnot. So, forcing the order of evaluation is not without its cons, even if it did solve all order of evaluation issues, and it really doesn't - especially when that often depends on what you expect.

Ketmar had a screwy example with arrays a while back that he was complaining bitterly about not working due to order of evaluation issues, but IIRC he had recursive function calls which affected each other and was having all kinds of problems just because he insisted on doing multiple things in an expression rather than splitting them out. And the results he was getting were completely consistent; they just weren't what he wanted. The order of evaluation mattered too much in the expressions that he was writing.

Ultimately, if you want to reduce the risk of bugs, you really should be writing expressions where the order of evaluation doesn't matter, or where it only matters based on operator precedence directly within the expression so that it really doesn't matter what other code is doing. And forcing left-to-right evaluation doesn't change that. All it really does is make what what's happening consistent. It doesn't mean that relying on it is a good idea or that it's going to fix anything but the some of the most basic order of evaluation issues.

Personally, I don't think that it's worth the cost in lost optimizations, but even if it is, my point here is really that at best it only fixes part of the problem.

- Jonathan M Davis
May 25, 2015
On 5/24/15 11:13 PM, Iain Buclaw via Digitalmars-d wrote:
> The context here involves concurrency where bar() calls yield and makes
> changes to foo before returning to assign the updated results.

We're not addressing that. += is not supposed to do concurrency magic. -- Andrei
May 25, 2015
On 5/25/15 1:00 AM, Jonathan M Davis wrote:
> foo(++i, ++i);

More complete example:

table[++i] = objTable[++i].funcTable[++i](++i, ++i);

should be well defined and evaluate left to right.


Andrei

May 25, 2015
Am Mon, 25 May 2015 09:40:34 -0700
schrieb Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org>:

> On 5/24/15 11:13 PM, Iain Buclaw via Digitalmars-d wrote:
> > The context here involves concurrency where bar() calls yield and makes changes to foo before returning to assign the updated results.
> 
> We're not addressing that. += is not supposed to do concurrency magic. -- Andrei

It's not += doing the magic, it's bar(). And it's not limited to concurrency, it happens with every side effect:

import std.stdio;
void main()
{
    int a = 0;
    int bar()
    {
        a++;
        return a;
    }
    a += bar(); // => a = a + bar()
    writeln(a);
}

DMD: 2
GDC: 1

which one is correct?


May 25, 2015
On 5/25/15 10:21 AM, Johannes Pfau wrote:
> Am Mon, 25 May 2015 09:40:34 -0700
> schrieb Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org>:
>
>> On 5/24/15 11:13 PM, Iain Buclaw via Digitalmars-d wrote:
>>> The context here involves concurrency where bar() calls yield and
>>> makes changes to foo before returning to assign the updated results.
>>
>> We're not addressing that. += is not supposed to do concurrency
>> magic. -- Andrei
>
> It's not += doing the magic, it's bar(). And it's not limited to
> concurrency, it happens with every side effect:
>
> import std.stdio;
> void main()
> {
>      int a = 0;
>      int bar()
>      {
>          a++;
>          return a;
>      }
>      a += bar(); // => a = a + bar()
>      writeln(a);
> }
>
> DMD: 2
> GDC: 1
>
> which one is correct?

GDC. -- Andrei


May 25, 2015
On 05/25/2015 07:21 PM, Johannes Pfau wrote:
> Am Mon, 25 May 2015 09:40:34 -0700
> schrieb Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org>:
>
>> On 5/24/15 11:13 PM, Iain Buclaw via Digitalmars-d wrote:
>>> The context here involves concurrency where bar() calls yield and
>>> makes changes to foo before returning to assign the updated results.
>>
>> We're not addressing that. += is not supposed to do concurrency
>> magic. -- Andrei
>
> It's not += doing the magic, it's bar(). And it's not limited to
> concurrency, it happens with every side effect:
>
> import std.stdio;
> void main()
> {
>      int a = 0;
>      int bar()
>      {
>          a++;
>          return a;
>      }
>      a += bar(); // => a = a + bar()
>      writeln(a);
> }
>
> DMD: 2
> GDC: 1
>
> which one is correct?
>
>

With left-to-right evaluation, 1 is correct. Java and C# also give 1.
May 25, 2015
"Andrei Alexandrescu"  wrote in message news:mjvlv5$vch$1@digitalmars.com...

> > which one is correct?
>
> GDC. -- Andrei

I don't think it matters too much if we pick strict LTR, or keep dmd's existing exception for assign expressions.  IIRC Walter is in favour of keeping the exception[1].

Could you and Walter please come to an agreement and confirm here?  It should be fairly straightforward to get this fixed once it's clear which way it should go.

[1] https://github.com/D-Programming-Language/dmd/pull/4035#issuecomment-58861231 

May 25, 2015
On Monday, 25 May 2015 at 17:21:05 UTC, Johannes Pfau wrote:
> import std.stdio;
> void main()
> {
>     int a = 0;
>     int bar()
>     {
>         a++;
>         return a;
>     }
>     a += bar(); // => a = a + bar()
>     writeln(a);
> }
>
> DMD: 2
> GDC: 1
>
> which one is correct?

So what about my previous example?

int b = 0;
((++b *= 5) *= 2) += ++b * (b -= 6);

DMD 2.067.1: 60, latest LDC: 65, GDC: ?

This divergence probably doesn't have anything to do with the evaluation order, which seems to be identical (LTR), but rather how the lhs expression is treated (a double-nature as nested lvalue to be assigned to and rvalue result of a binAssign expression). For more context, see https://github.com/ldc-developers/ldc/pull/873.
May 25, 2015
On 25 May 2015 21:00, "Daniel Murphy via Digitalmars-d" < digitalmars-d@puremagic.com> wrote:
>
> "Andrei Alexandrescu"  wrote in message news:mjvlv5$vch$1@digitalmars.com.
..
>
>
>> > which one is correct?
>>
>> GDC. -- Andrei
>
>
> I don't think it matters too much if we pick strict LTR, or keep dmd's
existing exception for assign expressions.  IIRC Walter is in favour of keeping the exception[1].
>
> Could you and Walter please come to an agreement and confirm here?  It
should be fairly straightforward to get this fixed once it's clear which way it should go.
>
> [1]
https://github.com/D-Programming-Language/dmd/pull/4035#issuecomment-58861231

Yeah, but his reasoning only applies to x86.  This makes it void in my books.


May 25, 2015
On 25 May 2015 at 21:02, kinke via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> On Monday, 25 May 2015 at 17:21:05 UTC, Johannes Pfau wrote:
>
>> import std.stdio;
>> void main()
>> {
>>     int a = 0;
>>     int bar()
>>     {
>>         a++;
>>         return a;
>>     }
>>     a += bar(); // => a = a + bar()
>>     writeln(a);
>> }
>>
>> DMD: 2
>> GDC: 1
>>
>> which one is correct?
>>
>
> So what about my previous example?
>
> int b = 0;
> ((++b *= 5) *= 2) += ++b * (b -= 6);
>
> DMD 2.067.1: 60, latest LDC: 65, GDC: ?
>
>
If the litmus test is "What does GDC do?", then LDC is doing it the correct way. :-)