January 20, 2007
"Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail@erdani.org> wrote in message news:eotmt2$2fru$3@digitaldaemon.com...
>
> A minor drawback is that Const!(Foo) must be implicitly constructible
> from a Foo, but another in-design language feature (opImplicitCast) will
> take care of that.

So... D is now becoming C++?  Are there going to be globally overloadable operators next?

And how did you get your hands on this juicy tidbit of info anyway?


January 20, 2007
Dave wrote:
> Andrei Alexandrescu (See Website for Email) wrote:
>> D is slated to have full-fledged compile-time reflection, meaning that
>> you can find out all information about any given symbol, during
>> compilation. So it turns out that a possible path to implement const is
>> to make it a template. Given an arbitrary struct Foo, you could define a
>> template called Const such that Const!(Foo) materializes into a struct
>> that holds a Foo* inside, and exposes only the non-mutating parts of Foo.
>>
>> So, given:
>>
>> struct Foo
>> {
>>   int alpha, beta;
>>   void nonmutator() { assert(alpha * beta != 0); }
>>   void mutator() { alpha = beta; }
>> }
>>
>> the template Const!(Foo) generates the code:
>>
>> struct Const!(Foo)
>> {
>>   private Foo* pFoo;
>>   this(inout Foo foo) { pFoo = &foo; }
>>   int alpha() { return pFoo->alpha; }
>>   int beta() { return pFoo->beta; }
>>   void nonmutator() { pFoo->nonmutator(); }
>> }
>>
> 
> But you could still trivially and legally subvert this 'const'-ness:
> 
>     Const!(Foo) c(Foo(100,200));
>     Foo* fp = *cast(Foo**)&c;
>     fp.alpha = 10000;
>     writefln(c.alpha);

Casts can break any design, so it's not phasing anyone off that they can break const. Besides, the fact that Const holds a pointer to the object as its first element is not something client code is allowed to rely on. This is a non-issue.

> So according to what Walter has said in the past about 'semantically meaningful const' then it wouldn't be enough to base optimizations on (even if the compiler could glean useful information from this).

With well-placed casts and simple operations you can subvert the workings of the garbage collector. The D compiler is not required to make every code that compiles, work as someone expects.

>> The only drawback that I can think right now is that the compiler can't
>> exploit this kind of constness with ease to generate better code; it's a
>> "user-space" implementation with semantics that are hard to figure out
>> at the compiler level.
>>
> 
> Both "user-space" and "compiler-space" const has been discussed at length in the past, and Walter's response has always been (paraphrasing) "D won't have const unless it is semantically meaningful (100% enforceable) by the compiler", which will never happen with a language like D.

This statement must be understood in spirit. The spirit is, D shouldn't add a feature that fails spectacularly at providing a true guarantee, as C++ did. The design I suggested does without the actual feature.

D 2.0 will have const. Walter wants it as much as the next guy. The challenge is indeed finding something that has a good complexity/performance ratio.

What I like about the template const idea is that it is entirely automatic - the user doesn't have to annotate functions manually as being const or not. The template will extract with precision the part of a type that can be used without mutating the object. (Such a deduction can also be part of a built-in feature.)

To start talking about const, D must first address the inout issue, which is nothing short of a source of radioactive decay that affects negatively random parts of the language, which otherwise has a sound design. Walter acknowledges that there is a problem, but I don't think he and I have the same understanding of its consequences (which I believe are enormous).

Once inout is taken care of, we need to take a deep breath and address the issue of lazy (which is related), and only after that const can enter the debate table.


Andrei
January 20, 2007
Jarrett Billingsley wrote:
> "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail@erdani.org> wrote in message news:eotmt2$2fru$3@digitaldaemon.com...
>> A minor drawback is that Const!(Foo) must be implicitly constructible
>> from a Foo, but another in-design language feature (opImplicitCast) will
>> take care of that.
> 
> So... D is now becoming C++?  Are there going to be globally overloadable operators next?

It's all too simple to protest against various features that other languages have bastardized and throw the all too simplistic "So what, D is now becoming Xyz?" as an argument in the discussion.

> And how did you get your hands on this juicy tidbit of info anyway?

Call it collocation: Walter and I live in the same city :o).


Andrei
January 20, 2007
Chris Nicholson-Sauls wrote:
> Andrei Alexandrescu (See Website for Email) wrote:
>> D is slated to have full-fledged compile-time reflection, meaning that
>> you can find out all information about any given symbol, during
>> compilation. So it turns out that a possible path to implement const is
>> to make it a template. Given an arbitrary struct Foo, you could define a
>> template called Const such that Const!(Foo) materializes into a struct
>> that holds a Foo* inside, and exposes only the non-mutating parts of Foo.
>>
>> So, given:
>>
>> struct Foo
>> {
>>   int alpha, beta;
>>   void nonmutator() { assert(alpha * beta != 0); }
>>   void mutator() { alpha = beta; }
>> }
>>
>> the template Const!(Foo) generates the code:
>>
>> struct Const!(Foo)
>> {
>>   private Foo* pFoo;
>>   this(inout Foo foo) { pFoo = &foo; }
>>   int alpha() { return pFoo->alpha; }
>>   int beta() { return pFoo->beta; }
>>   void nonmutator() { pFoo->nonmutator(); }
>> }
>>
>> The reflection mechanism would have to provide the information whether
>> or not a given member function changes the object.
>>
>> The only drawback that I can think right now is that the compiler can't
>> exploit this kind of constness with ease to generate better code; it's a
>> "user-space" implementation with semantics that are hard to figure out
>> at the compiler level.
>>
>> A minor drawback is that Const!(Foo) must be implicitly constructible
>> from a Foo, but another in-design language feature (opImplicitCast) will
>> take care of that.
>>
>>
>> Andrei
> 
> Its not a bad idea, although with a little more flavor added to Tuples, template Const might become more general and therefore more usable.  Particularly if we could get an 'identifier' construct in templates such as has been asked for before, and specializations that mimic what 'is' expressions can do, and a true "static foreach".  (I know, its a long list of wants.)

Let's see:

1. The 'identifier' construct is likely to make it in D 2.0. Walter and
I discussed it (the feature moniker that we use is "new alias") and
there is a clear understanding of its necessity. So - check that one.

2. static foreach is a definite go. It's necessary for compile-time
reflection. Walter acknowledged that himself on several occasions. Check
that one too.

3. Specializations that mimic what 'is' expressions can do... not sure I
understand that one. Check it too :o).

> struct Const (T : struct) {
>   private T* ptr ;
> 
>   static Const!(T) opCall (inout T t) { ptr = &t; }
> 
>   static foreach (field; T.tupleof) {
>     typeof(field) identifier(field.nameof) () { return *ptr.field; }
>   }
> }

I had in mind something a tad more comprehensive:

struct Const (T : struct) {
  private T* ptr ;
  // This sucks. Hopefully this() will make it in.
  static Const!(T) opCall (inout T t) { ptr = &t; }
  static foreach (field; T.tupleof) {
    typeof(field) field() { return *ptr.field; }
  }
  static foreach (fun; T.functionsof) {
    static if (fun.mutator) continue;
    fun.result fun(fun.argsOf args) { return ptr->fun(args); }
  }
}

> It loses the mapping to functions, though.  I almost have to admit a 'const' like parameter storage class might well be the way to go.  Either the 'byref' that has been mentioned previously, or a reuse of either 'static' or (*sigh*) 'const'.

The problem is that D does not have a clear stance on what a storage
class is, and how it is to be manipulated statically. It is unclear
whether storage is part of the type or a separate attribute of an
expression. If this is not solved, it will inflict unbounded amounts of
pain onto template code. Wiring more storage classes (such as 'lazy'
which I realized Wednesday was a bad idea as it murks things even more
and is thoroughly ill-designed because it is a storage class that
actually changes the syntax of the object use -- someday Walter should
stand the pressure and stop adding features just because people say they
seem cool) into the language is not going to help any. Heck that was a
long sentence :o).


Andrei

January 20, 2007
janderson wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> janderson wrote:
>>> Walter Bright wrote:
>>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>>> No, and for a good reason:
>>>>>
>>>>> foo(LargeStruct s1, LargeStruct s2)
>>>>> {
>>>>>   ...
>>>>> }
>>>>> ...
>>>>> LargeStruct s;
>>>>> foo(s, s);
>>>>>
>>>>> Nobody would enjoy hidden aliasing of s1 and s2.
>>>
>>> I think you mean more like:
>>>
>>>
>>> foo(LargeStruct s1, LargeStruct inout s2)
>>> {
>>>    ...
>>> }
>>> ...
>>> LargeStruct s;
>>> foo(s, s);
>>
>> I meant what I wrote, although your example is just as relevant. If the compiler takes initiative in passing things by reference, there is risk of unintended aliasing.
>>
>>
>> Andrei
> 
> If all the function does is read the values.  How can aliasing be a problem?

Probably I misunderstood some of what was said earlier in the discussion, sorry. My current understanding is that we're on the same page: there is an aliasing issue that needs proper addressing. :o)

Andrei
January 20, 2007
"Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@erdani.org> wrote in message news:45B2A3AE.20408@erdani.org...
>
> It's all too simple to protest against various features that other languages have bastardized and throw the all too simplistic "So what, D is now becoming Xyz?" as an argument in the discussion.

Well how long has Walter been anti-implicit casting?  It just seems like he's been "no overloading opCast for more than one type" and "implicit casting and construction are bad things" and "opAssign is the devil" for so long, and then just recently he seems to have had a change of mind.. what happened?

> Call it collocation: Walter and I live in the same city :o).

Weeeell foo.  Maybe you guys should tape your coffee shop rendezvous..es (or however you meet) and post them online ;)


January 21, 2007
Andrei Alexandrescu (See Website For Email) wrote:
> Dave wrote:
>> Andrei Alexandrescu (See Website for Email) wrote:
>>> D is slated to have full-fledged compile-time reflection, meaning that
>>> you can find out all information about any given symbol, during
>>> compilation. So it turns out that a possible path to implement const is
>>> to make it a template. Given an arbitrary struct Foo, you could define a
>>> template called Const such that Const!(Foo) materializes into a struct
>>> that holds a Foo* inside, and exposes only the non-mutating parts of Foo.
>>>
>>> So, given:
>>>
>>> struct Foo
>>> {
>>>   int alpha, beta;
>>>   void nonmutator() { assert(alpha * beta != 0); }
>>>   void mutator() { alpha = beta; }
>>> }
>>>
>>> the template Const!(Foo) generates the code:
>>>
>>> struct Const!(Foo)
>>> {
>>>   private Foo* pFoo;
>>>   this(inout Foo foo) { pFoo = &foo; }
>>>   int alpha() { return pFoo->alpha; }
>>>   int beta() { return pFoo->beta; }
>>>   void nonmutator() { pFoo->nonmutator(); }
>>> }
>>>
>>
>> But you could still trivially and legally subvert this 'const'-ness:
>>
>>     Const!(Foo) c(Foo(100,200));
>>     Foo* fp = *cast(Foo**)&c;
>>     fp.alpha = 10000;
>>     writefln(c.alpha);
> 
> Casts can break any design, so it's not phasing anyone off that they can break const. Besides, the fact that Const holds a pointer to the object as its first element is not something client code is allowed to rely on. This is a non-issue.

I wish Walter would have agreed with many of us that it (casts breaking any design) is a "non-issue" for going ahead with a 'semantically meaningful const' a year ago. I hope you can convince him!

>> So according to what Walter has said in the past about 'semantically meaningful const' then it wouldn't be enough to base optimizations on (even if the compiler could glean useful information from this).
> 
> With well-placed casts and simple operations you can subvert the workings of the garbage collector. The D compiler is not required to make every code that compiles, work as someone expects.
> 

I agree. Just like in http://digitalmars.com/d/garbage.html where it's acknowledged that certain pointer operations should not be used on GC'd memory, I think a section like that on 'const' would be good enough as long as the compiler enforced what could reasonably be expected. "If you (intentionally) break it, you buy it."

>>> The only drawback that I can think right now is that the compiler can't
>>> exploit this kind of constness with ease to generate better code; it's a
>>> "user-space" implementation with semantics that are hard to figure out
>>> at the compiler level.
>>>
>>
>> Both "user-space" and "compiler-space" const has been discussed at length in the past, and Walter's response has always been (paraphrasing) "D won't have const unless it is semantically meaningful (100% enforceable) by the compiler", which will never happen with a language like D.
> 
> This statement must be understood in spirit. The spirit is, D shouldn't add a feature that fails spectacularly at providing a true guarantee, as C++ did. The design I suggested does without the actual feature.
> 

I completely agree with this sentiment. In earlier discussions, there were designs that went even further, for example not being able to take the address of or cast from a const parameter, etc.

But IMO that sentiment has been subjectively and unevenly applied to different but often requested language requests over the years. Unfortunately the earlier const suggestions were a case where the same sentiment was not applied. IIRC, the majority of the "group" - many of which had a large amount of experience writing libraries with D - strongly favored a 'const' or 'readonly' even though the compiler could not always guarantee they couldn't be modified.

> D 2.0 will have const. Walter wants it as much as the next guy. The challenge is indeed finding something that has a good complexity/performance ratio.
> 
> What I like about the template const idea is that it is entirely automatic - the user doesn't have to annotate functions manually as being const or not. The template will extract with precision the part of a type that can be used without mutating the object. (Such a deduction can also be part of a built-in feature.)
> 
> To start talking about const, D must first address the inout issue, which is nothing short of a source of radioactive decay that affects negatively random parts of the language, which otherwise has a sound design. Walter acknowledges that there is a problem, but I don't think he and I have the same understanding of its consequences (which I believe are enormous).
> 
> Once inout is taken care of, we need to take a deep breath and address the issue of lazy (which is related), and only after that const can enter the debate table.
> 
> 
> Andrei
January 21, 2007
Jarrett Billingsley wrote:
> "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@erdani.org> wrote in message news:45B2A3AE.20408@erdani.org...
>> It's all too simple to protest against various features that other languages have bastardized and throw the all too simplistic "So what, D is now becoming Xyz?" as an argument in the discussion.
> 
> Well how long has Walter been anti-implicit casting?  It just seems like he's been "no overloading opCast for more than one type" and "implicit casting and construction are bad things" and "opAssign is the devil" for so long, and then just recently he seems to have had a change of mind.. what happened?

It happens to us all to associate some bad consequences with some wholesale cause without investigating further. Many of the perceptions above are based on various degrees to which C++ embarked on implementing them and partially failed because an understandable lack of clear foresight. To exemplify:

* "no overloading opCast for more than one type" and "implicit casting and construction are bad things" came from C++ having made implicit conversion the default one (and the only possible one when converting to built-ins), thus making implicit conversions springing all over the place in expressions. To assuage that, C++ also added the bizarre rule that maximum one user-defined conversion can act in one expression. This, coupled with the byzantine overloading rules of C++, contributed to a general perception that implicit conversion is a bad idea.

However, numerous use cases show that implicit conversions are expressive as long as they are the result of a conscious decision. Besides, the core does support implicit conversions, and I think history of programming languages has shown copiously that the dictum "Quod licet Iovis, non licet bovis" doesn't apply to language design.

* "opAssign is the devil" stems from a simple confusion - that assignment and copy construction are strongly related. They aren't, and as soon as this was clarified, opAssign made its way into the language. Copy construction suffers of an infinite regression problem that C++ avoided, arguably in quite a disingenious way that has caused much grief, to be assuaged by the upcoming rvalue references (which come with the unneeded complexity specific to afterthought designs). I fear that solving inout for D will have a similar stench.


Andrei
January 21, 2007
Walter Bright wrote:
> Bill Baxter wrote:
>> Walter Bright wrote:
>>> Bill Baxter wrote:
>>>> The question is how can we write functions in D that take big structs as parameters in such a way that it is:
>>>> A) efficient and
>>>> B) guaranteed not to change the caller's value
>>> There aren't const references in D. But the 3 ways to pass it by pointer are:
>>>
>>> 1) inout
>>> 2) take the address and use a pointer
>>> 3) make it a class instead
>>
>> And none of those accomplishes a or b.
> 
> All of them accomplish A.

Ha ha.  You almost had me for a minute there.  You cut and pasted bits and pieces of two different messages of mine.   The a/b in question was actually:

> Very true.  What I'm after is really a concise way to both
> a) document to readers of the code that it won't change and
> b) get the compiler to zap me if I accidentally try to modify it.

That's the a/b I said none of your pointer-based solutions achieve.

--bb
January 21, 2007
Dave wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> Casts can break any design, so it's not phasing anyone off that they can break const. Besides, the fact that Const holds a pointer to the object as its first element is not something client code is allowed to rely on. This is a non-issue.
> 
> I wish Walter would have agreed with many of us that it (casts breaking any design) is a "non-issue" for going ahead with a 'semantically meaningful const' a year ago. I hope you can convince him!

My statement about design-breaking casts needs qualifying. Walter (and I entirely agree with him) dislikes the fact that C++ actually _legalizes_ const removal via a cast. Consider the following C++ code:

void Foo(const int & x)
{
  const_cast<int&>(x) = 0;
)

int main()
{
  int x = 1;
  Foo(x);
}

This is 100% legal C++ code with defined behavior; casting away const is ok if the initial data was not const. This feature kicks const straight in the proverbials.

A "design-breaking" cast is different: the language does not guarantee the behavior of code that uses it, and therefore can act under the assumption that it will not happen. This is key to introducing const in D in a manner that is useful to everybody.

>>> Both "user-space" and "compiler-space" const has been discussed at length in the past, and Walter's response has always been (paraphrasing) "D won't have const unless it is semantically meaningful (100% enforceable) by the compiler", which will never happen with a language like D.
>>
>> This statement must be understood in spirit. The spirit is, D shouldn't add a feature that fails spectacularly at providing a true guarantee, as C++ did. The design I suggested does without the actual feature.
>>
> 
> I completely agree with this sentiment. In earlier discussions, there were designs that went even further, for example not being able to take the address of or cast from a const parameter, etc.
> 
> But IMO that sentiment has been subjectively and unevenly applied to different but often requested language requests over the years. Unfortunately the earlier const suggestions were a case where the same sentiment was not applied. IIRC, the majority of the "group" - many of which had a large amount of experience writing libraries with D - strongly favored a 'const' or 'readonly' even though the compiler could not always guarantee they couldn't be modified.

It is then possible they were wrong, or that they didn't come with a reasonable design. Speaking of which (famous last words to come), I don't want to sound harsh, but if there is one thing that's worse than language design by committee, that's got to be language design by community. Having too many cooks in the kitchen dilutes vision, which is probably the single most important thing in a language design. The few places where Walter made populist decisions are eminently visible in the language design (ah, "length", gotta love that one), and many feature requests that have been aired on this newsgroup are in dire need of design.


Andrei