December 10, 2005
In article <dne6ha$3jp$1@digitaldaemon.com>, Chris Sauls says...
>
>BCS wrote:
[...]
>> // is this
>> b(a)(i);
>> 
>> //this
>> int t1 = a.opCall(i)
>> b.opCall(t1);
>> 
>> //or this
>> B t2 = b.opCall(a);
>> t2.opCall(i);
>
>According to my understanding of the docs, and experience with D, the second one.  Really this whole discussion is silly.  The chained calls in the Whisper
> syntax can not be reordered, because each subsequent call is dependant on the previous call's return value. In 100% of cases of:
># obj (a) (b) (c) ;
>
>It is guaranteed that `?.opCall(b)` can not possibly be executed before
> `obj.opCall(a)` because of that little question-mark there, which stands for the return value of `obj.opCall(a)`.  Its all really very straightforward... in fact it reminds me of the sort of thing that would've been on a test in my High School C++ class.
>
[...]


I'm not sure where the expression ‘x.a().b().c()' came from, but this (or the
explicit use of opCall) eliminates the ambiguities that the whisper syntax
introduces. So getting back to that. The ambiguities come from the fact that ()
can be translated to a function call or just a set of parens. Example from my
last post:

struct A  {  int opCall(int);  }
struct B  {  B opCall(A);      B opCall(int)  }

A a;
B b;
int i;

b(a)(i);

if evaluated from the left, the parens around ‘a' are function call:
(   b.opCall(a)   ).opCall(i);

if evaluated from the right the parens around ‘a' just parens:
b.opCall(    (a).opCall(i)   );


December 10, 2005
Ivan Senji wrote:

[...]
> Can you please enlighten me and explain why is x.a().b().c()
> illegal?

As stated above `x.a().b().c()' is illegal in the general case.

This stems from the definition in the specs:
| Unless otherwise specified, the implementation is free to
| evaluate the components of an expression in any order. It is an
| error to depend on order of evaluation when it is not
| specified.

Intensified by
| Parenthesis control operator precedence, parenthesis do not
| control order of evaluation.

In `x.a().b().c()' postfix expressions are stringed together for which the order of evaluation is not specified. Therefore its an error to depend on the order of evaluation.

I.e. `x.a().b().c()' is legal only if there is a proof that the order of evaluation does not change the intended result.


In case of your example you state, that `x.a().b().c()' is equivalent to `c(b(a(x)))'.

This seems to indicate, that `a(x)' must be evaluated first. If so, `x.a().b().c()' is illegal according to the specs.

If `a(x)' is not required to be evaluated first every try of proof of independeny of evaluation must fail, because nothing is known of the intended result or the impact of the calls on the intended result.


Examples:
1) if `a()', `b()' and `c()' are known to be neutral operations
with respect to the intended result, then `x.a().b().c()' is legal
2) if in your example `c' does not care about its argument and `a'
and `b' are neutral with respect to the intended result, then `x.a
().b().c()' is legal

But without any additional knowledge every claim of independency of the evaluation order is ungrounded and therefore the proof for the general case "`x.a().b().c()' is illegal" holds.

-manfred
December 10, 2005
In article <dne4og$22l$1@digitaldaemon.com>, Kris says... [...]
>Not "should". The compiler *does* evaluate `b(a)', and then each subsequent opCall in turn :-)

"Does" is only relevant for a given compiler If you are talking in general, what is of interest is what the compiler "must" do.

[...]
>Don't know about you, but I'm talking about a language claiming to follow on from C++ (and Java for that matter). Both those languages support method-chaining. C supports function chaining. DMD currently supports method chaining on two platforms. GDC supports method chaining. That's three D compilers.
[...]

IIRC the front end for all three is identical.

>Thus, you've picked entirely the wrong person to have an "academic debate" with. If you want to complain about the documentation not being explicit about chaining behaviour, then take it up with Walter?

Bingo. This is a question about ambiguities (or lack of) in the language definitions, not current implementations.

>You really should ask him ... if you feel you can't do that, then I will.

Strait to the source:

Walter, what is your opinion on this?


December 10, 2005
In article <Xns9728D4F579A89svv1999hotmailcom@63.105.9.61>, Manfred Nowak says...
>
>Ivan Senji wrote:
>
>[...]
>> Can you please enlighten me and explain why is x.a().b().c()
>> illegal?
>
>As stated above `x.a().b().c()' is illegal in the general case.
>
>This stems from the definition in the specs:
>| Unless otherwise specified, the implementation is free to
>| evaluate the components of an expression in any order. It is an
>| error to depend on order of evaluation when it is not
>| specified.
>
>Intensified by
>| Parenthesis control operator precedence, parenthesis do not
>| control order of evaluation.
>
>In `x.a().b().c()' postfix expressions are stringed together for which the order of evaluation is not specified. Therefore its an error to depend on the order of evaluation.
>

I think the question is how can x.a().b().c() be evaluated in an order OTHER
than c(b(a(x)))?

The ?.b( ) part can't be executed until the ? part (x.a()) has been determine,
and the same for the ?.c() part. Unless I'm way off my rocker, there is no other
practicable way to build the code. If I'm wrong please tell me how.

p.s.

For that matter is this legal?

int i = f() + 2*f();

what if this is f();

int f() { static int r = 0; return r++; }


December 10, 2005
BCS wrote:

[...]
>>In `x.a().b().c()' postfix expressions are stringed together for
>>which the order of evaluation is not specified. Therefore its an
>>error to depend on the order of evaluation.
>>
> 
> I think the question is how can x.a().b().c() be evaluated in an
> order OTHER than c(b(a(x)))?
> 
> The ?.b( ) part can't be executed until the ? part (x.a()) has
> been determine, and the same for the ?.c() part. Unless I'm way
> off my rocker, there is no other practicable way to build the
> code. If I'm wrong please tell me how.

Sorry to say that to a really intelligent one: you are wrong. Look through the thread and you will find some examples. Most convincing to me is the following observation:

If `typeof( x.a().b()).c()' and `typeof( x.a()).b()' are accepted by
the compiler without errors, then each of the calls in `x.a().b().c
()' can be executed without the need to first call `a()', then `b()'.

This is for example the case when `a', `b' and `c' are `static' struct/union/class members or functions, not delegates.

> p.s.
> 
> For that matter is this legal?
> 
> int i = f() + 2*f();
> 
> what if this is f();
> 
> int f() { static int r = 0; return r++; }

It is not legal if the intended result is the value of the variable `i'. It is legal, if the intended result is to write something to `dout'.

-manfred
December 10, 2005
"mclysenk" <mclysenk_member@pathlink.com> wrote ...
> While I must concur with Kris and Derek that the whisper syntax is
> unambiguous,
> I disagree with its philosophy.

~~~~~~~~~~~~~~~~

Phew  ~ a rational perspective!  Do you mind if I change the subject-title?

You make a good point about the atomicity of call-chaining vs variadic arguments. Please permit me to offer an alternate viewpoint?


> One of the best things about D is type safe variadic arguments. Do we
> really
> need to go back to the bad old days of C++?

As noted before, mango.io was around long before typesafe variadic args. Mango.io was built like it is to explicitly gain type-safety where it was lacking before. Variadic typeinfo is great, yet extracting the actual type is not particularly efficient. More importantly, variadic typeinfo still only works reasonably for output; not input. Sean will attest to this, as he's done in the past ~ it's awkward to write an scanf() using typeinfo as it stands ... still requires pointers from the user. On the other hand, the Whisper notation is identical from the user perspective, for both input and output operation. It is always completely explicit about type, at compile-time. I feel the latter is important ~ other don't, and that's perfectly fine.


> writef/readf accomplish all the goals of stream insertion, but do not
> suffer
> from the lack of atomicity. Consider the following example,

The scenario describes a type of race-condition; where two or more threads contend for a shared resource. In this particular case, it's the console. I don't have to tell you that race-conditions usually have to be explicitly managed in one way or another, so I won't. Instead, I'll just note that mango.io is explicit about shared data ~ it never hides anything internally that might be shared between threads, thus requiring that all possible shared entities be provided by the programmer (such as a buffer). This is pretty self-evident, yet is often overlooked.

BTW: this atomicity concern refers to phobos.Stream just as much as it applies to mango.io

~~~~~~~~~~~~~~~~~~


So, what about atomicity? Does writefln() have some kind of an advantage over call-chaining, aka whisper notation? The answer is both yes and no. The notion that writefln() is atomic is only partly true ~ for one thing it is not synchronized. But that aside, it depends on what you want to call atomic. For instance:

writefln("header info %? %?", blah, foo);
// create some output data
writefln("content: %", wumpus);
// now create a footer
writefln("footer info %? %?", wombat, arff);

That's a contrived example, but the point is one of scope: how much atomicity is expected at any given time? What the user should do, where multiple threads are contending, is to be explicit about atomicity. Thus, it might be something like this contrived example:

synchronized (someGlobalLock)
{
writefln("header info %? %?", blah, foo);
// create some output data
writefln("content: %", wumpus);
// now create a footer
writefln("footer info %? %?", wombat, arff);
}

or some variation upon that. You see what I'm getting at? The atomicty of writefln() is really in the eye of the beholder ... even assuming it were itself synchronized at the function level. This is, I imagine, why writefln() does not synchronize (at least, I doubt that it does, and you impled it doesn't).

It is certainly why mango.io does not synchronize ~ would be just a waste of cycles, and is noted in the Stdout documentation.

~~~~~~~~~~~~~~~~~~~~~~

So what does mango.io do about this? Well, by design, it does support this kind of approach:

synchronized (Stdout)
{
    Stdout (blah blah blah);
    Stdout (blah blah blah);
}

At least there's a known, common synch point if you really, really need to do that kind of thing :-)

Mango.io also has a Print() object, which you can alias as writefln() if you like:

Print ("%d green bottles", 10);    // uses variadic args!

or even this:

synchronized (Print)
{
    Print ("%d green bottles", 10);
    Print ("hanging on the wall");
}

Please note that Stdout and Print are merely static object wrappers upon a flexible foundation. The mango.log package provides yet another alternative:

auto log = Logger.getLogger ("my.logger.name");
log.trace ("some kind of formatted " ~ "message");

If the logger is configured for the console, the output there will be "atomic". Of course, you can also do this kind of thing:

auto sprint = new Sprint (1024);
log.info (sprint ("%d green bottles", 10));

Mango.convert has both struct and class based formatting; all completely thread-safe; and all fully Unicode aware. More so than Phobos, and faster too, for what that's worth :-)

(note: mango formatting avoids the writef() issue of extracting format chars from each string).

Mango.log has far more interesting logging facilities that just the console, but it's noted here for the purposes of atomic comparison.

~~~~~~~~~~~~~~~~~~~~~~~~


> Moreover,
> writef and readf make it easier to specify things like formatting. Just
> add some
> format args, and you can spitout hex output or binary just as easily as
> decimal.

Very true. This is why mango.io is a multi-level design. The whisper notation is intended to provide general purpose I/O for text and binary data. Mango.convert has all the printf() like facilities, which are then wrapped in a number of different ways for both classes and structs (the latter are great when you need to avoid the heap):

Sprint ~ binds a formatter to a char/wchar/dchar array. Format ~ binds a delegate-trio to a formatter.

The formatting is bound to the console in two alternate ways, so you can choose what feels comfortable:

Print ~ binds a formatter to the console
Stdout ~ binds a formatter and a Whisper-writer to the console.

Additionally, mango.convert will *never*, itself, touch the heap. Never. From a throughput or server standpoint, that would be tantamount to criminal activity :-)

~~~~~~~~~~~~~~~~~~~~~~


> Ultimately, I believe the whisper syntax should be avoided since it seems redundant and error prone,

I'm afraid I cannot agree. Surprise ;-)

It's not redundant since it's more efficient that varargs, it works very cleanly for input as well as output, it catches type-errors at compile-time, and all the other 10 reasons I gave a couple of days back :-)

It's not error-prone. At least, not in the way you describe ~ which was about race conditions on the console. We can split hairs about what constitutes a level of atomicity all day, but in the end mango.io is certainly no worse off than Phobos in that regard. You can use Print if you prefer, or better yet, use mango.log instead! The latter will send your application output across the Internet if you like (to Chainsaw). Plus, you can dynamically configure the output of a running application via the web-based Log Manager. That's really a very powerful combination. Perhaps now that the intermittant cyclic-static-ctor thing appears to have been worked around, more people will actually give it a whirl?

~~~~~~~~~~~~~~~~~~~~~~~~

> not mention incongruous with the D style.

As to being incongruous to D style ~ I think that's perhaps a bit subjective? I really like having choice and flexibility in a library. I personally like the clear, simple, and obvious symmetry of Whisper:

output (time) (cost) (materials) ();
input  (time) (cost) (materials);

I like some of the extensibility aspects too. It's horses-for-courses, wouldn't you agree?

~~~~~~~~~~~~~~~~~~~~~~~~~~

I'll just add that mango.io was built originally for high-throughput in a highly-threaded environment ~ t'was built very-much with threads in mind. The Phobos I/O at the time was not even close to being applicable for what was needed. And to this day it's still, ahh, uncohesive. Phobos is great for some folks. Mango is there for others, if they wish to use it.

~~~~~~~~~~~~~~~~~~~~~~~~~

Thanks for the provocative points, mclysenk. I do appreciate it, and am well aware that mango.io is not for everyone :-)

- Kris


December 10, 2005
Manfred Nowak wrote:
> Ivan Senji wrote:
> 
> [...]
> 
>>Can you please enlighten me and explain why is x.a().b().c()
>>illegal? 
> 
> 
> As stated above `x.a().b().c()' is illegal in the general case.
> 

Still i don't agree.

> This stems from the definition in the specs:
> | Unless otherwise specified, the implementation is free to
> | evaluate the components of an expression in any order. It is an
> | error to depend on order of evaluation when it is not
> | specified.
> 
> Intensified by
> | Parenthesis control operator precedence, parenthesis do not
> | control order of evaluation. 
> 

But this probably refers to the parenthesis in expressions, nut to function call parenthesis.

> In `x.a().b().c()' postfix expressions are stringed together for which the order of evaluation is not specified. Therefore its an error to depend on the order of evaluation.

If this is not defined in the spec than this is an error in the spec. But the fact remains that there is not but one sane order of evaluation.

Are you saying this isn't defined:

> 
> I.e. `x.a().b().c()' is legal only if there is a proof that the order of evaluation does not change the intended 

That would be true in just a few cases.

> 
> 
> In case of your example you state, that `x.a().b().c()' is equivalent to `c(b(a(x)))'.

Yes it is.

> 
> This seems to indicate, that `a(x)' must be evaluated first. If so, `x.a().b().c()' is illegal according to the specs.

OK. If i agree that there is an error in the spec not mentioning that the order of evaluation in this case is defined, will you agree that there is only one meaningful way to evaluate these expressions?

> 
> If `a(x)' is not required to be evaluated first every try of proof of independeny of evaluation must fail, because nothing is known of the intended result or the impact of the calls on the intended result.

But it is required. You cannot call b when you don't know what are you passing to it's first argument.

> 
> 
> Examples:
> 1) if `a()', `b()' and `c()' are known to be neutral operations 

But this can't possibly be known, or would be atleast very hard to do in a compiler as there are no const methods.

> with respect to the intended result, then `x.a().b().c()' is legal

Yes.

> 2) if in your example `c' does not care about its argument and `a' and `b' are neutral with respect to the intended result, then `x.a
> ().b().c()' is legal

No. It is legal beacuse it has a well defined (but maybe missing from spec) order of evaluation :)-

> 
> But without any additional knowledge every claim of independency of the evaluation order is ungrounded and therefore the proof for the general case "`x.a().b().c()' is illegal" holds.
> 

My proof wasn't trying to prove independency of the order of evaluation as that would be near impossible, it was mearly trying to proove there is only one sane order.
December 10, 2005
Egg on my face (ick :-p) this can only be compiled one way:

a(b)(c)

if the compiler tries from the right, as I suggested earlier, you get the following (with <> indicating groupings)

a < (b)(c) >
a < b(c) >
a ret_b	// illegal expression

While a (poorly done) compiler might not be able to parse it, it can't generate any other interpretation of it ether.


And for something completely different...

In article <Xns9728E3BB1149Bsvv1999hotmailcom@63.105.9.61>, Manfred Nowak says...
>
>BCS wrote:
>
>[...]
>>>In `x.a().b().c()' postfix expressions are stringed together for
>>>which the order of evaluation is not specified. Therefore its an
>>>error to depend on the order of evaluation.
>>>
>> 
>> I think the question is how can x.a().b().c() be evaluated in an
>> order OTHER than c(b(a(x)))?
[...]
>
>Sorry to say that to a really intelligent one: you are wrong. Look through the thread and you will find some examples. Most convincing to me is the following observation:
>
>If `typeof( x.a().b()).c()' and `typeof( x.a()).b()' are accepted by
>the compiler without errors, then each of the calls in `x.a().b().c
>()' can be executed without the need to first call `a()', then `b()'.
>
>This is for example the case when `a', `b' and `c' are `static' struct/union/class members or functions, not delegates.
>
>
>-manfred



Ahhhh., I hadn't considered statics. That makes a lot more sense. I stand
corrected. (And thank you!)

On the other hand why shouldn't you be able to evaluate ANY typeof even when delegates are used?



December 10, 2005
Manfred Nowak wrote:
> Ooops. I meant
> "thereby defining the evaluation order by `typeof(x.a().b()).c()', then `typeof(x.a()).b()' and then `x.a()'."

The problem with this, is that using typeof() changes the semantics of the expression.  In the case of 'typeof(x.a()).b()' you are saying (warning: code to english) "call method b, on the return type of a, defined on x."  This is not the same as the Whisper syntax in question, because Whisper works only on object instances, never on types (unless Kris has added a static Whisper notation while I wasn't looking... that would definitely be questionable, IMHO.)  Its apples and oranges.  Yes it does provide a proof case, but it isn't equivelant to the case in practice.

-- Chris Sauls
December 10, 2005
BCS wrote:
[...]
> On the other hand why shouldn't you be able to evaluate ANY typeof even when delegates are used?

Because delegates are bound to their environment, creating the need to evaluate that environment first and therefore imposing an order of evaluation.

Naturally the typeof can be evaluated, but the following call not.

-manfred