March 29, 2007
Sean Kelly wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> kris wrote:
>>> There used to be a tango/example like this variation:
>>>
>>>     import tango.io.Console;
>>>
>>>     void main()
>>>     {
>>>         Cout ("Please enter your name: ").flush;
>>>         Cout ("Hello, ") (Cin.get);
>>>     }
>>
>> Ah, also, the last line is translated into:
>>
>> Cout.opCall("Hello, ").opCall(Cin.get);
>>
>> D does not specify evaluation order, so the code might end up printing "Hello, " before reading the standard input.
> 
> We discussed this a long time ago and came to the conclusion that while the D spec does not guarantee evaluation order for this scenario, it seems impossible for it to be anything other than left to right because the chained calls rely on the return value from previous calls.  If this is untrue then I'd love to know the reasoning.  Perhaps an aggressive inlining mechanism could violate this?

Actually if you go a bit lower-level (explicit 'this'), that line is equivalent to something like:
---
write(write(Cout, "Hello, "), get(Cin));
---
or
---
write(get(Cin), write("Hello, ", Cout));
---
depending on whether the calling convention passes 'this' first or last. (except with more complicated function names because of mangling, obviously)
So actually 'this' is just another parameter. As such, evaluation can happen before or after other parameters.
If the parameters happen to be evaluated in the "wrong" order (left-to-right in the first case, right-to-left in the second) the Cout("Hello, ") gets evaluated before write("Hello, ", Cout).
If it happens to be in the "right" order, it's the other way around. But I'm pretty sure there's no guarantee.
For that matter, 'this' could be treated specially (e.g. always passed in a specific register not otherwise used in the calling convention) and then there's no way to tell what the evaluation order will be, except for a specific implementation.

What I'm trying to say here is basically this: you shouldn't rely on the order of evaluation of parameters. Not even if one of them happens to be used as 'this' for the method in question.
March 29, 2007
Andrei Alexandrescu (See Website For Email) wrote:
> Frits van Bommel wrote:
>> Andrei Alexandrescu (See Website For Email) wrote:
>>> Sean Kelly wrote:
>>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>>> That's great, however the interface has a problem too: it does not produce atomic strings in multithreaded programs.
>>>>
>>>> Which interface?  And are you talking about input or output?
>>>
>>> Cout. The thing is that the design using the syntax Cout(a)(b) is conducive to two separate calls to the library. I recall this has been briefly discussed here.
>>
>> Of course that could easily be fixed if either struct destructors or scope class returning were to be added to the language. Then you could just return something with a destructor that releases the lock.
> 
> Struct destructors will be added, but typesafe variadics are already in. I think Cout(a, b) is the most natural interface to offer.

I dislike typesafe variadics for I/O, mainly because it'll instantiate a new version for every parameter list. I/O functions will typically be called with a lot of different parameter lists, which may lead to code bloat.

However, my point in that post was that using the method I suggested (the first call returning a struct with a destructor) also has the advantage that it allows for backwards-compatibility. The current syntax will continue to work, with the only difference being that now it'll be thread-safe.
March 29, 2007
Frits van Bommel wrote:
> Sean Kelly wrote:
>> Andrei Alexandrescu (See Website For Email) wrote:
>>> kris wrote:
>>>> There used to be a tango/example like this variation:
>>>>
>>>>     import tango.io.Console;
>>>>
>>>>     void main()
>>>>     {
>>>>         Cout ("Please enter your name: ").flush;
>>>>         Cout ("Hello, ") (Cin.get);
>>>>     }
>>>
>>> Ah, also, the last line is translated into:
>>>
>>> Cout.opCall("Hello, ").opCall(Cin.get);
>>>
>>> D does not specify evaluation order, so the code might end up printing "Hello, " before reading the standard input.
>>
>> We discussed this a long time ago and came to the conclusion that while the D spec does not guarantee evaluation order for this scenario, it seems impossible for it to be anything other than left to right because the chained calls rely on the return value from previous calls.  If this is untrue then I'd love to know the reasoning.  Perhaps an aggressive inlining mechanism could violate this?
> 
> Actually if you go a bit lower-level (explicit 'this'), that line is equivalent to something like:
> ---
> write(write(Cout, "Hello, "), get(Cin));
> ---
> or
> ---
> write(get(Cin), write("Hello, ", Cout));
> ---
> depending on whether the calling convention passes 'this' first or last. (except with more complicated function names because of mangling, obviously)
> So actually 'this' is just another parameter. As such, evaluation can happen before or after other parameters.
> If the parameters happen to be evaluated in the "wrong" order (left-to-right in the first case, right-to-left in the second) the Cout("Hello, ") gets evaluated before write("Hello, ", Cout).
> If it happens to be in the "right" order, it's the other way around. But I'm pretty sure there's no guarantee.
> For that matter, 'this' could be treated specially (e.g. always passed in a specific register not otherwise used in the calling convention) and then there's no way to tell what the evaluation order will be, except for a specific implementation.
> 
> What I'm trying to say here is basically this: you shouldn't rely on the order of evaluation of parameters. Not even if one of them happens to be used as 'this' for the method in question.

Exactly so, thanks for the clear explanation.

Andrei
March 29, 2007
Frits van Bommel wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> Frits van Bommel wrote:
>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>> Sean Kelly wrote:
>>>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>>>> That's great, however the interface has a problem too: it does not produce atomic strings in multithreaded programs.
>>>>>
>>>>> Which interface?  And are you talking about input or output?
>>>>
>>>> Cout. The thing is that the design using the syntax Cout(a)(b) is conducive to two separate calls to the library. I recall this has been briefly discussed here.
>>>
>>> Of course that could easily be fixed if either struct destructors or scope class returning were to be added to the language. Then you could just return something with a destructor that releases the lock.
>>
>> Struct destructors will be added, but typesafe variadics are already in. I think Cout(a, b) is the most natural interface to offer.
> 
> I dislike typesafe variadics for I/O, mainly because it'll instantiate a new version for every parameter list. I/O functions will typically be called with a lot of different parameter lists, which may lead to code bloat.

I think this is not a problem (for a well-written library) as those many parameter lists will essentially be sliced and diced into the equivalent hand-written calls.

> However, my point in that post was that using the method I suggested (the first call returning a struct with a destructor) also has the advantage that it allows for backwards-compatibility. The current syntax will continue to work, with the only difference being that now it'll be thread-safe.

This is not going to work. Essentially you are giving the user the license to execute arbitrary code with locked files, a sure recipe for puzzling deadlocks. Cout(a, b) is the way.


Andrei
March 29, 2007
kris wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> kris wrote:
>>
>>> Sean Kelly wrote:
>>> [snip]
>>>
>>>> I must be missing something.  Why is the following not acceptable?
>>>>
>>>>     import tango.io.Console;
>>>>
>>>>     void main()
>>>>     {
>>>>         char[] name;
>>>>         Cout( "Please enter your name: " ).flush;
>>>>         Cin.nextLine( name );
>>>>         Cout( "Hello, " )( name )( "!" );
>>>>     }
>>>
>>>
>>>
>>> There used to be a tango/example like this variation:
>>>
>>>     import tango.io.Console;
>>>
>>>     void main()
>>>     {
>>>         Cout ("Please enter your name: ").flush;
>>>         Cout ("Hello, ") (Cin.get);
>>>     }
>>
>>
>> Ah, also, the last line is translated into:
>>
>> Cout.opCall("Hello, ").opCall(Cin.get);
>>
>> D does not specify evaluation order, so the code might end up printing "Hello, " before reading the standard input. It's funny this does not happen exactly because of buffering, but the program has no control over the buffering so it should assume flushing could happen at any time. So the correct code is:
>>
>> auto name = Cin.get;
>> Cout("Hello, ")(name);
> 
> Well aware of that, thanks. BTW: evaluation order has been clarified before, on a similar topic.

Is that clarification identical with the one posted by Frits?

Andreo
March 29, 2007
Sean Kelly wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> Sean Kelly wrote:
>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>> Bill Baxter wrote:
>>>>>
>>>>> I believe the current discussion is only about under-the-hood implementation issues.  So I don't think you have to worry about any D libraries exposing (good or bad) C/C++ design decisions to users.  Tango is going to expose the same D interface no matter how it's implemented under the hood.
>>>>
>>>> That's great, however the interface has a problem too: it does not produce atomic strings in multithreaded programs.
>>>
>>> Which interface?  And are you talking about input or output?
>>
>> Cout. The thing is that the design using the syntax Cout(a)(b) is conducive to two separate calls to the library. I recall this has been briefly discussed here.
> 
> Just making sure we were on the same page.  In my experience, raw output is typically used for logging--that's about the only case I can think of where the random interleaving of messages is acceptable.  Tango has a separate logging facility which performs synchronization for exactly this purpose: tango.util.log.  Perhaps this particular critique would be more appropriately applied to the logging package?

I don't think so. For example, I have a multithreaded program that starts processes on multiple machines and outputs "stamped" lines to the standard output - lines with a prefix clarifying which machine the line comes from, and at what time. That output is gzipped for efficiency and transported via ethernet to a hub where the lines are demultiplexed and put into multiple files etc.

It is essential that lines are written atomically, but beyond that, the actual order does not matter.


Andrei
March 29, 2007
Sean Kelly wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> kris wrote:
>>> Sean Kelly wrote:
>>> [snip]
>>>> I must be missing something.  Why is the following not acceptable?
>>>>
>>>>     import tango.io.Console;
>>>>
>>>>     void main()
>>>>     {
>>>>         char[] name;
>>>>         Cout( "Please enter your name: " ).flush;
>>>>         Cin.nextLine( name );
>>>>         Cout( "Hello, " )( name )( "!" );
>>>>     }
>>>
>>>
>>> There used to be a tango/example like this variation:
>>>
>>>     import tango.io.Console;
>>>
>>>     void main()
>>>     {
>>>         Cout ("Please enter your name: ").flush;
>>>         Cout ("Hello, ") (Cin.get);
>>>     }
>>
>> Ah, also, the last line is translated into:
>>
>> Cout.opCall("Hello, ").opCall(Cin.get);
>>
>> D does not specify evaluation order, so the code might end up printing "Hello, " before reading the standard input.
> 
> We discussed this a long time ago and came to the conclusion that while the D spec does not guarantee evaluation order for this scenario, it seems impossible for it to be anything other than left to right because the chained calls rely on the return value from previous calls.  If this is untrue then I'd love to know the reasoning.  Perhaps an aggressive inlining mechanism could violate this?

Frits has provided the exact explanation. What you say would be true in the case:

Cout("Hello, ", Cin.get);

Then it's clear that Cout can't possibly be called before Cin.get returned. So variadics are the best solution in this case too :o).


Andrei
March 29, 2007
Andrei Alexandrescu (See Website For Email) wrote:
> kris wrote:
> 
>> Andrei Alexandrescu (See Website For Email) wrote:
>>
>>> kris wrote:
>>>
>>>> Sean Kelly wrote:
>>>> [snip]
>>>>
>>>>> I must be missing something.  Why is the following not acceptable?
>>>>>
>>>>>     import tango.io.Console;
>>>>>
>>>>>     void main()
>>>>>     {
>>>>>         char[] name;
>>>>>         Cout( "Please enter your name: " ).flush;
>>>>>         Cin.nextLine( name );
>>>>>         Cout( "Hello, " )( name )( "!" );
>>>>>     }
>>>>
>>>>
>>>>
>>>>
>>>> There used to be a tango/example like this variation:
>>>>
>>>>     import tango.io.Console;
>>>>
>>>>     void main()
>>>>     {
>>>>         Cout ("Please enter your name: ").flush;
>>>>         Cout ("Hello, ") (Cin.get);
>>>>     }
>>>
>>>
>>>
>>> Ah, also, the last line is translated into:
>>>
>>> Cout.opCall("Hello, ").opCall(Cin.get);
>>>
>>> D does not specify evaluation order, so the code might end up printing "Hello, " before reading the standard input. It's funny this does not happen exactly because of buffering, but the program has no control over the buffering so it should assume flushing could happen at any time. So the correct code is:
>>>
>>> auto name = Cin.get;
>>> Cout("Hello, ")(name);
>>
>>
>> Well aware of that, thanks. BTW: evaluation order has been clarified before, on a similar topic.
> 
> 
> Is that clarification identical with the one posted by Frits?
> 
> Andreo

Walter clarified, a long time ago, that D would evaluate chained-calls from left to right. I suggest you ask Walter?
March 29, 2007
Frits van Bommel wrote:
[snip]
> What I'm trying to say here is basically this: you shouldn't rely on the order of evaluation of parameters. Not even if one of them happens to be used as 'this' for the method in question.

Absolutely agree. Yet, without wishing to go off on a tangent, we're talking about call-chaining and not parameter evaluation-order. These are different things, and there was a painfully long thread on exactly this subject a year or two ago.

Walter closed that one by stating call-chaining can only realistically evaluate from left to right (or something to that effect) and that's what D does.

I'm sure JCC could locate that thread in a heartbeat ... don't know how he does that, but he sure is effective at it :)

March 29, 2007
kris wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> kris wrote:
>>
>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>
>>>> kris wrote:
>>>>
>>>>> Sean Kelly wrote:
>>>>> [snip]
>>>>>
>>>>>> I must be missing something.  Why is the following not acceptable?
>>>>>>
>>>>>>     import tango.io.Console;
>>>>>>
>>>>>>     void main()
>>>>>>     {
>>>>>>         char[] name;
>>>>>>         Cout( "Please enter your name: " ).flush;
>>>>>>         Cin.nextLine( name );
>>>>>>         Cout( "Hello, " )( name )( "!" );
>>>>>>     }
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> There used to be a tango/example like this variation:
>>>>>
>>>>>     import tango.io.Console;
>>>>>
>>>>>     void main()
>>>>>     {
>>>>>         Cout ("Please enter your name: ").flush;
>>>>>         Cout ("Hello, ") (Cin.get);
>>>>>     }
>>>>
>>>>
>>>>
>>>> Ah, also, the last line is translated into:
>>>>
>>>> Cout.opCall("Hello, ").opCall(Cin.get);
>>>>
>>>> D does not specify evaluation order, so the code might end up printing "Hello, " before reading the standard input. It's funny this does not happen exactly because of buffering, but the program has no control over the buffering so it should assume flushing could happen at any time. So the correct code is:
>>>>
>>>> auto name = Cin.get;
>>>> Cout("Hello, ")(name);
>>>
>>>
>>> Well aware of that, thanks. BTW: evaluation order has been clarified before, on a similar topic.
>>
>>
>> Is that clarification identical with the one posted by Frits?
>>
>> Andreo
> 
> Walter clarified, a long time ago, that D would evaluate chained-calls from left to right. I suggest you ask Walter?

As long as it's not in the language definition, you can't count on it. I think this is a language defect anyhow; I am lobbying Walter to define left-to-right order of evaluation in all cases.

Andrei