January 22, 2007
Tom S wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> I'm positive that lazy is the epitome of bad language design and should go away. The article:
>>
>> http://www.digitalmars.com/d/lazy-evaluation.html
>>
>> proudly acknowledges the post:
>>
>> http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=41633 
>>
>>
>> which doesn't make any sense of anything,
> 
> Well, thanks for the attack, but it was an attempt to suggest a fix, as the introduction of lazy evaluation broke existing code in a non-trivial way.

You are not being attacked. Not even the post was attacked, but rather the haste with which a feature was implemented before a good design was being put forth.

[snip]
>> I believe that populism in language design is not good.
> 
> Listening to ideas and opinions from language users is a bad idea ? Taking my post as an example, it was merely a compromise between the screams of other D users and Walter's opinion. Had Walter ignored it, the net result would be more confusion and irritation, along with a strange rule of implicitly converting lazy expressions to delegates.

I think the current state of affairs is stranger than that implicit conversion rule. It will now take additional effort, and a certain amount of code breakage, to fix the design. In an ideal world, things would have gone through a more critical review before just making it into the language.

> This said, I don't think that the way lazy expressions are currently implemented is even close to being optimal. out/inout could be changed too, as out/inout params can be detected, but one cannot realistically instantiate a function template with mixed in/out/inout params.

There is an ongoing idea of binding an expression to an alias, which probably will do a good job (better opportunities for inlining). As far as in/out/inout (and actually lazy/scope/const) go, Walter will take up to fixing the issue and has a design for distinguishing among them properly. All of these keywords will be raised to the status of type constructors, and the constructed types will have well-defined semantics. Combinations (e.g. lazy inout) will be allowed when the semantics allow it.

Anyhow, speaking of a more critical review, the current intended rules for lazy are:

* lazy is a type constructor (takes a type, returns a type)

* sizeof(lazy T) = sizeof(T delegate())

* initialization = expression of type T or expression of type lazy T

* assignment: expression of type lazy T

* access: every access invokes the delegate. No trailing parens.

* deduced automatically: never

* can occur in: (a) function argument list; (b) function return type; (c) all lvalue contexts (namespace-level, local, struct member, class member, array, hash)

One irregularity is that assignment from an expression of type T is not accepted, amid fears that this would cause confusion. Probably we could drop this irregularity.

Looking forward to feedback!


Andrei
January 22, 2007
Frits van Bommel wrote:
> You didn't respond to my paragraph on classes though:
> 
> Frits van Bommel wrote:
>  > I also don't see a way this can work for classes (in general). There's
>  > no way to limit a non-final method to non-mutating operations that I'm
>  > aware of, so it could easily be circumvented by subclassing. Even though
>  > that would only allow direct access to non-private fields, mutating base
>  > class methods could still be called for the rest (if available). So then
>  > the only way to get a const version of a class would be if the class
>  > doesn't allow mutations anyway (like java's String). Which leaves us
>  > right back where we started :(.
>  >
>  > And I don't think a 'const' implementation should work for structs and
>  > not for classes...

I did when I wrote:

>> That is correct. Interface functions, nonfinal methods, and
>> declared-only functions must be annotated manually.

I meant - yes, you are right.

Today, Walter and myself have come with a semantics for const (as in, "read-only view" that) seems to be a good starting point. The details are yet to be ironed out, but here's the basic plot:

* const is a unary type constructor (takes a type, returns a type)

* const is always transitive, meaning that once a value is const, the entire part of the world accessible through that value is const as well. This distances D from C++'s shallow view of const (only one level).

* const is not removable legally via a cast. But the compiler must assume in the general case that a const view may alias with a modifiable view. This is a weak point of the design. The hope is that in most cases the compiler will easily prove non-aliasing and can do a good job at optimizing based on const. Example: all pure functions don't care about aliasing to start with.

* Shallow const is achievable via final. Final only means a value is not supposed to be changed, but anything reachable through it can be mutated.

* The syntax for manually-annotated const is:

struct Widget
{
  void Foo(const)(int i) { ... }
}

This is in the spirit of current D, because Foo can be seen as a specialization for a subset of Widget types.

* If you do want to make Foo a template, it's:

struct Widget
{
  void Foo(const, T)(T i) { ... }
}

* D avoids the issue of duplicated function bodies in the following way:

struct Widget
{
  storageof(this) int* Foo(storageof(this))(int i) { ... }
}

This code transports whatever storage 'this' has to the result type. More involved type computations are allowed because inside Foo, typeof(this) includes the storage class. Effectively Foo above is a template.

* Constructors and destructors can figure the storage class of the object being constructed. This is useful for selecting different allocation strategies for immutable vs. mutable objects:

class Widget
{
  this(int i) { ... }
  this(const)(int i) { ... }
  ~this() { ... }
  ~this(const)() { ... }
}

Const cdtors can obviously mutate the object being cdted. In a cdtor, 'const' is not enforced -- it's just an information for the programmer.

* Walter sustains that given the above, there will never be a need to overload member functions based on const. I disagree, but I couldn't come up with a salient example. Besides, I don't care that much because the semantics above do allow telling const from nonconst, just in a roundabout way.


Andrei
January 22, 2007
Frits van Bommel wrote:
> "in", "out", "inout" and "lazy" aren't storage classes.
> http://www.digitalmars.com/d/declaration.html (in the big syntax block at the top):
> -----
> StorageClass:
>         abstract
>         auto
>         const
>         deprecated
>         extern
>         final
>         override
>         scope
>         static
>         synchronized
> -----
> and later:
> -----
> InOut:
>         in
>         out
>         inout
>         lazy
> -----

This is more proof that it's highly unclear what they are. The article on lazy clearly says lazy is a storage class. Walter routinely refers to inout as to a storage class.

The good news is that they will all be integrated: their semantics clearly defined, as will be the semantics of all legal combinations thereof (e.g. lazy inout scope). (Many combinations will be disallowed on practical grounds as being useless, e.g. "in out".)


Andrei
January 22, 2007
Andrei Alexandrescu (See Website For Email) wrote:
> Today, Walter and myself have come with a semantics for const (as in, "read-only view" that) seems to be a good starting point.

Five hours of coffee and scribbling <g>. Neither of us is thoroughly happy with the result, but at least we do have that good starting point.

> * const is not removable legally via a cast. But the compiler must assume in the general case that a const view may alias with a modifiable view. This is a weak point of the design. The hope is that in most cases the compiler will easily prove non-aliasing and can do a good job at optimizing based on const. Example: all pure functions don't care about aliasing to start with.

An important clarification on that: the compiler *can* assume that the 'shallow const' portion (defined below) will not change. There's no way the compiler can guarantee it won't change (for various technical reasons), so it will be up to the user to ensure that no other thread or alias changes it for the duration of the scope. I'll also argue that the C++ requirement that the compiler assume it will change offers no usable advantage, it just sabotages optimizations.

> * Shallow const is achievable via final. Final only means a value is not supposed to be changed, but anything reachable through it can be mutated.
> 
> * The syntax for manually-annotated const is:
> 
> struct Widget
> {
>   void Foo(const)(int i) { ... }
> }

In C++ this is analogous to:

struct Widget
{
  void Foo(int i) const { ... }
}

I never liked the C++ syntax for attributing the 'this' pointer.

> 
> This is in the spirit of current D, because Foo can be seen as a specialization for a subset of Widget types.
> 
> * If you do want to make Foo a template, it's:
> 
> struct Widget
> {
>   void Foo(const, T)(T i) { ... }
> }
> 
> * D avoids the issue of duplicated function bodies in the following way:
> 
> struct Widget
> {
>   storageof(this) int* Foo(storageof(this))(int i) { ... }
> }
> 
> This code transports whatever storage 'this' has to the result type. More involved type computations are allowed because inside Foo, typeof(this) includes the storage class. Effectively Foo above is a template.
> 
> * Constructors and destructors can figure the storage class of the object being constructed. This is useful for selecting different allocation strategies for immutable vs. mutable objects:
> 
> class Widget
> {
>   this(int i) { ... }
>   this(const)(int i) { ... }
>   ~this() { ... }
>   ~this(const)() { ... }
> }
> 
> Const cdtors can obviously mutate the object being cdted. In a cdtor, 'const' is not enforced -- it's just an information for the programmer.
> 
> * Walter sustains that given the above, there will never be a need to overload member functions based on const. I disagree, but I couldn't come up with a salient example. Besides, I don't care that much because the semantics above do allow telling const from nonconst, just in a roundabout way.
January 22, 2007
Andrei Alexandrescu (See Website For Email) wrote:

> Anyhow, speaking of a more critical review, the current intended rules for lazy are:

Does the lack of syntax mean that the syntax has yet to be decided upon?

> * lazy is a type constructor (takes a type, returns a type)

Cool that seems like an interesting idea for this and const.

> * sizeof(lazy T) = sizeof(T delegate())
> 
> * initialization = expression of type T or expression of type lazy T
> 
> * assignment: expression of type lazy T
> 
> * access: every access invokes the delegate. No trailing parens.
> 
> * deduced automatically: never

What does this mean, exactly?  Is it speaking of a template parameter? Or for things like 'auto foo = <expr>'?

> * can occur in: (a) function argument list; (b) function return type; (c) all lvalue contexts (namespace-level, local, struct member, class member, array, hash)

> One irregularity is that assignment from an expression of type T is not accepted, amid fears that this would cause confusion. Probably we could drop this irregularity.

So this is ok:
  lazy int a = 3+4;
And this is ok:
  lazy int b = 3+4;
  lazy int a;
  a = b+2;

But this is not?
  lazy int a;
  a = 3+4;


Don't really have any criticisms of this at the moment, just trying to figure out exactly what it's going to mean in practice.
So far what I'm getting is that it's similar to the current 'lazy' just generalized to apply to more situations, and that now lazy is part of the type rather than being a storage class.

Will there be any additional interop with delegates?  What about assigning a delegate to a lazy variable?

--bb
January 22, 2007
Andrei Alexandrescu (See Website For Email) wrote:
> Frits van Bommel wrote:
>> You didn't respond to my paragraph on classes though:
>>
>> Frits van Bommel wrote:
>>  > I also don't see a way this can work for classes (in general). There's
>>  > no way to limit a non-final method to non-mutating operations that I'm
>>  > aware of, so it could easily be circumvented by subclassing. Even though
>>  > that would only allow direct access to non-private fields, mutating base
>>  > class methods could still be called for the rest (if available). So then
>>  > the only way to get a const version of a class would be if the class
>>  > doesn't allow mutations anyway (like java's String). Which leaves us
>>  > right back where we started :(.
>>  >
>>  > And I don't think a 'const' implementation should work for structs and
>>  > not for classes...
> 
> I did when I wrote:
> 
>  >> That is correct. Interface functions, nonfinal methods, and
>  >> declared-only functions must be annotated manually.
> 
> I meant - yes, you are right.
> 
> Today, Walter and myself have come with a semantics for const (as in, "read-only view" that) seems to be a good starting point. The details are yet to be ironed out, but here's the basic plot:
> 

So the keyword for the "read-only view" will be 'const' too? It will share space with the 'const' as in "compile time constant" ?

> * const is a unary type constructor (takes a type, returns a type)
> 
> * const is always transitive, meaning that once a value is const, the entire part of the world accessible through that value is const as well. This distances D from C++'s shallow view of const (only one level).
> 
> * const is not removable legally via a cast. But the compiler must assume in the general case that a const view may alias with a modifiable view. This is a weak point of the design. The hope is that in most cases the compiler will easily prove non-aliasing and can do a good job at optimizing based on const. Example: all pure functions don't care about aliasing to start with.
> 
> * Shallow const is achievable via final. Final only means a value is not supposed to be changed, but anything reachable through it can be mutated.
> 

So are we going to use the 'final' keyword for that? That's good since it clears up the overloaded meaning of 'const' in current D. (used both for compile time constants and final)

> * The syntax for manually-annotated const is:
> 
> struct Widget
> {
>   void Foo(const)(int i) { ... }
> }
> 
> This is in the spirit of current D, because Foo can be seen as a specialization for a subset of Widget types.
> 
> * If you do want to make Foo a template, it's:
> 
> struct Widget
> {
>   void Foo(const, T)(T i) { ... }
> }
> 
> * D avoids the issue of duplicated function bodies in the following way:
> 
> struct Widget
> {
>   storageof(this) int* Foo(storageof(this))(int i) { ... }
> }
> 

Hum, "storageof"? Shouldn't the name be different, since these aren't really storage classes?

> This code transports whatever storage 'this' has to the result type. More involved type computations are allowed because inside Foo, typeof(this) includes the storage class. Effectively Foo above is a template.
> 
> * Constructors and destructors can figure the storage class of the object being constructed. This is useful for selecting different allocation strategies for immutable vs. mutable objects:
> 
> class Widget
> {
>   this(int i) { ... }
>   this(const)(int i) { ... }
>   ~this() { ... }
>   ~this(const)() { ... }
> }
> 
> Const cdtors can obviously mutate the object being cdted. In a cdtor, 'const' is not enforced -- it's just an information for the programmer.
> 

Whoa, huh? What's the meaning of a const constructor or of a const destructor? How would they even be invoked?

> * Walter sustains that given the above, there will never be a need to overload member functions based on const. I disagree, but I couldn't come up with a salient example. Besides, I don't care that much because the semantics above do allow telling const from nonconst, just in a roundabout way.
> 
> 
> Andrei


-- 
Bruno Medeiros - MSc in CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
January 22, 2007
Bruno Medeiros wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> Today, Walter and myself have come with a semantics for const (as in, "read-only view" that) seems to be a good starting point. The details are yet to be ironed out, but here's the basic plot:
>>
> 
> So the keyword for the "read-only view" will be 'const' too? It will share space with the 'const' as in "compile time constant" ?

Yes. This is arguably a weak point of the design.

>> * Shallow const is achievable via final. Final only means a value is not supposed to be changed, but anything reachable through it can be mutated.
>>
> 
> So are we going to use the 'final' keyword for that? That's good since it clears up the overloaded meaning of 'const' in current D. (used both for compile time constants and final)

That is correct. An easy way to remember both is: final refers to a name (the name cannot be rebound), const refers to memory accessible through a name (the memory cannot be changed).

>> * D avoids the issue of duplicated function bodies in the following way:
>>
>> struct Widget
>> {
>>   storageof(this) int* Foo(storageof(this))(int i) { ... }
>> }
>>
> 
> Hum, "storageof"? Shouldn't the name be different, since these aren't really storage classes?

I think const can be considered a storage class even when it is "borrowed" from data that originally was not const.

>> * Constructors and destructors can figure the storage class of the object being constructed. This is useful for selecting different allocation strategies for immutable vs. mutable objects:
>>
>> class Widget
>> {
>>   this(int i) { ... }
>>   this(const)(int i) { ... }
>>   ~this() { ... }
>>   ~this(const)() { ... }
>> }
>>
>> Const cdtors can obviously mutate the object being cdted. In a cdtor, 'const' is not enforced -- it's just an information for the programmer.
>>
> 
> Whoa, huh? What's the meaning of a const constructor or of a const destructor? How would they even be invoked?

class Widget { ... }
auto a = new Widget; // invokes this()
auto b = new const Widget; // invokes this(const)()


Andrei
January 22, 2007
Bill Baxter wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
> 
>> Anyhow, speaking of a more critical review, the current intended rules for lazy are:
> 
> Does the lack of syntax mean that the syntax has yet to be decided upon?

The syntax was described and is the same as using regular lvalues.

>> * deduced automatically: never
> 
> What does this mean, exactly?  Is it speaking of a template parameter? Or for things like 'auto foo = <expr>'?

Both. And also for future deduction of storage classes by template functions. Basically the compiler assumes eager evaluation by default.

>> * can occur in: (a) function argument list; (b) function return type; (c) all lvalue contexts (namespace-level, local, struct member, class member, array, hash)
> 
>> One irregularity is that assignment from an expression of type T is not accepted, amid fears that this would cause confusion. Probably we could drop this irregularity.
> 
> So this is ok:
>   lazy int a = 3+4;
> And this is ok:
>   lazy int b = 3+4;
>   lazy int a;
>   a = b+2;
> 
> But this is not?
>   lazy int a;
>   a = 3+4;

That is the plan. Although probably it's a mistake.

> Don't really have any criticisms of this at the moment, just trying to figure out exactly what it's going to mean in practice.
> So far what I'm getting is that it's similar to the current 'lazy' just generalized to apply to more situations, and that now lazy is part of the type rather than being a storage class.
> 
> Will there be any additional interop with delegates?  What about assigning a delegate to a lazy variable?

That's a good idea. Frankly I am not thrilled by lazy because there is too much overlap with delegates to be worth any effort (and your idea takes that overlap to the extreme). After thinking more about it, I think lazy should go and automatic conversion from expressions to aliases should replace it as a more general and efficient alternative.

Figuring out the vagaries of each storage class and all combinations thereof is just a lot of effort from the creator of the language, the compiler implementor, and the programmer who later learns the language. It's even more demotivating to see that a storage class is just white sugar for a feature that could, and probably will, be implemented in a more principled way. I honestly think lazy should go. It's a waste of time.


Andrei
January 23, 2007
Bruno Medeiros wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> Frits van Bommel wrote:
>>> You didn't respond to my paragraph on classes though:
>>>
>>> Frits van Bommel wrote:
>>>  > I also don't see a way this can work for classes (in general). There's
>>>  > no way to limit a non-final method to non-mutating operations that I'm
>>>  > aware of, so it could easily be circumvented by subclassing. Even though
>>>  > that would only allow direct access to non-private fields, mutating base
>>>  > class methods could still be called for the rest (if available). So then
>>>  > the only way to get a const version of a class would be if the class
>>>  > doesn't allow mutations anyway (like java's String). Which leaves us
>>>  > right back where we started :(.
>>>  >
>>>  > And I don't think a 'const' implementation should work for structs and
>>>  > not for classes...
>>
>> I did when I wrote:
>>
>>  >> That is correct. Interface functions, nonfinal methods, and
>>  >> declared-only functions must be annotated manually.
>>
>> I meant - yes, you are right.
>>
>> Today, Walter and myself have come with a semantics for const (as in, "read-only view" that) seems to be a good starting point. The details are yet to be ironed out, but here's the basic plot:
>>
> 
> So the keyword for the "read-only view" will be 'const' too? It will share space with the 'const' as in "compile time constant" ?

'const' is not always a compile time constant. A const in function or in a class can have a different value for each call/instance. Don't ask me why.


L.
January 23, 2007
Lionello Lunesu wrote:
> Bruno Medeiros wrote:
> 
>> Andrei Alexandrescu (See Website For Email) wrote:
>>
>>> Frits van Bommel wrote:
>>>
>>>> You didn't respond to my paragraph on classes though:
>>>>
>>>> Frits van Bommel wrote:
>>>>  > I also don't see a way this can work for classes (in general). There's
>>>>  > no way to limit a non-final method to non-mutating operations that I'm
>>>>  > aware of, so it could easily be circumvented by subclassing. Even though
>>>>  > that would only allow direct access to non-private fields, mutating base
>>>>  > class methods could still be called for the rest (if available). So then
>>>>  > the only way to get a const version of a class would be if the class
>>>>  > doesn't allow mutations anyway (like java's String). Which leaves us
>>>>  > right back where we started :(.
>>>>  >
>>>>  > And I don't think a 'const' implementation should work for structs and
>>>>  > not for classes...
>>>
>>>
>>> I did when I wrote:
>>>
>>>  >> That is correct. Interface functions, nonfinal methods, and
>>>  >> declared-only functions must be annotated manually.
>>>
>>> I meant - yes, you are right.
>>>
>>> Today, Walter and myself have come with a semantics for const (as in, "read-only view" that) seems to be a good starting point. The details are yet to be ironed out, but here's the basic plot:
>>>
>>
>> So the keyword for the "read-only view" will be 'const' too? It will share space with the 'const' as in "compile time constant" ?
> 
> 
> 'const' is not always a compile time constant. A const in function or in a class can have a different value for each call/instance. Don't ask me why.
> 
> 
> L.


const int also takes space in an executable. It apparenltly becomes a true 4-byte instance, not a compile-time constant. Though, why anyone would *expect* to take the address of a constant is completely beyond my comprehension.

No big deal, huh? Then consider the avalanche of constants in a full Win32 API