www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - equivariant functions

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Many functions return one of their parameters regardless of the way it 
was qualified.

char[] stripl(char[] s);
const(char)[] stripl(const(char)[] s);
invariant(char)[] stripl(invariant(char)[] s);

Stripl is not a particularly good example because it needs to work on 
wchar and dchar too, but let's ignore that aspect for now.

There's been several proposals in this group on tackling that problem.

In unrelated proposals and discussions, people mentioned the need for 
functions that return the exact type of this:

class A { A clone(); }
class B : A { B clone(); }

How can we declare A.clone such that all of its derived classes have it 
return their own type?

It took me a while to realize they are really very related. This is easy 
to figure out if you think that invariant(char)[] and char[] are 
subtypes of const(char)[]!

I discussed with Walter a variant that implements equivariant functions 
without actually adding an explicit feature to the language. Consider:

typeof(s) stripl(const(char)[] s);

This signature states that it returns the same type as an argument. I 
propose that that pattern means stripl can accept _any_ subtype of 
const(char)[] and return that exact type. Inside the function, however, 
the type of s is the type declared, thus restricting its use.

I need to convince myself that function bodies of this type can be 
reliably typechecked, but first I wanted to run it by everyone to get a 
feel of it.

Equivariant functions are not (necessarily) templates and can be used as 
virtual functions. Only one body is generated for one equivariant 
function, unless other template mechanisms are in vigor.

Here are some examples:

a) Simple equivariance

typeof(s) stripl(const(char)[] s);

b) Parameterized equivariance

typeof(s) stripl(S)(S s) if (isSomeString!S);

c) Equivariance of field:

typeof(s.ptr) getpointer(const(char)[] s);

d) Equivariance inside a class/struct declaration:

class S
{
     typeof(this) clone();
     typeof(this.field) getfield();
     int field;
}

What do you think? I'm almost afraid to post this.


Andrei
Oct 12 2008
next sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Andrei Alexandrescu wrote:
 class A { A clone(); }
 class B : A { B clone(); }

...
 Here are some examples:
 
 a) Simple equivariance
 
 typeof(s) stripl(const(char)[] s);
 
 b) Parameterized equivariance
 
 typeof(s) stripl(S)(S s) if (isSomeString!S);
 
 c) Equivariance of field:
 
 typeof(s.ptr) getpointer(const(char)[] s);
 
 d) Equivariance inside a class/struct declaration:
 
 class S
 {
     typeof(this) clone();
     typeof(this.field) getfield();
     int field;
 }
 
 What do you think? I'm almost afraid to post this.
 
 
 Andrei

All your examples use typeof(something). Is the intent to make this example work?
 class A { A clone(); }
 class B : A { B clone(); }

I take it that this would also extend to delegates? class A {} class B : A {} void foo (A delegate (A) dg) { } B bar (B b) { } foo (&bar); If you require "typeof(something)", I'd never use this feature. Otherwise, I'd use it sometimes.
Oct 12 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Christopher Wright wrote:
 All your examples use typeof(something). Is the intent to make this 
 example work?
  > class A { A clone(); }
  > class B : A { B clone(); }

That example does work today. It's covariance of return values and is only loosely related to this topic.
 I take it that this would also extend to delegates?
 
 class A {}
 class B : A {}
 
 void foo (A delegate (A) dg) { }
 B bar (B b) { }
 foo (&bar);
 
 If you require "typeof(something)", I'd never use this feature. 
 Otherwise, I'd use it sometimes.

The example is wrong because B delegate(B) is not a supertype nor a subtype of A delegate(A). The proposed solution does involve writing typeof as an indication of the need to return the same type as the argument's. Andrei
Oct 12 2008
parent Christopher Wright <dhasenan gmail.com> writes:
Andrei Alexandrescu wrote:
 Christopher Wright wrote:
 All your examples use typeof(something). Is the intent to make this 
 example work?
  > class A { A clone(); }
  > class B : A { B clone(); }

That example does work today. It's covariance of return values and is only loosely related to this topic.
 I take it that this would also extend to delegates?

 class A {}
 class B : A {}

 void foo (A delegate (A) dg) { }
 B bar (B b) { }
 foo (&bar);

 If you require "typeof(something)", I'd never use this feature. 
 Otherwise, I'd use it sometimes.

The example is wrong because B delegate(B) is not a supertype nor a subtype of A delegate(A). The proposed solution does involve writing typeof as an indication of the need to return the same type as the argument's. Andrei

My mistake -- I could have written it as: void foo (A delegate (B) dg) { } B bar (A a) { } foo (&bar); No input that foo can give to the delegate cannot be given to bar, and no output from bar can be unacceptable.
Oct 12 2008
prev sibling next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
Andrei Alexandrescu wrote:
 Many functions return one of their parameters regardless of the way it 
 was qualified.
 
 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);
 
 Stripl is not a particularly good example because it needs to work on 
 wchar and dchar too, but let's ignore that aspect for now.
 
 There's been several proposals in this group on tackling that problem.
 
 In unrelated proposals and discussions, people mentioned the need for 
 functions that return the exact type of this:
 
 class A { A clone(); }
 class B : A { B clone(); }
 
 How can we declare A.clone such that all of its derived classes have it 
 return their own type?
 
 It took me a while to realize they are really very related. This is easy 
 to figure out if you think that invariant(char)[] and char[] are 
 subtypes of const(char)[]!
 
 I discussed with Walter a variant that implements equivariant functions 
 without actually adding an explicit feature to the language. Consider:
 
 typeof(s) stripl(const(char)[] s);
 
 This signature states that it returns the same type as an argument. I 
 propose that that pattern means stripl can accept _any_ subtype of 
 const(char)[] and return that exact type. Inside the function, however, 
 the type of s is the type declared, thus restricting its use.
 
 I need to convince myself that function bodies of this type can be 
 reliably typechecked, but first I wanted to run it by everyone to get a 
 feel of it.
 
 Equivariant functions are not (necessarily) templates and can be used as 
 virtual functions. Only one body is generated for one equivariant 
 function, unless other template mechanisms are in vigor.
 
 Here are some examples:
 
 a) Simple equivariance
 
 typeof(s) stripl(const(char)[] s);
 
 b) Parameterized equivariance
 
 typeof(s) stripl(S)(S s) if (isSomeString!S);
 
 c) Equivariance of field:
 
 typeof(s.ptr) getpointer(const(char)[] s);
 
 d) Equivariance inside a class/struct declaration:
 
 class S
 {
     typeof(this) clone();
     typeof(this.field) getfield();
     int field;
 }
 
 What do you think? I'm almost afraid to post this.
 
 
 Andrei

Sounds good. -- Please resolve ambiguity when s is a global variable. string s; // currently compiles. typeof(s) f(int s) { return typeof(return).init; }
Oct 12 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
KennyTM~ wrote:
 Andrei Alexandrescu wrote:
 Many functions return one of their parameters regardless of the way it 
 was qualified.

 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);

 Stripl is not a particularly good example because it needs to work on 
 wchar and dchar too, but let's ignore that aspect for now.

 There's been several proposals in this group on tackling that problem.

 In unrelated proposals and discussions, people mentioned the need for 
 functions that return the exact type of this:

 class A { A clone(); }
 class B : A { B clone(); }

 How can we declare A.clone such that all of its derived classes have 
 it return their own type?

 It took me a while to realize they are really very related. This is 
 easy to figure out if you think that invariant(char)[] and char[] are 
 subtypes of const(char)[]!

 I discussed with Walter a variant that implements equivariant 
 functions without actually adding an explicit feature to the language. 
 Consider:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an argument. I 
 propose that that pattern means stripl can accept _any_ subtype of 
 const(char)[] and return that exact type. Inside the function, 
 however, the type of s is the type declared, thus restricting its use.

 I need to convince myself that function bodies of this type can be 
 reliably typechecked, but first I wanted to run it by everyone to get 
 a feel of it.

 Equivariant functions are not (necessarily) templates and can be used 
 as virtual functions. Only one body is generated for one equivariant 
 function, unless other template mechanisms are in vigor.

 Here are some examples:

 a) Simple equivariance

 typeof(s) stripl(const(char)[] s);

 b) Parameterized equivariance

 typeof(s) stripl(S)(S s) if (isSomeString!S);

 c) Equivariance of field:

 typeof(s.ptr) getpointer(const(char)[] s);

 d) Equivariance inside a class/struct declaration:

 class S
 {
     typeof(this) clone();
     typeof(this.field) getfield();
     int field;
 }

 What do you think? I'm almost afraid to post this.


 Andrei

Sounds good. -- Please resolve ambiguity when s is a global variable. string s; // currently compiles. typeof(s) f(int s) { return typeof(return).init; }

Yes, incidentally there is a bug in the current compiler (I put it somewhere in bugzilla a while ago) that prevents it from using parameter names in a typeof expression. Andrei
Oct 12 2008
parent KennyTM~ <kennytm gmail.com> writes:
Andrei Alexandrescu wrote:
 KennyTM~ wrote:
 Andrei Alexandrescu wrote:
 Many functions return one of their parameters regardless of the way 
 it was qualified.

 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);

 Stripl is not a particularly good example because it needs to work on 
 wchar and dchar too, but let's ignore that aspect for now.

 There's been several proposals in this group on tackling that problem.

 In unrelated proposals and discussions, people mentioned the need for 
 functions that return the exact type of this:

 class A { A clone(); }
 class B : A { B clone(); }

 How can we declare A.clone such that all of its derived classes have 
 it return their own type?

 It took me a while to realize they are really very related. This is 
 easy to figure out if you think that invariant(char)[] and char[] are 
 subtypes of const(char)[]!

 I discussed with Walter a variant that implements equivariant 
 functions without actually adding an explicit feature to the 
 language. Consider:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an argument. I 
 propose that that pattern means stripl can accept _any_ subtype of 
 const(char)[] and return that exact type. Inside the function, 
 however, the type of s is the type declared, thus restricting its use.

 I need to convince myself that function bodies of this type can be 
 reliably typechecked, but first I wanted to run it by everyone to get 
 a feel of it.

 Equivariant functions are not (necessarily) templates and can be used 
 as virtual functions. Only one body is generated for one equivariant 
 function, unless other template mechanisms are in vigor.

 Here are some examples:

 a) Simple equivariance

 typeof(s) stripl(const(char)[] s);

 b) Parameterized equivariance

 typeof(s) stripl(S)(S s) if (isSomeString!S);

 c) Equivariance of field:

 typeof(s.ptr) getpointer(const(char)[] s);

 d) Equivariance inside a class/struct declaration:

 class S
 {
     typeof(this) clone();
     typeof(this.field) getfield();
     int field;
 }

 What do you think? I'm almost afraid to post this.


 Andrei

Sounds good. -- Please resolve ambiguity when s is a global variable. string s; // currently compiles. typeof(s) f(int s) { return typeof(return).init; }

Yes, incidentally there is a bug in the current compiler (I put it somewhere in bugzilla a while ago) that prevents it from using parameter names in a typeof expression. Andrei

Oh this is a bug? o_O I though it is expected. Anyway this suggestion seems better than taking the type of some global variable.
Oct 12 2008
prev sibling next sibling parent Derek Parnell <derek psych.ward> writes:
On Sun, 12 Oct 2008 14:34:05 -0500, Andrei Alexandrescu wrote:


 typeof(s) stripl(const(char)[] s);

 What do you think? I'm almost afraid to post this.

LOL. I already thought this was possible in D2. The concept sounds fine but the "typeof(s)" part is a bit clunky. I know we have to tell the compiler about this somehow, but maybe there is a better syntax. -- Derek Parnell Melbourne, Australia skype: derek.j.parnell
Oct 12 2008
prev sibling next sibling parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Many functions return one of their parameters regardless of the way it  
 was qualified.

 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);

 [snip]

 There's been several proposals in this group on tackling that problem.

 [snip]

 I discussed with Walter a variant that implements equivariant functions  
 without actually adding an explicit feature to the language. Consider:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an argument. I  
 propose that that pattern means stripl can accept _any_ subtype of  
 const(char)[] and return that exact type. Inside the function, however,  
 the type of s is the type declared, thus restricting its use.

Sounds more like an easter egg to me. But that will not work, I'm afraid. Consider the following example: char* strstr(char* str1, const(char)* str2) { return str1; // it's buggy, but who cares? } char* s = "hello".dup.ptr; char* p = strstr(s, "o"); p[0] = 0; printf("%s", s); // prints 'hell' Let's turn it into 'equivariant function': const(char)* strstr(const(char)* str1, const(char)* str2); Do you see the problem already? const(char)* strstr(const(char)* str1, const(char)* str2) { // which one of the two arguments is of dynamic constancy? Both? return str2; // <g> } char* s = "hello".dup.ptr; char* p = strstr(s, "o"); p[0] = 0; // bang! access violation The dynamic constancy attribute should be distiguishable from other attributes like const/invariant. May I suggest one of my personal preference? Here it is: sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2) { return str2; // compile-time error. Can't cast const(char)* to sameconst(char)* implicitly }
Oct 12 2008
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Denis Koroskin wrote:
 On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:
 Let's turn it into 'equivariant function':
 const(char)* strstr(const(char)* str1, const(char)* str2);
 
 Do you see the problem already?

Yes, you didn't write it properly :o). The correct syntax is: typeof(str1) strstr(const(char)* str1, const(char)* str2); Andrei
Oct 12 2008
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Denis Koroskin wrote:
 May I suggest one of my personal preference? Here it is:
 
 sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2)
 {
     return str2; // compile-time error. Can't cast const(char)* to 
 sameconst(char)* implicitly
 }

I prefer my solution because it's more general and addresses more issues than passing the qualifier out. Andrei
Oct 12 2008
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Denis Koroskin wrote:
 On Mon, 13 Oct 2008 01:17:36 +0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:
 
 Denis Koroskin wrote:
 May I suggest one of my personal preference? Here it is:
  sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2)
 {
     return str2; // compile-time error. Can't cast const(char)* to 
 sameconst(char)* implicitly
 }

I prefer my solution because it's more general and addresses more issues than passing the qualifier out. Andrei

Yes, my solution doesn't address clone() method return type, but I think they aren't related.

I just showed how they are related. You can't "think" they aren't related. Feel free to believe they aren't related, but that means you need to work around some facts.
 Besides, your solution can't implement the following example, so mine is 
 more flexible: :)
 
 sameconst(char)[] findPatternInS1OrS2(sameconst(char)[] s1,
     sameconst(char)[] s2, string needle);

This can be easily worked out with a template. There are some limitations of the solution I suggested. If we're able to address most problems, we can leave the occasional straggler to a template solution. Andrei
Oct 12 2008
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Denis Koroskin wrote:
 On Mon, 13 Oct 2008 01:27:42 +0400, Denis Koroskin <2korden gmail.com> 
 wrote:
 
 On Mon, 13 Oct 2008 01:17:36 +0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 May I suggest one of my personal preference? Here it is:
  sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2)
 {
     return str2; // compile-time error. Can't cast const(char)* to 
 sameconst(char)* implicitly
 }

I prefer my solution because it's more general and addresses more issues than passing the qualifier out. Andrei

Yes, my solution doesn't address clone() method return type, but I think they aren't related. Besides, your solution can't implement the following example, so mine is more flexible: :) sameconst(char)[] findPatternInS1OrS2(sameconst(char)[] s1, sameconst(char)[] s2, string needle);

And also this: class Storage { sameconst(Foo) foo() sameconst(this) // const, invariant or none { return _foo; } private Foo _foo; } Your version?

class Storage { typeof(this._foo) foo() const { return _foo; } } Please note that your version is technically identical to Walter's suggestion: T foo(return const T value); meaning that whatever qualifier value had will be returned back. Your solution is better visually at the obvious cost of adding a keyword, and let me warn again that we can't afford to add one keyword for each problem we solve. I believe it is more fertile to align compiler's view of the code with programmer expectations. Andrei
Oct 12 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Mon, 13 Oct 2008 01:16:16 +0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:
 Let's turn it into 'equivariant function':
 const(char)* strstr(const(char)* str1, const(char)* str2);
  Do you see the problem already?

Yes, you didn't write it properly :o). The correct syntax is: typeof(str1) strstr(const(char)* str1, const(char)* str2); Andrei

Yeah, my bad. But still, you can't argue that typeof(str1) == typeof(str2).
Oct 12 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Mon, 13 Oct 2008 01:17:36 +0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 May I suggest one of my personal preference? Here it is:
  sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2)
 {
     return str2; // compile-time error. Can't cast const(char)* to  
 sameconst(char)* implicitly
 }

I prefer my solution because it's more general and addresses more issues than passing the qualifier out. Andrei

Yes, my solution doesn't address clone() method return type, but I think they aren't related. Besides, your solution can't implement the following example, so mine is more flexible: :) sameconst(char)[] findPatternInS1OrS2(sameconst(char)[] s1, sameconst(char)[] s2, string needle);
Oct 12 2008
prev sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Mon, 13 Oct 2008 01:27:42 +0400, Denis Koroskin <2korden gmail.com>  
wrote:

 On Mon, 13 Oct 2008 01:17:36 +0400, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 May I suggest one of my personal preference? Here it is:
  sameconst(char)* strstr(sameconst(char)* str1, const(char)* str2)
 {
     return str2; // compile-time error. Can't cast const(char)* to  
 sameconst(char)* implicitly
 }

I prefer my solution because it's more general and addresses more issues than passing the qualifier out. Andrei

Yes, my solution doesn't address clone() method return type, but I think they aren't related. Besides, your solution can't implement the following example, so mine is more flexible: :) sameconst(char)[] findPatternInS1OrS2(sameconst(char)[] s1, sameconst(char)[] s2, string needle);

And also this: class Storage { sameconst(Foo) foo() sameconst(this) // const, invariant or none { return _foo; } private Foo _foo; } Your version?
Oct 12 2008
prev sibling next sibling parent Jason House <jason.james.house gmail.com> writes:
Andrei Alexandrescu wrote:

 I discussed with Walter a variant that implements equivariant functions
 without actually adding an explicit feature to the language. Consider:
 
 typeof(s) stripl(const(char)[] s);

I don't like how those who are unaware of this feature will misinterpret the meaning of the function signature. What about use as function arguments? I had to refactor a lot of interface-based code because some code needed exact types and the casting to/from interfaces was a significant performance hit.
 
 This signature states that it returns the same type as an argument. I
 propose that that pattern means stripl can accept _any_ subtype of
 const(char)[] and return that exact type. Inside the function, however,
 the type of s is the type declared, thus restricting its use.
 
 I need to convince myself that function bodies of this type can be
 reliably typechecked, but first I wanted to run it by everyone to get a
 feel of it.
 
 Equivariant functions are not (necessarily) templates and can be used as
 virtual functions. Only one body is generated for one equivariant
 function, unless other template mechanisms are in vigor.
 
 Here are some examples:
 
 a) Simple equivariance
 
 typeof(s) stripl(const(char)[] s);
 
 b) Parameterized equivariance
 
 typeof(s) stripl(S)(S s) if (isSomeString!S);
 
 c) Equivariance of field:
 
 typeof(s.ptr) getpointer(const(char)[] s);
 
 d) Equivariance inside a class/struct declaration:
 
 class S
 {
      typeof(this) clone();
      typeof(this.field) getfield();
      int field;
 }
 
 What do you think? I'm almost afraid to post this.
 
 
 Andrei

Oct 12 2008
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Hmm. Given:

   typeof(s) foo(long s);
   auto x = foo(1);

is x going to be declared as an int? If so, is the cast of the return 
type of foo() an implicit cast, or an explicit one? If explicit, what 
about downcast conversions? For example:

   class A : B { }
   typeof(a) foo(A a) { return new A(); }
   auto x = foo(b);

If we do an explicit cast, we've got null in x. I suppose that's ok.

What about this:

    typeof(s) foo(const char[] s) { auto x = new char[3]; ...; return x; }
    invariant(char)[] t;
    auto s = foo(t);

now s will be unsafely typed as invariant(char)[]. Since const cannot be 
safely cast to invariant or mutable, we might have a serious problem.
Oct 12 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Walter Bright wrote:
 Hmm. Given:
 
   typeof(s) foo(long s);
   auto x = foo(1);
 
 is x going to be declared as an int?

int is neither a supertype or a subtype of long. Andrei
Oct 12 2008
parent Max Samukha <samukha voliacable.com.removethis> writes:
On Sun, 12 Oct 2008 16:45:49 -0500, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:

Walter Bright wrote:
 Hmm. Given:
 
   typeof(s) foo(long s);
   auto x = foo(1);
 
 is x going to be declared as an int?

int is neither a supertype or a subtype of long. Andrei

Are typedefs subtypes of the type they are based on? typedef int Bar; typeof(s) foo(Bar s); auto x = foo(1); // is x int? What will be the type of a typeof(s) inside the function? typeof(s) foo(const(char)[] s) { alias typeof(s) T; // what type is T? }
Oct 12 2008
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Walter Bright wrote:
 If explicit, what 
 about downcast conversions? For example:
 
   class A : B { }
   typeof(a) foo(A a) { return new A(); }
   auto x = foo(b);
 
 If we do an explicit cast, we've got null in x. I suppose that's ok.

foo will not pass typechecking. Please note that we haven't discussed typechecking yet.
 What about this:
 
    typeof(s) foo(const char[] s) { auto x = new char[3]; ...; return x; }
    invariant(char)[] t;
    auto s = foo(t);
 
 now s will be unsafely typed as invariant(char)[]. Since const cannot be 
 safely cast to invariant or mutable, we might have a serious problem.

That function will not typecheck either. Let's defer the discussion on how to typecheck the function (I have a few good starters) to after we gauge interest in the solution. Andrei
Oct 12 2008
prev sibling next sibling parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Many functions return one of their parameters regardless of the way it  
 was qualified.

 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);

 Stripl is not a particularly good example because it needs to work on  
 wchar and dchar too, but let's ignore that aspect for now.

 There's been several proposals in this group on tackling that problem.

 In unrelated proposals and discussions, people mentioned the need for  
 functions that return the exact type of this:

 class A { A clone(); }
 class B : A { B clone(); }

 How can we declare A.clone such that all of its derived classes have it  
 return their own type?

 It took me a while to realize they are really very related. This is easy  
 to figure out if you think that invariant(char)[] and char[] are  
 subtypes of const(char)[]!

 I discussed with Walter a variant that implements equivariant functions  
 without actually adding an explicit feature to the language. Consider:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an argument. I  
 propose that that pattern means stripl can accept _any_ subtype of  
 const(char)[] and return that exact type. Inside the function, however,  
 the type of s is the type declared, thus restricting its use.

 I need to convince myself that function bodies of this type can be  
 reliably typechecked, but first I wanted to run it by everyone to get a  
 feel of it.

 Equivariant functions are not (necessarily) templates and can be used as  
 virtual functions. Only one body is generated for one equivariant  
 function, unless other template mechanisms are in vigor.

 Here are some examples:

 a) Simple equivariance

 typeof(s) stripl(const(char)[] s);

 b) Parameterized equivariance

 typeof(s) stripl(S)(S s) if (isSomeString!S);

 c) Equivariance of field:

 typeof(s.ptr) getpointer(const(char)[] s);

 d) Equivariance inside a class/struct declaration:

 class S
 {
      typeof(this) clone();
      typeof(this.field) getfield();
      int field;
 }

 What do you think? I'm almost afraid to post this.


 Andrei

Sorry to hit a dead horse but... auto dg = &stripl; // what is the type of dg? string z = dg("foo"); char[] x = dg("foo".dup); It is great that you brought the issue to discussion, but I think this solution is a miss.
Oct 12 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Denis Koroskin wrote:
 Sorry to hit a dead horse but...
 
 auto dg = &stripl; // what is the type of dg?

stripl is three overloads. You must use a precise type on the receiving side to get a pointer to function, as with any overloaded symbol.
 string z = dg("foo");
 char[] x = dg("foo".dup);
 
 It is great that you brought the issue to discussion, but I think this 
 solution is a miss.

I'm glad you're trying to dent it, this is the best way to find bugs in it. But let me note that so far you haven't gotten even near :o). Andrei
Oct 12 2008
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Denis Koroskin wrote:
 Ok, I'll try harder. Here is a common used pattern:
 
 interface IMaybeBar
 {
     Bar isBar();
     const(Bar) isBar();
     invariant(Bar) isBar();
 }

You mean: interface IMaybeBar { Bar isBar(); const(Bar) isBar() const; invariant(Bar) isBar() invariant; }
 class Foo : IMaybeBar
 {
     Bar isBar() { return null; }
     const(Bar) isBar() const { return null; }
     invariant(Bar) isBar() invariant { return null; }
 }
 
 class Bar : Foo
 {
     Bar isBar() { return this; }
     const(Bar) isBar() const { return this; }
     invariant(Bar) isBar() invariant { return this; }
 }
 
 let's change it into one.
 
 interface IMaybeBar
 {
     same(Bar) isBar() same(this); // same is shorter than sameconst and 
 as descriptive as sameconst
 }

// Yah and hijacks a short and common word. // When will the keyword bleeding stop?
 class Foo
 {
     same(Bar) isBar() same(this) { return null; }
 }
 
 class Bar : Foo
 {
     same(Bar) isBar() same(this) { return this; }
 }
 
 Your solution? Note that it shouldn't conflict with your clone example:
 
 class S
 {
      typeof(this) clone();
 }


My solution doesn't cover this case because the functions do not return an incoming argument. It's a good example. I take a dent. Andrei
Oct 12 2008
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Denis Koroskin wrote:
 On Mon, 13 Oct 2008 01:52:25 +0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:
 
 Denis Koroskin wrote:
 Sorry to hit a dead horse but...
  auto dg = &stripl; // what is the type of dg?

stripl is three overloads. You must use a precise type on the receiving side to get a pointer to function, as with any overloaded symbol.
 string z = dg("foo");
 char[] x = dg("foo".dup);
  It is great that you brought the issue to discussion, but I think 
 this solution is a miss.

I'm glad you're trying to dent it, this is the best way to find bugs in it. But let me note that so far you haven't gotten even near :o). Andrei

Ok, I'll try harder. Here is a common used pattern: interface IMaybeBar { Bar isBar(); const(Bar) isBar(); invariant(Bar) isBar(); } class Foo : IMaybeBar { Bar isBar() { return null; } const(Bar) isBar() const { return null; } invariant(Bar) isBar() invariant { return null; } } class Bar : Foo { Bar isBar() { return this; } const(Bar) isBar() const { return this; } invariant(Bar) isBar() invariant { return this; } } let's change it into one. interface IMaybeBar { same(Bar) isBar() same(this); // same is shorter than sameconst and as descriptive as sameconst } class Foo { same(Bar) isBar() same(this) { return null; } } class Bar : Foo { same(Bar) isBar() same(this) { return this; } } Your solution? Note that it shouldn't conflict with your clone example:
 class S
 {
      typeof(this) clone();
 }


Walter found an answer last night. interface IMaybeBar { Bar isBar(); const(Bar) isBar() const; invariant(Bar) isBar() invariant; } class Foo : IMaybeBar { PassQual!(typeof(this), Bar) isBar() const { return null; } } class Bar : Foo { typeof(this) isBar() const { return this; } } Andrei
Oct 13 2008
parent reply KennyTM~ <kennytm gmail.com> writes:
Andrei Alexandrescu wrote:
 Denis Koroskin wrote:
 On Mon, 13 Oct 2008 01:52:25 +0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 Sorry to hit a dead horse but...
  auto dg = &stripl; // what is the type of dg?

stripl is three overloads. You must use a precise type on the receiving side to get a pointer to function, as with any overloaded symbol.
 string z = dg("foo");
 char[] x = dg("foo".dup);
  It is great that you brought the issue to discussion, but I think 
 this solution is a miss.

I'm glad you're trying to dent it, this is the best way to find bugs in it. But let me note that so far you haven't gotten even near :o). Andrei

Ok, I'll try harder. Here is a common used pattern: interface IMaybeBar { Bar isBar(); const(Bar) isBar(); invariant(Bar) isBar(); } class Foo : IMaybeBar { Bar isBar() { return null; } const(Bar) isBar() const { return null; } invariant(Bar) isBar() invariant { return null; } } class Bar : Foo { Bar isBar() { return this; } const(Bar) isBar() const { return this; } invariant(Bar) isBar() invariant { return this; } } let's change it into one. interface IMaybeBar { same(Bar) isBar() same(this); // same is shorter than sameconst and as descriptive as sameconst } class Foo { same(Bar) isBar() same(this) { return null; } } class Bar : Foo { same(Bar) isBar() same(this) { return this; } } Your solution? Note that it shouldn't conflict with your clone example:
 class S
 {
      typeof(this) clone();
 }


Walter found an answer last night. interface IMaybeBar { Bar isBar(); const(Bar) isBar() const; invariant(Bar) isBar() invariant; } class Foo : IMaybeBar { PassQual!(typeof(this), Bar) isBar() const { return null; } } class Bar : Foo { typeof(this) isBar() const { return this; } } Andrei

What is PassQual!() ?_?
Oct 13 2008
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
KennyTM~ wrote:
 Andrei Alexandrescu wrote:
 Denis Koroskin wrote:
 On Mon, 13 Oct 2008 01:52:25 +0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 Sorry to hit a dead horse but...
  auto dg = &stripl; // what is the type of dg?

stripl is three overloads. You must use a precise type on the receiving side to get a pointer to function, as with any overloaded symbol.
 string z = dg("foo");
 char[] x = dg("foo".dup);
  It is great that you brought the issue to discussion, but I think 
 this solution is a miss.

I'm glad you're trying to dent it, this is the best way to find bugs in it. But let me note that so far you haven't gotten even near :o). Andrei

Ok, I'll try harder. Here is a common used pattern: interface IMaybeBar { Bar isBar(); const(Bar) isBar(); invariant(Bar) isBar(); } class Foo : IMaybeBar { Bar isBar() { return null; } const(Bar) isBar() const { return null; } invariant(Bar) isBar() invariant { return null; } } class Bar : Foo { Bar isBar() { return this; } const(Bar) isBar() const { return this; } invariant(Bar) isBar() invariant { return this; } } let's change it into one. interface IMaybeBar { same(Bar) isBar() same(this); // same is shorter than sameconst and as descriptive as sameconst } class Foo { same(Bar) isBar() same(this) { return null; } } class Bar : Foo { same(Bar) isBar() same(this) { return this; } } Your solution? Note that it shouldn't conflict with your clone example:
 class S
 {
      typeof(this) clone();
 }


Walter found an answer last night. interface IMaybeBar { Bar isBar(); const(Bar) isBar() const; invariant(Bar) isBar() invariant; } class Foo : IMaybeBar { PassQual!(typeof(this), Bar) isBar() const { return null; } } class Bar : Foo { typeof(this) isBar() const { return this; } } Andrei

What is PassQual!() ?_?

Pass the qualifiers of the first argument to the second argument. It's a simple template. Andrei
Oct 13 2008
prev sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Mon, 13 Oct 2008 01:52:25 +0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 Sorry to hit a dead horse but...
  auto dg = &stripl; // what is the type of dg?

stripl is three overloads. You must use a precise type on the receiving side to get a pointer to function, as with any overloaded symbol.
 string z = dg("foo");
 char[] x = dg("foo".dup);
  It is great that you brought the issue to discussion, but I think this  
 solution is a miss.

I'm glad you're trying to dent it, this is the best way to find bugs in it. But let me note that so far you haven't gotten even near :o). Andrei

Ok, I'll try harder. Here is a common used pattern: interface IMaybeBar { Bar isBar(); const(Bar) isBar(); invariant(Bar) isBar(); } class Foo : IMaybeBar { Bar isBar() { return null; } const(Bar) isBar() const { return null; } invariant(Bar) isBar() invariant { return null; } } class Bar : Foo { Bar isBar() { return this; } const(Bar) isBar() const { return this; } invariant(Bar) isBar() invariant { return this; } } let's change it into one. interface IMaybeBar { same(Bar) isBar() same(this); // same is shorter than sameconst and as descriptive as sameconst } class Foo { same(Bar) isBar() same(this) { return null; } } class Bar : Foo { same(Bar) isBar() same(this) { return this; } } Your solution? Note that it shouldn't conflict with your clone example:
 class S
 {
      typeof(this) clone();
 }

Oct 12 2008
prev sibling next sibling parent Sergey Gromov <snake.scaly gmail.com> writes:
Sun, 12 Oct 2008 14:34:05 -0500,
Andrei Alexandrescu wrote:
 I discussed with Walter a variant that implements equivariant functions 
 without actually adding an explicit feature to the language. Consider:
 
 typeof(s) stripl(const(char)[] s);
 
 This signature states that it returns the same type as an argument. I 
 propose that that pattern means stripl can accept _any_ subtype of 
 const(char)[] and return that exact type. Inside the function, however, 
 the type of s is the type declared, thus restricting its use.
 
 I need to convince myself that function bodies of this type can be 
 reliably typechecked, but first I wanted to run it by everyone to get a 
 feel of it.
 
 Equivariant functions are not (necessarily) templates and can be used as 
 virtual functions. Only one body is generated for one equivariant 
 function, unless other template mechanisms are in vigor.
 
 Here are some examples:
 
 a) Simple equivariance
 
 typeof(s) stripl(const(char)[] s);
 
 b) Parameterized equivariance
 
 typeof(s) stripl(S)(S s) if (isSomeString!S);
 
 c) Equivariance of field:
 
 typeof(s.ptr) getpointer(const(char)[] s);
 
 d) Equivariance inside a class/struct declaration:
 
 class S
 {
      typeof(this) clone();
      typeof(this.field) getfield();
      int field;
 }
 
 What do you think? I'm almost afraid to post this.

I'd use it.
Oct 12 2008
prev sibling next sibling parent reply Christian Kamm <kamm-incasoftware removethis.de> writes:
Can you, given an alias to a function that uses this equivariance, create a
forwarder function with the exact same behaviour? I.e. will calls to

typeof(return) identity(alias fn)(ParameterTuple!(fn) args) {
  return fn(args);
}

or similar work exactly as calls to fn, even though typeof(return) has to be
more than just a type? The parameter storage classes ref, out, lazy already
make this kind of forwarding very tricky to do right (I think you have to
escape to mixins) and I fear this'll make it even more involved.
Oct 13 2008
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Christian Kamm wrote:
 Can you, given an alias to a function that uses this equivariance, create a
 forwarder function with the exact same behaviour? I.e. will calls to
 
 typeof(return) identity(alias fn)(ParameterTuple!(fn) args) {
   return fn(args);
 }
 
 or similar work exactly as calls to fn, even though typeof(return) has to be
 more than just a type? The parameter storage classes ref, out, lazy already
 make this kind of forwarding very tricky to do right (I think you have to
 escape to mixins) and I fear this'll make it even more involved.

I think so. Technically the problem is similar to handling an overloaded fn. Andrei
Oct 13 2008
prev sibling next sibling parent reply ore-sama <spam here.lot> writes:
Andrei Alexandrescu Wrote:

 typeof(s) stripl(const(char)[] s);
 
 This signature states that it returns the same type as an argument. I 
 propose that that pattern means stripl can accept _any_ subtype of 
 const(char)[] and return that exact type. Inside the function, however, 
 the type of s is the type declared, thus restricting its use.

this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.
Oct 13 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
ore-sama wrote:
 Andrei Alexandrescu Wrote:
 
 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an argument. I 
 propose that that pattern means stripl can accept _any_ subtype of 
 const(char)[] and return that exact type. Inside the function, however, 
 the type of s is the type declared, thus restricting its use.

this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.

That is correct. Andrei
Oct 13 2008
parent reply Jason House <jason.james.house gmail.com> writes:
Andrei Alexandrescu Wrote:

 ore-sama wrote:
 Andrei Alexandrescu Wrote:
 
 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an argument. I 
 propose that that pattern means stripl can accept _any_ subtype of 
 const(char)[] and return that exact type. Inside the function, however, 
 the type of s is the type declared, thus restricting its use.

this conflicts with current definition of typeof. Currently typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.

That is correct. Andrei

I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.
Oct 13 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Jason House wrote:
 Andrei Alexandrescu Wrote:
 
 ore-sama wrote:
 Andrei Alexandrescu Wrote:
 
 typeof(s) stripl(const(char)[] s);
 
 This signature states that it returns the same type as an
 argument. I propose that that pattern means stripl can accept
 _any_ subtype of const(char)[] and return that exact type.
 Inside the function, however, the type of s is the type
 declared, thus restricting its use.

typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.

Andrei

I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.

Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andrei
Oct 13 2008
next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
Andrei Alexandrescu wrote:
 Jason House wrote:
 Andrei Alexandrescu Wrote:

 ore-sama wrote:
 Andrei Alexandrescu Wrote:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an
 argument. I propose that that pattern means stripl can accept
 _any_ subtype of const(char)[] and return that exact type.
 Inside the function, however, the type of s is the type
 declared, thus restricting its use.

typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.

Andrei

I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.

Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andrei

Yes. That makes foreign English learners confusing the many uses of prepositions. I think a programming language should not be like a natural language, but it must be precise enough to reduce ambiguity as much as possible. Overloading keywords with entirely different meaning is no good (scope, static, invariant). (I think this is OK to use typeof in this feature, since you're really getting a type of something.) - BTW, the keyword problem exists because we chose to use /[_a-z]\w*/ to represent identifiers, which coincide with keywords. Perl and PHP, for example, uses /[$ *%][_a-z]\w*/ to represent variables, so the keyword problem can be somewhat diminished. Of course I'm not asking you to adopt this, as I hate those sigils too, but I'm just saying adding keywords can leave user identifiers alone.
Oct 13 2008
parent reply Yigal Chripun <yigal100 gmail.com> writes:
KennyTM~ wrote:
 Andrei Alexandrescu wrote:
 Jason House wrote:
 Andrei Alexandrescu Wrote:

 ore-sama wrote:
 Andrei Alexandrescu Wrote:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an
 argument. I propose that that pattern means stripl can accept
 _any_ subtype of const(char)[] and return that exact type.
 Inside the function, however, the type of s is the type
 declared, thus restricting its use.

typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.

Andrei

I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.

Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andrei

Yes. That makes foreign English learners confusing the many uses of prepositions. I think a programming language should not be like a natural language, but it must be precise enough to reduce ambiguity as much as possible. Overloading keywords with entirely different meaning is no good (scope, static, invariant). (I think this is OK to use typeof in this feature, since you're really getting a type of something.) - BTW, the keyword problem exists because we chose to use /[_a-z]\w*/ to represent identifiers, which coincide with keywords. Perl and PHP, for example, uses /[$ *%][_a-z]\w*/ to represent variables, so the keyword problem can be somewhat diminished. Of course I'm not asking you to adopt this, as I hate those sigils too, but I'm just saying adding keywords can leave user identifiers alone.

if I remember correctly, C# allows you to use keywords as identifiers. for example: int if = 5; the tells the parser not to treat "if" as a reserved word. I don't know how much that feature is actually being used but having a way to escape keywords like that also solves the problem.
Oct 13 2008
parent KennyTM~ <kennytm gmail.com> writes:
Yigal Chripun wrote:
 KennyTM~ wrote:
 Andrei Alexandrescu wrote:
 Jason House wrote:
 Andrei Alexandrescu Wrote:

 ore-sama wrote:
 Andrei Alexandrescu Wrote:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an
 argument. I propose that that pattern means stripl can accept
 _any_ subtype of const(char)[] and return that exact type.
 Inside the function, however, the type of s is the type
 declared, thus restricting its use.

typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.

Andrei

prefer new keywords for new concepts.

different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andrei

prepositions. I think a programming language should not be like a natural language, but it must be precise enough to reduce ambiguity as much as possible. Overloading keywords with entirely different meaning is no good (scope, static, invariant). (I think this is OK to use typeof in this feature, since you're really getting a type of something.) - BTW, the keyword problem exists because we chose to use /[_a-z]\w*/ to represent identifiers, which coincide with keywords. Perl and PHP, for example, uses /[$ *%][_a-z]\w*/ to represent variables, so the keyword problem can be somewhat diminished. Of course I'm not asking you to adopt this, as I hate those sigils too, but I'm just saying adding keywords can leave user identifiers alone.

if I remember correctly, C# allows you to use keywords as identifiers. for example: int if = 5; the tells the parser not to treat "if" as a reserved word. I don't know how much that feature is actually being used but having a way to escape keywords like that also solves the problem.

int _if = 5; The problem Andrei stated is not existing keywords chewing the vocabularies I believe, but *new* keywords chewing the vocabularies. But I also think that there are some "universal keywords" that a good programmer should avoid using as an identifier, e.g. "define", "implements" etc. so you're free to use, though it could also be quite subjective.
Oct 13 2008
prev sibling next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
Andrei Alexandrescu wrote:
 Jason House wrote:
 Andrei Alexandrescu Wrote:

 ore-sama wrote:
 Andrei Alexandrescu Wrote:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an
 argument. I propose that that pattern means stripl can accept
 _any_ subtype of const(char)[] and return that exact type.
 Inside the function, however, the type of s is the type
 declared, thus restricting its use.

typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.

Andrei

I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.

Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andrei

Just read this paper by Herb Sutter last night: http://www.gotw.ca/publications/C++CLIRationale.pdf In particular, the Bjarne Stroustrup quote is quite poignant: "" My experience is that people are addicted to keywords for introducing concepts to the point where a concept that doesnt have its own keyword is surprisingly hard to teach. This effect is more important and deep-rooted than peoples vocally expressed dislike for new keywords. Given a choice and time to consider, people invariably choose the new keyword over a clever workaround. B. Stroustrup (D&E, p. 119) "" And Herb himself: "" When a language feature is necessary, programmers strongly prefer keywords. Normally, all C++ keywords are also re- served words, and taking a new one would break code that is already using that word as an identifier (e.g., as a type or variable name). C++/CLI avoids adding reserved words so as to preserve the goal of having pure extensions, but it also recognizes that programmers expect keywords. C++/CLI balances these re- quirements by adding keywords where most are not reserved words and so do not conflict with user identifiers. ""
Oct 13 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Robert Fraser wrote:
 Just read this paper by Herb Sutter last night:
 
 http://www.gotw.ca/publications/C++CLIRationale.pdf
 
 In particular, the Bjarne Stroustrup quote is quite poignant:
 
 ""
 My experience is that people are addicted to keywords for introducing 
 concepts to the point where a concept  that doesnt  have its own 
 keyword is surprisingly  hard to  teach. This effect is more important 
 and deep-rooted  than  peoples  vocally expressed  dislike for new 
 keywords.  Given a choice and time to consider, people invariably choose 
 the new keyword over a clever workaround.
        B. Stroustrup (D&E, p. 119)
 ""

I agree. I'd also add that that could backfire as well. "class" being essentially the same deal as "struct" with a different hat has wasted a perfect opportunity to create a really useful distinct concept. Right now it's no more than a source of embarrassment. "typename" is a shame begotten by another shame, the angle brackets. "explicit" is an expensive fix for a minor problem, bringing along with it new oddities and corner-case behaviors that had to be defined.
 And Herb himself:
 
 ""
 When a language feature is necessary, programmers strongly
 prefer keywords. Normally, all C++ keywords  are also re-
 served words, and taking a new one would break code that
 is already using that word as an identifier (e.g., as a type or
 variable name).
 
 C++/CLI avoids adding reserved words so as to preserve the
 goal of having pure extensions, but it also recognizes that
 programmers expect keywords. C++/CLI balances  these re-
 quirements by adding keywords where most are not reserved
 words and so do not conflict with user identifiers.
 ""

I agree. When considering keyword addition, I think we all should think more about adding contextual keywords, which in the grand D tradition are usually defined as keyword(contextual_keyword). Andrei
Oct 13 2008
next sibling parent reply Jeff Nowakowski <jeff dilacero.org> writes:
Andrei Alexandrescu wrote:
 I agree. When considering keyword addition, I think we all should think 
 more about adding contextual keywords, which in the grand D tradition 
 are usually defined as keyword(contextual_keyword).

Is there some reason why most languages don't use a reserved symbol for keywords, like prefixing them with $ or %? Every language runs into the problem of wanting to add new keywords later on -- why don't new languages avoid this old problem? -Jeff
Oct 13 2008
parent Benji Smith <dlanguage benjismith.net> writes:
Bill Baxter wrote:
 I've not seen a language where keywords are prefixed, but now that you
 mention it, it does kinda make sense.  You only expect to have a few
 dozen keywords, but the number of variables used in any given program
 will be much much greater.  So if you're going to push one or the
 other into a separate namespace, it seems more logical to do it to the
 keywords, not the identifiers.

The C preprocessor uses # as a prefix for all of its keywords. My preference in D would have been for compile-time keywords to use that same convention, rather than all being prefixed with the word "static". --benji
Oct 13 2008
prev sibling parent Robert Fraser <fraserofthenight gmail.com> writes:
Andrei Alexandrescu wrote:
 I agree. When considering keyword addition, I think we all should think 
 more about adding contextual keywords, which in the grand D tradition 
 are usually defined as keyword(contextual_keyword).

C++/CLI adds them in quite a few places as long as they could not be misinterpreted as identifiers, and still allows them as identifiers in places where identifiers would be expected. Of course, it makes writing a parser a bit harder (but the technique itself doesn't require semantic analysis, so D could use this while maintaining an unambiguous grammar). For example, the "in" keyword can never occur in an identifier position, so it's treated as an identifier if it's in one, and treated as an operator if it's seen in a "for each" loop (another cool syntax in the language to sneak in a keyword -- note the space).
Oct 14 2008
prev sibling next sibling parent Christopher Wright <dhasenan gmail.com> writes:
Andrei Alexandrescu wrote:
 Even in natural language identical words are reused for various 
 different meanings. This suggests that people would not feel comfortable 
 with a very large vocabulary. The size of vocabularies varies 
 dramatically, but if you look closer you'll see that languages with 
 large vocabularies tend to have simpler grammars, suggesting that a 
 language's overall complexity is a constant-sum game.

When you try to use principles from natural languages in a programming language, you might end up with Perl. For all of our sakes, be careful. For that matter, what about adding a few synonyms to D? There's always == versus is for primitive value types....
Oct 13 2008
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Andrei Alexandrescu wrote:
 Jason House wrote:
 Andrei Alexandrescu Wrote:

 ore-sama wrote:
 Andrei Alexandrescu Wrote:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an
 argument. I propose that that pattern means stripl can accept
 _any_ subtype of const(char)[] and return that exact type.
 Inside the function, however, the type of s is the type
 declared, thus restricting its use.

typeof(s) stripl(const(char)[] s); should be interpreted exactly as const(char)[] stripl(const(char)[] s); it's unclear that typeof here gets type of actual argument rather than parameter. Currently typeof applies to parameter.

Andrei

I've never understood the reuse of keywords for new meanings. I much prefer new keywords for new concepts.

Even in natural language identical words are reused for various different meanings. This suggests that people would not feel comfortable with a very large vocabulary. The size of vocabularies varies dramatically, but if you look closer you'll see that languages with large vocabularies tend to have simpler grammars, suggesting that a language's overall complexity is a constant-sum game. In programming languages growing the vocabulary indiscriminately is worse because it chews into the vocabulary available to user-defined symbols. Also, whenever a feature is to be integrated into the language, it is preferable if possible to integrate it under an existing concept instead of "allocating" a new concept to it. Andrei

But this is somewhat unprecedented in D. In other situations of keyword recycling, the new keyword meaning was usually added in a new context where the previous use of the same keyword was not valid and was totally unrelated. (ie, 'invariant' for example). But this makes new use of typeof in a situation which is very similar to the previous use, but not consistent or orthogonal with such previous use (ie, the behavior is quite different). The only thing that distinguishes the uses is whether the typeof expression references a parameter defined ahead, instead of something which is in scope. This seems confusing to look for. In fact, one could argue that typeof (the normal one) used in a function return type should be able to see the function's parameter, even though syntactically it appears before (semantically it makes more sense to come afterwards). -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 18 2008
prev sibling next sibling parent "Bill Baxter" <wbaxter gmail.com> writes:
On Tue, Oct 14, 2008 at 12:02 PM, Jeff Nowakowski <jeff dilacero.org> wrote:
 Andrei Alexandrescu wrote:
 I agree. When considering keyword addition, I think we all should think
 more about adding contextual keywords, which in the grand D tradition are
 usually defined as keyword(contextual_keyword).

Is there some reason why most languages don't use a reserved symbol for keywords, like prefixing them with $ or %? Every language runs into the problem of wanting to add new keywords later on -- why don't new languages avoid this old problem?

As someone mentioned, Perl uses the opposite. User identifiers are prefixed with $ or , keywords are bare. I think the reason it's not used more commonly is just that the code starts to look like line noise. I've not seen a language where keywords are prefixed, but now that you mention it, it does kinda make sense. You only expect to have a few dozen keywords, but the number of variables used in any given program will be much much greater. So if you're going to push one or the other into a separate namespace, it seems more logical to do it to the keywords, not the identifiers. I think in Perl's case, Larry Wall thought that making the distinction between scalars ($var) and arrays ( var) would be useful information to always have right in your face like. --bb
Oct 13 2008
prev sibling parent "Bill Baxter" <wbaxter gmail.com> writes:
On Tue, Oct 14, 2008 at 1:54 PM, Benji Smith <dlanguage benjismith.net> wrote:
 Bill Baxter wrote:
 I've not seen a language where keywords are prefixed, but now that you
 mention it, it does kinda make sense.  You only expect to have a few
 dozen keywords, but the number of variables used in any given program
 will be much much greater.  So if you're going to push one or the
 other into a separate namespace, it seems more logical to do it to the
 keywords, not the identifiers.

The C preprocessor uses # as a prefix for all of its keywords.

Good point. A sub-language grafted on top of another language... Text markup languages like HTML/XML etc are like that too, now that I think of it. The keywords aren't prefixed, but they are syntactically separated. Doxygen/JavaDoc/DDoc are other examples. Those param type keywords are all prefixed. LaTeX is like that too.
 My preference in D would have been for compile-time keywords to use that
 same convention, rather than all being prefixed with the word "static".

Interesting thought. # is certainly another one of those syntactic under-performers. There was a discussion about reclaiming it maybe over a year ago. I think it involved Andrei, too -- in his first stint on the NG, before his extended leave. --bb
Oct 13 2008
prev sibling next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2008-10-12 15:34:05 -0400, Andrei Alexandrescu 
<SeeWebsiteForEmail erdani.org> said:

 Many functions return one of their parameters regardless of the way it 
 was qualified.
 
 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);
 
 Stripl is not a particularly good example because it needs to work on 
 wchar and dchar too, but let's ignore that aspect for now.
 
 There's been several proposals in this group on tackling that problem.

I'm not sure why one would one add another syntax feature for this. I mean: we have templates which are capable of this. Sure, templates are instanciated only when needed and that's not what we want, but I think we'd better reuse the syntax we have, adding a modifier and if necessary for forcing one instanciation at the definition site when possible. I'm not sure if this is valid in D2 (I don't have a D2 compiler in front of me right now), but let's say that this makes C any subtype of const(char): C strip1(C : const(char))(C[] s); This template does what you want, except that it's instanciated only at the call site, making it unusable in some situations. What we need is to make this specific template work like C# and Java's generics: only one instanciation dealing with multiple parametrized types. So let's extend the template syntax a little: C strip1(equivariant C : const(char))(C[] s); Hum, what did that change? Well, the new "equivariant" keyword I added imposes new restrictions to the template argument, such as you cannot know the exact type beyond the type restriction in the parameter definition. These restrictions are designed to enable only one instanciation of the template to cover them all. For instance: C strip1(equivariant C : const(char))(C[] s) { C[] s1 = s; // okay, s1 is of the same type. C[] s2 = "abc"; // error, "abc" is invariant(char)[], // incompatible with base type const(char)[] C[] s2 = new C[5]; // can allocate since we know we deal with "char", // whatever constness it has, it'll always be of the same size. return s; // okay, same type. } Reusing the template syntax would avoid adding yet another different but similar syntax for generic functions. Perhaps you find the template syntax overly verbose for this (I do), but then I'd say it's the template syntax that ought to be rethought instead reinventing it in another more limited form for equivariant functions. (Of course we could start from an equivalent function syntax and then extend it to templates later, so I'm not really against what you're exposing here. I only wish we can reach some syntax consistency in the end.) -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Oct 13 2008
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Michel Fortin wrote:
 On 2008-10-12 15:34:05 -0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> said:
 
 Many functions return one of their parameters regardless of the way it 
 was qualified.

 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);

 Stripl is not a particularly good example because it needs to work on 
 wchar and dchar too, but let's ignore that aspect for now.

 There's been several proposals in this group on tackling that problem.

I'm not sure why one would one add another syntax feature for this. I mean: we have templates which are capable of this. Sure, templates are instanciated only when needed and that's not what we want, but I think we'd better reuse the syntax we have, adding a modifier and if necessary for forcing one instanciation at the definition site when possible.

That's a good point.
 I'm not sure if this is valid in D2 (I don't have a D2 compiler in front 
 of me right now), but let's say that this makes C any subtype of 
 const(char):
 
     C strip1(C : const(char))(C[] s);

High time to change fonts in your newsreader: it's stripL, not stripOne... :o) Here's a better way to write that template: S stripl(S)(S s) if (is(S : const char[])); meaning S is a subtype of const char[].
 This template does what you want, except that it's instanciated only at 
 the call site, making it unusable in some situations. What we need is to 
 make this specific template work like C# and Java's generics: only one 
 instanciation dealing with multiple parametrized types. So let's extend 
 the template syntax a little:
 
     C strip1(equivariant C : const(char))(C[] s);
 
 Hum, what did that change? Well, the new "equivariant" keyword I added 
 imposes new restrictions to the template argument, such as you cannot 
 know the exact type beyond the type restriction in the parameter 
 definition.

I think this could work, but then if you take the hit of a new keyword, maybe Denis' solution works even better. It's less verbose and easier to understand. Andrei
Oct 13 2008
prev sibling parent reply Jason House <jason.james.house gmail.com> writes:
Michel Fortin Wrote:

 On 2008-10-12 15:34:05 -0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> said:
 
 Many functions return one of their parameters regardless of the way it 
 was qualified.
 
 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);
 
 Stripl is not a particularly good example because it needs to work on 
 wchar and dchar too, but let's ignore that aspect for now.
 
 There's been several proposals in this group on tackling that problem.

I'm not sure why one would one add another syntax feature for this. I mean: we have templates which are capable of this. Sure, templates are instanciated only when needed and that's not what we want, but I think we'd better reuse the syntax we have, adding a modifier and if necessary for forcing one instanciation at the definition site when possible.

Templates are non-virtual right now
 
 I'm not sure if this is valid in D2 (I don't have a D2 compiler in 
 front of me right now), but let's say that this makes C any subtype of 
 const(char):
 
 	C strip1(C : const(char))(C[] s);
 
 This template does what you want, except that it's instanciated only at 
 the call site, making it unusable in some situations. What we need is 
 to make this specific template work like C# and Java's generics: only 
 one instanciation dealing with multiple parametrized types. So let's 
 extend the template syntax a little:
 
 	C strip1(equivariant C : const(char))(C[] s);
 
 Hum, what did that change? Well, the new "equivariant" keyword I added 
 imposes new restrictions to the template argument, such as you cannot 
 know the exact type beyond the type restriction in the parameter 
 definition. These restrictions are designed to enable only one 
 instanciation of the template to cover them all. For instance:
 
 	C strip1(equivariant C : const(char))(C[] s)
 	{
 		C[] s1 = s; // okay, s1 is of the same type.
 		C[] s2 = "abc"; // error, "abc" is invariant(char)[],
 		                // incompatible with base type const(char)[]
 		C[] s2 = new C[5]; // can allocate since we know we deal with "char",
 		                   // whatever constness it has, it'll always be of 
 the same size.
 		return s; // okay, same type.
 	}
 
 Reusing the template syntax would avoid adding yet another different 
 but similar syntax for generic functions. Perhaps you find the template 
 syntax overly verbose for this (I do), but then I'd say it's the 
 template syntax that ought to be rethought instead reinventing it in 
 another more limited form for equivariant functions.
 
 (Of course we could start from an equivalent function syntax and then 
 extend it to templates later, so I'm not really against what you're 
 exposing here. I only wish we can reach some syntax consistency in the 
 end.)
 
 -- 
 Michel Fortin
 michel.fortin michelf.com
 http://michelf.com/
 

Oct 13 2008
parent reply Christopher Wright <dhasenan gmail.com> writes:
Jason House wrote:
 Templates are non-virtual right now

That could be changed, but it would require a lot of bookkeeping in temporary files (and not object files). And it would interfere with precompiled libraries. Once D gets decent runtime reflection, I'd like to get generics that would pretty much just hide a bunch of casts and reflection calls: generic (T : class) void foo (T thing) { int i = thing.someMethod(); thing.otherMethod (10, true, new Object()); } transforms to: void foo (ClassInfo info, Object thing) { int i = *cast(int*)info.getMethod ("someMethod").invoke!()(thing); info.getMethod ("otherMethod").invoke!(uint, bool, Object)(thing, 10, true, new Object()); } But that's probably a bit much to ask.
Oct 13 2008
parent reply Jason House <jason.james.house gmail.com> writes:
Christopher Wright wrote:

 Jason House wrote:
 Templates are non-virtual right now

That could be changed, but it would require a lot of bookkeeping in temporary files (and not object files). And it would interfere with precompiled libraries.

If a template's arguments can be defined a prior, adding the functions to the vtable is easy. That middle ground may be useful, but wouldn't work for everything.
Oct 13 2008
parent Christopher Wright <dhasenan gmail.com> writes:
Jason House wrote:
 Christopher Wright wrote:
 
 Jason House wrote:
 Templates are non-virtual right now

temporary files (and not object files). And it would interfere with precompiled libraries.

If a template's arguments can be defined a prior, adding the functions to the vtable is easy. That middle ground may be useful, but wouldn't work for everything.

If the range of possible arguments is not extensible, yes. You can get this by deferring the creation of the vtbl until after all templates have been instantiated. Or you can get it by analyzing the template parameters, but that's extremely expensive. Maybe if the parameter is an enum value, or based on constness of a particular type, but that's about it. That would take a lot of development time. I hope that Walter doesn't choose to pursue this in the near future.
Oct 13 2008
prev sibling next sibling parent reply Aarti_pl <aarti interia.pl> writes:
I don't want to push once again my solution for templates from "Uniform 
syntax" thread, but it seems that my solution would work also in this 
case as it is another type of pattern matching.

My comments inlined.


Andrei Alexandrescu pisze:
 Many functions return one of their parameters regardless of the way it 
 was qualified.
 
 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);
 
 Stripl is not a particularly good example because it needs to work on 
 wchar and dchar too, but let's ignore that aspect for now.
 
 There's been several proposals in this group on tackling that problem.

Q(char)[] stripl(Q : Q(char)[] s); It is not template, so it should be putted into "runtime parenthesis". In first case (function taking char[]) Q will be evaluated to nothing and will be skipped.
 In unrelated proposals and discussions, people mentioned the need for 
 functions that return the exact type of this:
 
 class A { A clone(); }
 class B : A { B clone(); }
 
 How can we declare A.clone such that all of its derived classes have it 
 return their own type?

In this case I would just use typeof(this) class A { typeof(this) clone(); } class B : A { typeof(this) clone(); } I don't see a reason why it should be same as in above case.
 It took me a while to realize they are really very related. This is easy 
 to figure out if you think that invariant(char)[] and char[] are 
 subtypes of const(char)[]!
 
 I discussed with Walter a variant that implements equivariant functions 
 without actually adding an explicit feature to the language. Consider:
 
 typeof(s) stripl(const(char)[] s);

This solution seems to me quite unclear. I would prefer if something better would be invented.
 This signature states that it returns the same type as an argument. I 
 propose that that pattern means stripl can accept _any_ subtype of 
 const(char)[] and return that exact type. Inside the function, however, 
 the type of s is the type declared, thus restricting its use.
 
 I need to convince myself that function bodies of this type can be 
 reliably typechecked, but first I wanted to run it by everyone to get a 
 feel of it.
 
 Equivariant functions are not (necessarily) templates and can be used as 
 virtual functions. Only one body is generated for one equivariant 
 function, unless other template mechanisms are in vigor.
 
 Here are some examples:
 
 a) Simple equivariance
 
 typeof(s) stripl(const(char)[] s);

Q(char)[] stripl(Q : Q(char)[] s); or typeof(s) stripl(Q : Q(char)[] s); No problems here.
 b) Parameterized equivariance
 
 typeof(s) stripl(S)(S s) if (isSomeString!S);

Q(S) stripl(S)(Q : Q(S) if(isSomeString!(S)) s); or Q(S) stripl(S)(Q : Q(S) s) if(isSomeString!(S)); or typeof(s) stripl(S)(Q : Q(S) s) if(isSomeString!(S)); (Sorry, I don't accommodate syntax without parenthesis for one parameter templates :-[)
 
 c) Equivariance of field:
 
 typeof(s.ptr) getpointer(const(char)[] s);

typeof(s.ptr) getpointer(Q : Q(char)[] s); In this case Q doesn't matter, so it is not used in result type. typeof is still useful.
 d) Equivariance inside a class/struct declaration:
 
 class S
 {
     typeof(this) clone();
     typeof(this.field) getfield();
     int field;
 }
 

Looks ok.
 What do you think? I'm almost afraid to post this.
 Andrei

IMHO your solution is almost perfect. I was lacking just a clear definition what can be changed by user. My version is maybe longer, but also more clear about intents. IMHO two versions should be allowed: Q(char)[] stripl(Q : Q(char)[] s); and typeof(s) stripl(Q : Q(char)[] s); Best Regards Marcin Kuszczak (aarti_pl)
Oct 13 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Aarti_pl wrote:
 I don't want to push once again my solution for templates from "Uniform 
 syntax" thread, but it seems that my solution would work also in this 
 case as it is another type of pattern matching.
 
 My comments inlined.
 
 
 Andrei Alexandrescu pisze:
 Many functions return one of their parameters regardless of the way it 
 was qualified.

 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);

 Stripl is not a particularly good example because it needs to work on 
 wchar and dchar too, but let's ignore that aspect for now.

 There's been several proposals in this group on tackling that problem.

Q(char)[] stripl(Q : Q(char)[] s);

It was my first choice. Walter would rather avoid it for implementation difficulty reasons.
 It is not template, so it should be putted into "runtime parenthesis". 
 In first case (function taking char[]) Q will be evaluated to nothing 
 and will be skipped.

And that's part of the difficulty: there is one more type of alias, and now it could even expand to nothing.
 In unrelated proposals and discussions, people mentioned the need for 
 functions that return the exact type of this:

 class A { A clone(); }
 class B : A { B clone(); }

 How can we declare A.clone such that all of its derived classes have 
 it return their own type?

In this case I would just use typeof(this) class A { typeof(this) clone(); } class B : A { typeof(this) clone(); } I don't see a reason why it should be same as in above case.

Because you don't want B's implementor to implement clone to return an A. That would be a mistake.
 It took me a while to realize they are really very related. This is 
 easy to figure out if you think that invariant(char)[] and char[] are 
 subtypes of const(char)[]!

 I discussed with Walter a variant that implements equivariant 
 functions without actually adding an explicit feature to the language. 
 Consider:

 typeof(s) stripl(const(char)[] s);

This solution seems to me quite unclear. I would prefer if something better would be invented.

Yah me too. Andrei
Oct 13 2008
parent Aarti_pl <aarti interia.pl> writes:
Andrei Alexandrescu pisze:

 In unrelated proposals and discussions, people mentioned the need for 
 functions that return the exact type of this:

 class A { A clone(); }
 class B : A { B clone(); }

 How can we declare A.clone such that all of its derived classes have 
 it return their own type?

In this case I would just use typeof(this) class A { typeof(this) clone(); } class B : A { typeof(this) clone(); } I don't see a reason why it should be same as in above case.

Because you don't want B's implementor to implement clone to return an A. That would be a mistake.

IMHO it is much more consistent than current situation. When you write typeof(this) in class B it should mean B not A (currently when overriding A function with above signature you will get result of type A ), because "this" for B objects is clearly of type B. Additionally such a syntax shows that function participate in overriding, as the result type is same (like in common cases). BTW. I hope that Walter will reconsider more complicated solutions (also my proposal :-) ) because compiler "facade" to user is very important. This is the syntax, which makes working with language pleasure or difficult. It might be worthy to put more time into implementation, but get more flexible and clean solution. BR Marcin Kuszczak (aarti_pl)
Oct 13 2008
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
I'm glad there's interest in equivariant functions. Let me share below 
how I think they can be properly typechecked.

The general signature of an equivariant function is:

typeof(expression_involving(pk)) fun(T1 p1, ..., Tk pk, ..., Tn pn);

The actual notation does not matter for the purpose of typechecking. 
What the notation means is that fun will accept a subtype of Tk in 
position pk, call it Uk, and will return type Uk. Of course, for that 
magic to happen there are a few restrictions that must be met.

The simplest way to typecheck fun is by substituting Tk with a typedef 
for it:

typedef Tk __Surrogate;
typeof(expression_involving(__Surrogate.init))
fun(T1 p1, ..., __Surrogate pk, ..., Tn pn)
{
    ... typecheck me ...
}

The typedef was Walter's idea; my idea involved a synthetic subtype. I 
think the typedef has a lot of appeal. This is because the typedef of Tk 
is almost like a subtype of Tk: it has Tk's layout trivially prefix it, 
converts to Tk implicitly, yet it is a distinct type.

Let's see this typechecking at work:

1. Accessing a field:

typeof(s.ptr) at(const char[] s, uint i) { return s.ptr + i; }

Typecheck with a typedef for const char[], yielding:

typedef const(char[]) __Surrogate;
typeof(__Surrogate.init.ptr) at(__Surrogate s, uint i)
{ return s.ptr + i; }

Pass.

2. Returning (part of) an argument:

typeof(s) stripl(const(char)[] s)
{
     uint i;
     ...
     return s[i .. $];
}

Typecheck like this:

typedef const(char)[] __Surrogate;
__Surrogate stripl(__Surrogate s)
{
     uint i;
     ...
     return s[i .. $];
}

Fail. I think that reveals a bug in the compiler. A slice of a typedef'd 
array should return the same type as the array itself. I'll file a bug.

3. Walter's example:

class Base {}
class Derived : Base {}

typeof(a) foo(Base a) { return new Base; }

Typecheck like this:

typedef Base __Surrogate;
__Surrogate foo(__Surrogate a)
{
     return new Base;
}

Fail, as it should.


Come at it with all you've got.

Andrei
Oct 13 2008
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 I'm glad there's interest in equivariant functions. Let me share below how 
 I think they can be properly typechecked.

 The general signature of an equivariant function is:

 typeof(expression_involving(pk)) fun(T1 p1, ..., Tk pk, ..., Tn pn);

 The actual notation does not matter for the purpose of typechecking. What 
 the notation means is that fun will accept a subtype of Tk in position pk, 
 call it Uk, and will return type Uk. Of course, for that magic to happen 
 there are a few restrictions that must be met.

 The simplest way to typecheck fun is by substituting Tk with a typedef for 
 it:

 typedef Tk __Surrogate;
 typeof(expression_involving(__Surrogate.init))
 fun(T1 p1, ..., __Surrogate pk, ..., Tn pn)
 {
    ... typecheck me ...
 }

 The typedef was Walter's idea; my idea involved a synthetic subtype. I 
 think the typedef has a lot of appeal. This is because the typedef of Tk 
 is almost like a subtype of Tk: it has Tk's layout trivially prefix it, 
 converts to Tk implicitly, yet it is a distinct type.

 Let's see this typechecking at work:

 1. Accessing a field:

 typeof(s.ptr) at(const char[] s, uint i) { return s.ptr + i; }

 Typecheck with a typedef for const char[], yielding:

 typedef const(char[]) __Surrogate;
 typeof(__Surrogate.init.ptr) at(__Surrogate s, uint i)
 { return s.ptr + i; }

 Pass.

 2. Returning (part of) an argument:

 typeof(s) stripl(const(char)[] s)
 {
     uint i;
     ...
     return s[i .. $];
 }

 Typecheck like this:

 typedef const(char)[] __Surrogate;
 __Surrogate stripl(__Surrogate s)
 {
     uint i;
     ...
     return s[i .. $];
 }

 Fail. I think that reveals a bug in the compiler. A slice of a typedef'd 
 array should return the same type as the array itself. I'll file a bug.

 3. Walter's example:

 class Base {}
 class Derived : Base {}

 typeof(a) foo(Base a) { return new Base; }

 Typecheck like this:

 typedef Base __Surrogate;
 __Surrogate foo(__Surrogate a)
 {
     return new Base;
 }

 Fail, as it should.


 Come at it with all you've got.

I think there is a problem with a simple typedef. The problem is that all the fields and functions do not have an equivalent typedef, only the outer type has a typedef. What you need is a 'deep' typedef, or one that typedefs all the contained types for all the fields/functions to ensure that a piece of another argument that is not protected by the typeof() construct is not returned. So what we really need is a type modifier, like const. Here is an example: typedef const(char[]) __Surrogate; typeof(__Surrogate.init.ptr) foo(__Surrogate s, uint i, const(char)[] s2) { return s2.ptr + i; } In your proposed notation, this would look like: typeof(s.ptr) foo(const(char)[] s, uint i, const(char)[] s2) This should fail to compile, but it doesn't (tested with 2.019). You can cause problems like this: char[] test = "test".dup; char *testptr = foo(test, 0, "TEST"); Now testptr points to the beginning of the invariant string "TEST". But your usage of typedef is very good! I never thought of it that way when designing the scoped const proposal. BTW, I didn't say it in my other reply, but I would definitely use something like this. -Steve
Oct 14 2008
prev sibling parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Andrei Alexandrescu wrote:
 3. Walter's example:
 
 class Base {}
 class Derived : Base {}
 
 typeof(a) foo(Base a) { return new Base; }
 
 Typecheck like this:
 
 typedef Base __Surrogate;
 __Surrogate foo(__Surrogate a)
 {
     return new Base;
 }
 
 Fail, as it should.
 
 
 Come at it with all you've got.

What does 'return new typeof(a);' do? :)
Oct 15 2008
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Many functions return one of their parameters regardless of the way it was 
 qualified.

 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);

 Stripl is not a particularly good example because it needs to work on 
 wchar and dchar too, but let's ignore that aspect for now.

 There's been several proposals in this group on tackling that problem.

 In unrelated proposals and discussions, people mentioned the need for 
 functions that return the exact type of this:

 class A { A clone(); }
 class B : A { B clone(); }

 How can we declare A.clone such that all of its derived classes have it 
 return their own type?

 It took me a while to realize they are really very related. This is easy 
 to figure out if you think that invariant(char)[] and char[] are subtypes 
 of const(char)[]!

 I discussed with Walter a variant that implements equivariant functions 
 without actually adding an explicit feature to the language. Consider:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an argument. I 
 propose that that pattern means stripl can accept _any_ subtype of 
 const(char)[] and return that exact type. Inside the function, however, 
 the type of s is the type declared, thus restricting its use.

 I need to convince myself that function bodies of this type can be 
 reliably typechecked, but first I wanted to run it by everyone to get a 
 feel of it.

 Equivariant functions are not (necessarily) templates and can be used as 
 virtual functions. Only one body is generated for one equivariant 
 function, unless other template mechanisms are in vigor.

 Here are some examples:

 a) Simple equivariance

 typeof(s) stripl(const(char)[] s);

 b) Parameterized equivariance

 typeof(s) stripl(S)(S s) if (isSomeString!S);

 c) Equivariance of field:

 typeof(s.ptr) getpointer(const(char)[] s);

 d) Equivariance inside a class/struct declaration:

 class S
 {
     typeof(this) clone();
     typeof(this.field) getfield();
     int field;
 }

 What do you think? I'm almost afraid to post this.

I'm glad you are looking into this. This is along the same vein as what I called 'scoped const' (see http://d.puremagic.com/issues/show_bug.cgi?id=1961), but I only addressed const variance. Just in terms of const, I have a case where your solution doesn't help/work. Easiest case is min/max: typeof(v1) min(T)(const(T) v1, const(T) v2) { if(v1 < v2) return v1; return v2;} Now, imagine you call it like this: char[] best = "bb".dup; best = min(aa, "aa"); If the function and usage are allowed to compile, then this results in best, being a char[], to be pointing to an invariant(char)[]. There are two problems that need to be solved here. First, you need another const type. One that is treated as const, but is implicitly castable back to the argument type, and can't be implicitly casted to. That type modifier needs to be perpetuated throughout the function, because you shouldn't be able to return things that didn't originate from the input. If I create a temporary variable, I should have to use this modifier to declare the variable (in my proposal, at the suggestion of Janice, I used 'inout', a dead keyword). This guarantees that your output is a subset of the input. An example of how min looks in my proposal (with the proposed 'inout' keyword): inout(T) min(T)(inout(T) v1, inout(T) v2) The second problem is, you want the return type to be dependent on both v1 and v2. In my proposal, the return type could depend on multiple arguments, and if they varied by constancy, the return type was defaulted to const, as this is the only possible type that anything can be casted to. You might be tempted to do something like this: typeof(v1) min(T)(const(T) v1, typeof(v1) v2) But then if you pass in the mutable version as the first, the function fails to compile in the usage I have above. In terms of the auto casting to derived types (not just const), I have to get my head around it before I can think of possible counter cases, but const is definitely broken by your solution (or at least, the solution doesn't handle all cases). -Steve
Oct 13 2008
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Steven Schveighoffer" wrote
 You might be tempted to do something like this:

 typeof(v1) min(T)(const(T) v1, typeof(v1) v2)

 But then if you pass in the mutable version as the first, the function 
 fails to compile in the usage I have above.

d'oh! A valid solution should fail to compile that usage ;) I meant this usage (which should compile). const(char)[] x = "bb"; x = min("aa", x); -Steve
Oct 13 2008
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I'm glad you are looking into this.  This is along the same vein as what I 
 called 'scoped const' (see 
 http://d.puremagic.com/issues/show_bug.cgi?id=1961), but I only addressed 
 const variance.
 
 Just in terms of const, I have a case where your solution doesn't help/work.
 
 Easiest case is min/max:
 
 typeof(v1) min(T)(const(T) v1, const(T) v2) { if(v1 < v2) return v1; return 
 v2;}
 
 Now, imagine you call it like this:
 
 char[] best = "bb".dup;
 best = min(aa, "aa");
 
 If the function and usage are allowed to compile, then this results in best, 
 being a char[], to be pointing to an invariant(char)[].

The function doesn't compile per the typechecking I discussed in a different post.
 There are two problems that need to be solved here.  First, you need another 
 const type.  One that is treated as const, but is implicitly castable back 
 to the argument type, and can't be implicitly casted to.  That type modifier 
 needs to be perpetuated throughout the function, because you shouldn't be 
 able to return things that didn't originate from the input.  If I create a 
 temporary variable, I should have to use this modifier to declare the 
 variable (in my proposal, at the suggestion of Janice, I used 'inout', a 
 dead keyword).  This guarantees that your output is a subset of the input.

I think the typedef-based approach could work (and be that type you mention).
 An example of how min looks in my proposal (with the proposed 'inout' 
 keyword):
 
 inout(T) min(T)(inout(T) v1, inout(T) v2)
 
 The second problem is, you want the return type to be dependent on both v1 
 and v2.  In my proposal, the return type could depend on multiple arguments, 
 and if they varied by constancy, the return type was defaulted to const, as 
 this is the only possible type that anything can be casted to.
 
 You might be tempted to do something like this:
 
 typeof(v1) min(T)(const(T) v1, typeof(v1) v2)
 
 But then if you pass in the mutable version as the first, the function fails 
 to compile in the usage I have above.

My solution does theoretically support multiple types by including them in the typeof expression. Walter, however, mentioned potential difficulties with implementing them. Andrei
Oct 13 2008
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I'm glad you are looking into this.  This is along the same vein as what 
 I called 'scoped const' (see 
 http://d.puremagic.com/issues/show_bug.cgi?id=1961), but I only addressed 
 const variance.

 Just in terms of const, I have a case where your solution doesn't 
 help/work.

 Easiest case is min/max:

 typeof(v1) min(T)(const(T) v1, const(T) v2) { if(v1 < v2) return v1; 
 return v2;}

 Now, imagine you call it like this:

 char[] best = "bb".dup;
 best = min(aa, "aa");

 If the function and usage are allowed to compile, then this results in 
 best, being a char[], to be pointing to an invariant(char)[].

The function doesn't compile per the typechecking I discussed in a different post.

I read that, but I didn't understand it, I'll go read it again ;)
 There are two problems that need to be solved here.  First, you need 
 another const type.  One that is treated as const, but is implicitly 
 castable back to the argument type, and can't be implicitly casted to. 
 That type modifier needs to be perpetuated throughout the function, 
 because you shouldn't be able to return things that didn't originate from 
 the input.  If I create a temporary variable, I should have to use this 
 modifier to declare the variable (in my proposal, at the suggestion of 
 Janice, I used 'inout', a dead keyword).  This guarantees that your 
 output is a subset of the input.

I think the typedef-based approach could work (and be that type you mention).

So in your scheme, the following is true? void foo2(const(char)[] arg); typeof(arg) foo3(const(char)[] arg); typeof(arg1) foo(const(char)[] arg1) { arg1 = "foo"; // fails typeof(arg1) arg1copy = arg1; // ok const(char)[] arg2 = arg1; // ok foo2(arg1); // ok arg1copy = foo3(arg1); // ok return arg1copy; // ok } If that's the case, then the scheme is similar to what I had in mind. I don't really like the typeof(arg1) to use as the type name for temporary variables inside the function, it seems innocuous. Perhaps a special type name can be implicitly declared inside the function?
 An example of how min looks in my proposal (with the proposed 'inout' 
 keyword):

 inout(T) min(T)(inout(T) v1, inout(T) v2)

 The second problem is, you want the return type to be dependent on both 
 v1 and v2.  In my proposal, the return type could depend on multiple 
 arguments, and if they varied by constancy, the return type was defaulted 
 to const, as this is the only possible type that anything can be casted 
 to.

 You might be tempted to do something like this:

 typeof(v1) min(T)(const(T) v1, typeof(v1) v2)

 But then if you pass in the mutable version as the first, the function 
 fails to compile in the usage I have above.

My solution does theoretically support multiple types by including them in the typeof expression. Walter, however, mentioned potential difficulties with implementing them.

OK, so how does min look in your scheme? -Steve
Oct 14 2008
prev sibling next sibling parent reply Benji Smith <dlanguage benjismith.net> writes:
Andrei Alexandrescu wrote:
 What do you think? I'm almost afraid to post this.

It's a lot to swallow all at once, especially if the equivariance applies to all type/subtype relationships, and not just the mutability of a value. Am I reading that right? --benji
Oct 13 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Benji Smith wrote:
 Andrei Alexandrescu wrote:
 What do you think? I'm almost afraid to post this.

It's a lot to swallow all at once, especially if the equivariance applies to all type/subtype relationships, and not just the mutability of a value. Am I reading that right?

Yes. Andrei
Oct 13 2008
parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Andrei Alexandrescu wrote:
 Benji Smith wrote:
 Andrei Alexandrescu wrote:
 What do you think? I'm almost afraid to post this.

It's a lot to swallow all at once, especially if the equivariance applies to all type/subtype relationships, and not just the mutability of a value. Am I reading that right?

Yes.

Are you sure about that? How would this work for interfaces and classes that implement them? Remember: Interface references currently don't point to the object vtable at the beginning of an object but to the interface vptr inside the object. Unless that was changed/is going to change? (For example, interface references could be implemented as "fat pointers" consisting of { object ptr, interface vptr } instead of the current { pointer to interface vptr }, which could make this easier to implement)
Oct 15 2008
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant functions 
 without actually adding an explicit feature to the language. Consider:

 typeof(s) stripl(const(char)[] s);

As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive. I understand the need to not change the language, but I think most would prefer a syntax where the type modifier is specified on at least the argument. People are going to be extremely confused when they can't treat 's' like a normal const(char)[]. If the ultimate result is that no intuitive syntax can be made without changing the language, then I think it is more important to have this feature than to not change the language. One other syntax that Janice proposed (and I later put into a bugzilla), is to use the dead keyword inout. Meaning, what you send in is what you get out. ref already completely replaces inout, so there is no need to keep it under its current meaning: inout(char)[] stripl(inout(char)[] s); I'm not in love with this completely, but it has the benefit of not requiring a new keyword. -Steve
Oct 14 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant functions 
 without actually adding an explicit feature to the language. Consider:

 typeof(s) stripl(const(char)[] s);

As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

I agree. We need to look for a better notation.
 I understand the need to not change the language, but I think most would 
 prefer a syntax where the type modifier is specified on at least the 
 argument.  People are going to be extremely confused when they can't treat 
 's' like a normal const(char)[].
 
 If the ultimate result is that no intuitive syntax can be made without 
 changing the language, then I think it is more important to have this 
 feature than to not change the language.
 
 One other syntax that Janice proposed (and I later put into a bugzilla), is 
 to use the dead keyword inout.  Meaning, what you send in is what you get 
 out.  ref already completely replaces inout, so there is no need to keep it 
 under its current meaning:
 
 inout(char)[] stripl(inout(char)[] s);
 
 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei
Oct 14 2008
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant functions 
 without actually adding an explicit feature to the language. Consider:

 typeof(s) stripl(const(char)[] s);

As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

I agree. We need to look for a better notation.
 I understand the need to not change the language, but I think most would 
 prefer a syntax where the type modifier is specified on at least the 
 argument.  People are going to be extremely confused when they can't 
 treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without 
 changing the language, then I think it is more important to have this 
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a bugzilla), 
 is to use the dead keyword inout.  Meaning, what you send in is what you 
 get out.  ref already completely replaces inout, so there is no need to 
 keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);

In this last example, what does the inout mean at the front?
 I think this could work and doesn't look half bad. Of course, you'll be 
 tasked with addressing protests about yet another D1/D2 incompatibility. 
 :o)

inout is obsolete in D1 also. The answer to the protest is 'change inout to ref.' At some point, you can make inout invalid in D1, so it doesn't have a double meaning. Not a big deal IMO ;) -Steve
Oct 14 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant functions 
 without actually adding an explicit feature to the language. Consider:

 typeof(s) stripl(const(char)[] s);

find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

 I understand the need to not change the language, but I think most would 
 prefer a syntax where the type modifier is specified on at least the 
 argument.  People are going to be extremely confused when they can't 
 treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without 
 changing the language, then I think it is more important to have this 
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a bugzilla), 
 is to use the dead keyword inout.  Meaning, what you send in is what you 
 get out.  ref already completely replaces inout, so there is no need to 
 keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);

In this last example, what does the inout mean at the front?

Accept and return any subtype of Base. Andrei
Oct 14 2008
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant 
 functions without actually adding an explicit feature to the language. 
 Consider:

 typeof(s) stripl(const(char)[] s);

can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

 I understand the need to not change the language, but I think most 
 would prefer a syntax where the type modifier is specified on at least 
 the argument.  People are going to be extremely confused when they 
 can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without 
 changing the language, then I think it is more important to have this 
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a 
 bugzilla), is to use the dead keyword inout.  Meaning, what you send in 
 is what you get out.  ref already completely replaces inout, so there 
 is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);

In this last example, what does the inout mean at the front?

Accept and return any subtype of Base.

so in this case, inout is equivalent to inout(Base)? Definitely a nice idea, since it can always be implied. But this makes it look very weird if the function is a member: class X { inout inout foo(); } Where the first inout is a modifier on the 'this' argument, and the second inout is equivalent to inout(X). Unless Walter is ok with abolishing the ability to put this modifiers at the front of member functions? :D -Steve
Oct 14 2008
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Simen Kjaeraas wrote:
 I don't really see this as a problem. Returning mutable or invariant 
 would be worse. Anyways, if you need the two arguments to be of the same 
 type, I'd prefer this syntax:
 
   inout min(inout(A) a1, typeof(a1) a2){}

IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei
Oct 14 2008
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Denis Koroskin wrote:
 On Tue, 14 Oct 2008 23:45:32 +0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:
 
 Simen Kjaeraas wrote:
 I don't really see this as a problem. Returning mutable or invariant 
 would be worse. Anyways, if you need the two arguments to be of the 
 same type, I'd prefer this syntax:
    inout min(inout(A) a1, typeof(a1) a2){}

IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei

IMHO, the solution should be consistent and general enough to cover templates, too, as well as zero, single and multiple input arguments. Templates would benefit from it, too, reducing the generated file size (there are many complains about this issue).

If you can find a solution that is simple and general enough, my hat is off to you. Andrei
Oct 14 2008
prev sibling parent Robert Fraser <fraserofthenight gmail.com> writes:
Denis Koroskin wrote:
 On Tue, 14 Oct 2008 22:38:58 +0400, Simen Kjaeraas 
 <simen.kjaras gmail.com> wrote:
 
 On Tue, 14 Oct 2008 18:51:56 +0200, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:

 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant 
 functions without actually adding an explicit feature to the 
 language. Consider:

 typeof(s) stripl(const(char)[] s);

can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

 I understand the need to not change the language, but I think most 
 would prefer a syntax where the type modifier is specified on at 
 least the argument.  People are going to be extremely confused 
 when they can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made 
 without changing the language, then I think it is more important 
 to have this feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a 
 bugzilla), is to use the dead keyword inout.  Meaning, what you 
 send in is what you get out.  ref already completely replaces 
 inout, so there is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of 
 not requiring a new keyword.

class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);


Accept and return any subtype of Base. Andrei

I was first wondering how this would treat things like inout foo(Base b, int n); But then I noticed it saying "inout Base b", so only the argument(s) marked inout are considered for the return type?

Yes
 Also, this means a maximum of one
 inout argument type per function, right?

No, there were many example of multiple (and even none) inout function arguments.
 As for the keyword, I feel inout is better than typeof, and good 
 enough. Not perfect, but good enough.

My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.

Er.... isn't that the point. If I pass two mutable arguments to min(), I want a mutable back. If I pass two invariant (immutable, hopefully!) arguments to min() I want an invariant back. Otherwise, I want a const back. That's why this syntax is needed at all.
Oct 14 2008
prev sibling next sibling parent reply "Bill Baxter" <wbaxter gmail.com> writes:
On Wed, Oct 15, 2008 at 2:31 PM, Denis Koroskin <2korden gmail.com> wrote:
 On Wed, 15 Oct 2008 05:30:52 +0400, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 On Tue, 14 Oct 2008 23:45:32 +0400, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 Simen Kjaeraas wrote:
 I don't really see this as a problem. Returning mutable or invariant
 would be worse. Anyways, if you need the two arguments to be of the same
 type, I'd prefer this syntax:
   inout min(inout(A) a1, typeof(a1) a2){}

IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei

templates, too, as well as zero, single and multiple input arguments. Templates would benefit from it, too, reducing the generated file size (there are many complains about this issue).

If you can find a solution that is simple and general enough, my hat is off to you. Andrei

We are here to discuss it. I made many suggestions, but I don't know whether they fine, too complex or just lame untill someone comments it (interesting enough, some reply without reading). What's wrong with typeof(this) (I just generalized your clone() idea combined with implicit upcasting suggested by Steven)? The "I don't like it" comment would be useful, too. What's wrong with inout/whatever? So far you brought just one 'problematic' example: inout(C) foo(inout(B) function(inout(A)) fn); I believe this is as meaningless as inout(A) foo(); // what does it return? A, const(A) or invariant(A)? and thus should be statically disallowed. I mean, inout(return) doesn't have any sense unless a function accepts some inout(parameter): inout(B) foo(inout(A) a); // ok // your example with an added inout(in) parameter. Now it is fine inout(C) foo(inout(C) c, inout(B) function(inout(A) a) fn); In the last example, constancy of return value matches constancy of input parameter.

.. and inout(B), inout(A) there have that same constancy? Or is their constancy unrelated? --bb
Oct 14 2008
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Bill Baxter" wrote
 On Wed, Oct 15, 2008 at 2:31 PM, Denis Koroskin <2korden gmail.com> wrote:
 On Wed, 15 Oct 2008 05:30:52 +0400, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 On Tue, 14 Oct 2008 23:45:32 +0400, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 Simen Kjaeraas wrote:
 I don't really see this as a problem. Returning mutable or invariant
 would be worse. Anyways, if you need the two arguments to be of the 
 same
 type, I'd prefer this syntax:
   inout min(inout(A) a1, typeof(a1) a2){}

IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei

templates, too, as well as zero, single and multiple input arguments. Templates would benefit from it, too, reducing the generated file size (there are many complains about this issue).

If you can find a solution that is simple and general enough, my hat is off to you. Andrei

We are here to discuss it. I made many suggestions, but I don't know whether they fine, too complex or just lame untill someone comments it (interesting enough, some reply without reading). What's wrong with typeof(this) (I just generalized your clone() idea combined with implicit upcasting suggested by Steven)? The "I don't like it" comment would be useful, too. What's wrong with inout/whatever? So far you brought just one 'problematic' example: inout(C) foo(inout(B) function(inout(A)) fn); I believe this is as meaningless as inout(A) foo(); // what does it return? A, const(A) or invariant(A)? and thus should be statically disallowed. I mean, inout(return) doesn't have any sense unless a function accepts some inout(parameter): inout(B) foo(inout(A) a); // ok // your example with an added inout(in) parameter. Now it is fine inout(C) foo(inout(C) c, inout(B) function(inout(A) a) fn); In the last example, constancy of return value matches constancy of input parameter.

.. and inout(B), inout(A) there have that same constancy? Or is their constancy unrelated?

inout is a type modifier. At the time you call foo, inout(B) and inout(A) are not resolved to anything yet. But when you call fn, that is when the compiler determines what inout should resolve to. Like any signature, it is telling you what the type of the parameters will be during the fn function. However, the special inout modifier tells the compiler it is safe to cast back to the original parameter constancy (or in the case of mutilple parameters, the greatest common constancy). -Steve
Oct 15 2008
prev sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Wed, 15 Oct 2008 09:51:59 +0400, Bill Baxter <wbaxter gmail.com> wrote:

 inout(C) foo(inout(C) c, inout(B) function(inout(A) a) fn);

 In the last example, constancy of return value matches constancy of  
 input
 parameter.

.. and inout(B), inout(A) there have that same constancy? Or is their constancy unrelated? --bb

To answer this I need to take a use case and look at a concrete function implementation: Case #1 ------- void testInoutFunction(inout(char[]) function(inout(char)[]) fn) { char[] a1 = "hello".dup; a1 = fn(a1); writefln(a1); const(char)[] a2 = "hello"; a2 = b(a2); writefln(a2); invariant(char)[] a3 = "hello"; a3 = b(a3); writefln(a3); } inout(char)[] foo(inout(char)[] a); testInoutFunction(&foo); I think this is a valid use case. Note that testInoutFunction doesn't take any inout() params and the inout() constancy expantion doesn't take place at the call-time, i.e. void testInoutFunction(inout(char[]) function(inout(char)[]) fn) doesn't transform into one of these: void testInoutFunction(char[] function(char[]) fn); void testInoutFunction(const(char[]) function(const(char)[]) fn); void testInoutFunction(invariant(char[]) function(invariant(char)[]) fn); Adding an additional parameter doesn't change a bit (function shouldn't change its behaviour drastically given additional parameter): inout(char)[] testInoutFunction(inout(char)[] a, inout(char[]) function(inout(char)[]) fn) { testInoutFunction(fn); // call zero-arg version // do something with a return a[1..$]; } So a rule here is that inout() constancy expansion doesn't take place for delegate and function pointers. Case #2 ------- In this case, inout() constancy expantion does take place on delegates and function pointers. Let's see if it is useful: char[] foo1(char[] a); invariant(char)[] foo2(invariant(char)[] a); void testInoutFunction(inout(char[]) function(inout(char)[]) fn); testInoutFunction(&foo1); // assume, we wanna call like this testInoutFunction(&foo2); // and like this Given these uses cases, what assumptions can testInoutFunction make and actions take? void testInoutFunction(inout(char[]) function(inout(char)[]) fn) { char[] a = "hello".dup; a = fn(a); // invalid, violates foo2 typechecking invariant(char)[] b = "hello"; b = fn(b); // invalid, violates foo1 typechecking const(char)[] c = "hello"; c = fn(c); // ok } See, testInoutFunction's actually can't do much. In fact it is no different from void testInoutFunction(const(char)[] function(char(char)[]) fn); So the second use case is in fact incorrect and should not be allowed. Back to the original example:
 inout(C) foo(inout(C) c, inout(B) function(inout(A) a) fn);

Type of return value here depends on type of first argument (c), type of fn doesn't vary, it is always the same.
Oct 15 2008
prev sibling next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
Andrei Alexandrescu wrote:
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant 
 functions without actually adding an explicit feature to the 
 language. Consider:

 typeof(s) stripl(const(char)[] s);

As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

I agree. We need to look for a better notation.
 I understand the need to not change the language, but I think most 
 would prefer a syntax where the type modifier is specified on at least 
 the argument.  People are going to be extremely confused when they 
 can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without 
 changing the language, then I think it is more important to have this 
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a 
 bugzilla), is to use the dead keyword inout.  Meaning, what you send 
 in is what you get out.  ref already completely replaces inout, so 
 there is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei

I'm afraid the meaning of inout here is very unclear without explanation.
Oct 14 2008
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"KennyTM~" wrote
 Andrei Alexandrescu wrote:
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant functions 
 without actually adding an explicit feature to the language. Consider:

 typeof(s) stripl(const(char)[] s);

As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

I agree. We need to look for a better notation.
 I understand the need to not change the language, but I think most would 
 prefer a syntax where the type modifier is specified on at least the 
 argument.  People are going to be extremely confused when they can't 
 treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without 
 changing the language, then I think it is more important to have this 
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a bugzilla), 
 is to use the dead keyword inout.  Meaning, what you send in is what you 
 get out.  ref already completely replaces inout, so there is no need to 
 keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei

I'm afraid the meaning of inout here is very unclear without explanation.

You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -Steve
Oct 14 2008
next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
Denis Koroskin wrote:
 On Tue, 14 Oct 2008 21:04:01 +0400, Steven Schveighoffer 
 <schveiguy yahoo.com> wrote:
 
 "KennyTM~" wrote
 Andrei Alexandrescu wrote:
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant 
 functions
 without actually adding an explicit feature to the language. 
 Consider:

 typeof(s) stripl(const(char)[] s);

As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

I agree. We need to look for a better notation.
 I understand the need to not change the language, but I think most 
 would
 prefer a syntax where the type modifier is specified on at least the
 argument.  People are going to be extremely confused when they can't
 treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without
 changing the language, then I think it is more important to have this
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a 
 bugzilla),
 is to use the dead keyword inout.  Meaning, what you send in is 
 what you
 get out.  ref already completely replaces inout, so there is no 
 need to
 keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not
 requiring a new keyword.

Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei



I think that basic idea is very good! (for some reason I was bound to constness issue and didn't think about the latter example).
 I'm afraid the meaning of inout here is very unclear without 
 explanation.

You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -Steve

I have previously proposed the 'same' keyword: same(Base) foo(same(Base) bar) { bar.callSomeBaseMethod(); return bar; } foo(new Derived()); interface IClonable { same(this) clone(); } It is short and very descriptive. Frankly speaking, I don't see the difference between inout or some other keyword. You either ditch inout and introduce FOO or obsolete inout and reuse it. Both have similar effect: some code is broken and needs fixing (because inout doesn't work anymore), a new keyword is introduced to denote the feature. The only difference is a possible name collision that any new keyword might introduce. But it is nothing to worry about, because short and meaningful keywords are more important than some potentially broken (and easily fixable) code for the language in a long run.

“same” seems to be a common identifier though... “inout” already has an equivalent keyword. It is called “ref”.
Oct 14 2008
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Denis Koroskin" wrote
 On Tue, 14 Oct 2008 22:16:33 +0400, KennyTM~ <kennytm gmail.com> wrote:

 "same" seems to be a common identifier though..

 "inout" already has an equivalent keyword. It is called "ref".

Yeah, I know :) Thanks anyway. But is there any reason to worry about name collision? I just grep'd about 20Mb of C++ source code and didn't find *a single* usage of `same' outside of the comments.

I'd say if we were to add a keyword, same would be on the list of choices, I can't think of a common usage for it. But you can't deny that in general using a commonly used symbol as a keyword might be a bad idea. For example 'get' or 'next'. So yes, when adding a keyword, one should be wary of how it affects commonly used keywords, and yes, 'same' is a good choice in that regard ;) For example, when porting Tango to D2, I had to change about 5 or so singleton members (and their uses) named 'shared' since it is now a keyword. -Steve
Oct 14 2008
prev sibling next sibling parent KennyTM~ <kennytm gmail.com> writes:
Denis Koroskin wrote:
 On Tue, 14 Oct 2008 22:16:33 +0400, KennyTM~ <kennytm gmail.com> wrote:
 
 “same” seems to be a common identifier though..

 “inout” already has an equivalent keyword. It is called “ref”.

Yeah, I know :) Thanks anyway. But is there any reason to worry about name collision? I just grep'd about 20Mb of C++ source code and didn't find *a single* usage of `same' outside of the comments.

Googled "filetype:cpp same" and got ≥1 obscure results: void series::init() { hide = false; fl_sr2 = same; fl_ga = same; fl_gb = same; // -snip- } (http://evgenii.rudnyi.ru/soft/varcomp/varcomp/sumsqrut.cpp) But no result so far in D. Probably a safe choice after all, but to me too un-keyword-like :p.
Oct 14 2008
prev sibling parent ore-sama <spam here.lot> writes:
Denis Koroskin Wrote:

 But is there any reason to worry about name collision? I just grep'd about  
 20Mb of C++ source code and didn't find *a single* usage of `same' outside  
 of the comments.

google codesearch says that "same" is used in mozilla code and "inout" - in audacity code.
Oct 15 2008
prev sibling parent reply KennyTM~ <kennytm gmail.com> writes:
Steven Schveighoffer wrote:
 "KennyTM~" wrote
 Andrei Alexandrescu wrote:
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant functions 
 without actually adding an explicit feature to the language. Consider:

 typeof(s) stripl(const(char)[] s);

find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

 I understand the need to not change the language, but I think most would 
 prefer a syntax where the type modifier is specified on at least the 
 argument.  People are going to be extremely confused when they can't 
 treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without 
 changing the language, then I think it is more important to have this 
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a bugzilla), 
 is to use the dead keyword inout.  Meaning, what you send in is what you 
 get out.  ref already completely replaces inout, so there is no need to 
 keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei


You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -Steve

Sorry, I can't think of an existing keyword that can clearly qualify the purpose in this syntax (“stuff(Type) f(stuff(Type) s)”) either. By the explanation argument, I actually mean what a programmer first sees this feature think of. Presented with Andrei's “typeof” syntax, I can at least guess what the function looks like (it returns the same type as “s”, OK); but with “inout” I just stopped with "What the heck is going on?!".
Oct 14 2008
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"KennyTM~" wrote
 Steven Schveighoffer wrote:
 "KennyTM~" wrote
 Andrei Alexandrescu wrote:
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant 
 functions without actually adding an explicit feature to the 
 language. Consider:

 typeof(s) stripl(const(char)[] s);

can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

 I understand the need to not change the language, but I think most 
 would prefer a syntax where the type modifier is specified on at least 
 the argument.  People are going to be extremely confused when they 
 can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without 
 changing the language, then I think it is more important to have this 
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a 
 bugzilla), is to use the dead keyword inout.  Meaning, what you send 
 in is what you get out.  ref already completely replaces inout, so 
 there is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei

explanation.

You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -Steve

Sorry, I can't think of an existing keyword that can clearly qualify the purpose in this syntax ("stuff(Type) f(stuff(Type) s)") either.

I meant a new keyword. There aren't many existing keywords that I'd want to double the meaning of for this (including typeof). inout is advantageous because its current meaning is exactly covered by another keyword.
 By the explanation argument, I actually mean what a programmer first sees 
 this feature think of. Presented with Andrei's "typeof" syntax, I can at 
 least guess what the function looks like (it returns the same type as "s", 
 OK)

Then you failed to see the true meaning of Andrei's syntax ;) It means that the return value is the same as what you used as s to call the function, not the type of s (which is whatever the function declares s as). The return type changes depending on what you call it with. e.g.: typeof(s) foo(const(char)[] s); ... char[] x = "hello".dup; auto x2 = foo(x); // x2 is typed as char[], even though s is declared as const(char)[]
 ; but with "inout" I just stopped with "What the heck is going on?!".

At least you are not guessing the wrong thing. This would probably prompt you to look up how inout works in the docs. I'm not saying that inout is the ideal keyword. I'm saying that this is a novel enough concept that probably people are not going to intuitively see what is going on no matter what keyword is used. They have to learn what it's doing by reading docs. Using typeof as Andrei defined seems intuitively to mean something entirely different, and most likely will not prompt a lookup of the docs (until it doesn't behave as expected). Plus it doubles the meaning of yet another keyword, which I hate... e.g. what did you think was happening when you first saw 'mixin'? Did you intuitively think 'oh, this must take what I give it as a compile-time generated string and compile it into actual code'. Probably not, so is mixin a bad keyword choice for that feature because you didn't intuitively think of it? -Steve
Oct 14 2008
next sibling parent KennyTM~ <kennytm gmail.com> writes:
Steven Schveighoffer wrote:
 "KennyTM~" wrote
 Steven Schveighoffer wrote:
 "KennyTM~" wrote
 Andrei Alexandrescu wrote:
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant 
 functions without actually adding an explicit feature to the 
 language. Consider:

 typeof(s) stripl(const(char)[] s);

can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

 I understand the need to not change the language, but I think most 
 would prefer a syntax where the type modifier is specified on at least 
 the argument.  People are going to be extremely confused when they 
 can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without 
 changing the language, then I think it is more important to have this 
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a 
 bugzilla), is to use the dead keyword inout.  Meaning, what you send 
 in is what you get out.  ref already completely replaces inout, so 
 there is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei

explanation.

output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -Steve

purpose in this syntax ("stuff(Type) f(stuff(Type) s)") either.

I meant a new keyword. There aren't many existing keywords that I'd want to double the meaning of for this (including typeof). inout is advantageous because its current meaning is exactly covered by another keyword.
 By the explanation argument, I actually mean what a programmer first sees 
 this feature think of. Presented with Andrei's "typeof" syntax, I can at 
 least guess what the function looks like (it returns the same type as "s", 
 OK)

Then you failed to see the true meaning of Andrei's syntax ;) It means that the return value is the same as what you used as s to call the function, not the type of s (which is whatever the function declares s as). The return type changes depending on what you call it with. e.g.: typeof(s) foo(const(char)[] s); ... char[] x = "hello".dup; auto x2 = foo(x); // x2 is typed as char[], even though s is declared as const(char)[]

Yeah I know. It may better be called sth like typeof callsite(s) :)
 ; but with "inout" I just stopped with "What the heck is going on?!".

At least you are not guessing the wrong thing. This would probably prompt you to look up how inout works in the docs.

That could be an advantage. We've different viewpoints :)
 I'm not saying that inout is the ideal keyword.  I'm saying that this is a 
 novel enough concept that probably people are not going to intuitively see 
 what is going on no matter what keyword is used.  They have to learn what 
 it's doing by reading docs.  Using typeof as Andrei defined seems 
 intuitively to mean something entirely different, and most likely will not 
 prompt a lookup of the docs (until it doesn't behave as expected).  Plus it 
 doubles the meaning of yet another keyword, which I hate...
 
 e.g. what did you think was happening when you first saw 'mixin'?  Did you 
 intuitively think 'oh, this must take what I give it as a compile-time 
 generated string and compile it into actual code'.  Probably not, so is 
 mixin a bad keyword choice for that feature because you didn't intuitively 
 think of it?
 

I'd use eval() if I designed the language :p (Though this also confuses user as eval() is more used in runtime code evaluation)
 -Steve 
 
 

Oct 14 2008
prev sibling parent David Gileadi <foo bar.com> writes:
Steven Schveighoffer wrote:
 "KennyTM~" wrote

 By the explanation argument, I actually mean what a programmer first sees 
 this feature think of. Presented with Andrei's "typeof" syntax, I can at 
 least guess what the function looks like (it returns the same type as "s", 
 OK)

Then you failed to see the true meaning of Andrei's syntax ;) It means that the return value is the same as what you used as s to call the function, not the type of s (which is whatever the function declares s as). The return type changes depending on what you call it with. e.g.: typeof(s) foo(const(char)[] s); .... char[] x = "hello".dup; auto x2 = foo(x); // x2 is typed as char[], even though s is declared as const(char)[]
 ; but with "inout" I just stopped with "What the heck is going on?!".

At least you are not guessing the wrong thing. This would probably prompt you to look up how inout works in the docs. I'm not saying that inout is the ideal keyword. I'm saying that this is a novel enough concept that probably people are not going to intuitively see what is going on no matter what keyword is used. They have to learn what it's doing by reading docs. Using typeof as Andrei defined seems intuitively to mean something entirely different, and most likely will not prompt a lookup of the docs (until it doesn't behave as expected). Plus it doubles the meaning of yet another keyword, which I hate... e.g. what did you think was happening when you first saw 'mixin'? Did you intuitively think 'oh, this must take what I give it as a compile-time generated string and compile it into actual code'. Probably not, so is mixin a bad keyword choice for that feature because you didn't intuitively think of it? -Steve

FWIW I agree with KennyTM~ here--typeof(s) made sense to me, including its meaning that you describe above. This, even though it conflicts with the keyword's current meaning. I suppose it's because "typeof(s)" could stand for "some type of s", i.e. some child type of s. The thing that concerns me about using typeof(s) is your earlier comment:
 I understand the need to not change the language, but I think most 
 would prefer a syntax where the type modifier is specified on at least 
 the argument.  People are going to be extremely confused when they 
 can't treat 's' like a normal const(char)[].

-Dave
Oct 14 2008
prev sibling parent Christopher Wright <dhasenan gmail.com> writes:
KennyTM~ wrote:
 Sorry, I can't think of an existing keyword that can clearly qualify the 
 purpose in this syntax (“stuff(Type) f(stuff(Type) s)”) either.
 
 By the explanation argument, I actually mean what a programmer first 
 sees this feature think of. Presented with Andrei's “typeof” syntax, I 
 can at least guess what the function looks like (it returns the same 
 type as “s”, OK); but with “inout” I just stopped with "What the heck
is 
 going on?!".

I can't: typeof(s) foo(const(char)[] s) This looks like it's shorthand for: const(char)[] foo(const(char)[] s) And the examples with typeof(A), where A is a type, just didn't make any sense.
Oct 14 2008
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
KennyTM~ wrote:
 Andrei Alexandrescu wrote:
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant 
 functions without actually adding an explicit feature to the 
 language. Consider:

 typeof(s) stripl(const(char)[] s);

As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

I agree. We need to look for a better notation.
 I understand the need to not change the language, but I think most 
 would prefer a syntax where the type modifier is specified on at 
 least the argument.  People are going to be extremely confused when 
 they can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made 
 without changing the language, then I think it is more important to 
 have this feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a 
 bugzilla), is to use the dead keyword inout.  Meaning, what you send 
 in is what you get out.  ref already completely replaces inout, so 
 there is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei

I'm afraid the meaning of inout here is very unclear without explanation.

I think more explanation is needed for the other case: inout(char)[] stripl(inout(char)[] s); What if I pass a const char[]? Is that going to work? In contrast, this is simple: inout stripl(inout const(char)[] s); Accept and return any subtype of const(char)[]. Andrei
Oct 14 2008
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 KennyTM~ wrote:
 Andrei Alexandrescu wrote:
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant 
 functions without actually adding an explicit feature to the language. 
 Consider:

 typeof(s) stripl(const(char)[] s);

As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

I agree. We need to look for a better notation.
 I understand the need to not change the language, but I think most 
 would prefer a syntax where the type modifier is specified on at least 
 the argument.  People are going to be extremely confused when they 
 can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without 
 changing the language, then I think it is more important to have this 
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a 
 bugzilla), is to use the dead keyword inout.  Meaning, what you send in 
 is what you get out.  ref already completely replaces inout, so there 
 is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei

I'm afraid the meaning of inout here is very unclear without explanation.

I think more explanation is needed for the other case: inout(char)[] stripl(inout(char)[] s); What if I pass a const char[]? Is that going to work? In contrast, this is simple: inout stripl(inout const(char)[] s); Accept and return any subtype of const(char)[].

I like this a lot better. What about returning a member? i.e.: inout(typeof(s.ptr)) getptr(inout const(char)[] s) { return s.ptr;} is that what you had in mind? this syntax is going to be used often, since it's what you would use for an accessor. So it should be simple to understand if possible. -Steve
Oct 14 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 What about returning a member?  i.e.:
 
 inout(typeof(s.ptr)) getptr(inout const(char)[] s) { return s.ptr;}

Yah there is a recurring problem - we need to find a notation that works nicely and expressively for member functions as well as free functions. For free functions an easy-to-explain-and-understand way is to have "inout" mark the incoming / outgoing TYPE entirely, not only its qualifier. Then: inout stripl(inout const(char)[] s); means: accept any subtype of const(char)[] and call that type "inout" in the return type. Then it's easy to access dependent stuff: typeof(inout.ptr) at(inout const(char)[] s); Notice that for one-parameter functions there's not even a need to specify the inout in the argument list because it's unambiguous: inout stripl(const(char)[] s); typeof(inout.ptr) at(const(char)[] s); When you get into member functions things aren't quite nice: class A { private int a; inout clone() inout; // ehm typeof(&inout.a) getPtrToA() inout; // ehm }
 is that what you had in mind?
 
 this syntax is going to be used often, since it's what you would use for an 
 accessor.  So it should be simple to understand if possible.

Yah I agree. At this point I don't have any solid solution for notation... please continue rolling out ideas. Andrei
Oct 14 2008
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 What about returning a member?  i.e.:

 inout(typeof(s.ptr)) getptr(inout const(char)[] s) { return s.ptr;}

Yah there is a recurring problem - we need to find a notation that works nicely and expressively for member functions as well as free functions. For free functions an easy-to-explain-and-understand way is to have "inout" mark the incoming / outgoing TYPE entirely, not only its qualifier. Then: inout stripl(inout const(char)[] s); means: accept any subtype of const(char)[] and call that type "inout" in the return type. Then it's easy to access dependent stuff: typeof(inout.ptr) at(inout const(char)[] s); Notice that for one-parameter functions there's not even a need to specify the inout in the argument list because it's unambiguous: inout stripl(const(char)[] s); typeof(inout.ptr) at(const(char)[] s); When you get into member functions things aren't quite nice: class A { private int a; inout clone() inout; // ehm typeof(&inout.a) getPtrToA() inout; // ehm }
 is that what you had in mind?

 this syntax is going to be used often, since it's what you would use for 
 an accessor.  So it should be simple to understand if possible.

Yah I agree. At this point I don't have any solid solution for notation... please continue rolling out ideas.

Damn, I think there is some confusion here, because we are trying to solve two related, but unequal problems. First is const equivariance (is that the right term?). I want to specify a function that treats an argument as const, but does not affect the const contract that the caller has with that argument (i.e. my original scoped const proposal). The second is type equivariance. I want to specify that I will treat an argument as a base type, but I will not change the derived type that the caller is using. These are similar, if you consider that const is a 'base type' of its mutable or invariant version. But there is one caveat that is hard to explain using this terminology. const is a type modifier, not a type. So it's not really a base type of a type, and it can be applied to any type in existance. Not the same as specifying a base type. So the first requirement (const equivariance) does not require that I return a derivative of the argument, it requires that I return an argument that is part of the input, but contains the same const contract as the passed in variable at the *call site*. It does not require that the return type is derived from the input. It is this first requirement that is the only requirement necessary for functions which return unrelated types from their arguments. i.e., for an accessor, which is not returning something of the same type as its argument, the only thing that is important to keep the same at the call site is the const modifiers. For example, you can return a base type that is automatically converted to a derived type, but that must be accomplished with an overridden function. That is already handled using covariance, and I really don't think there is a way to enforce this in the compiler. An example, a linked list. class Link { private Link next; Link getNext() { return next;} } class DerivedLink : Link { int someExtraData; } We currently solve this by adding a covariant getNext() to DerivedLink. But would it be enough to just change the getNext function in the base type to: inout getNext() { return next; } inout; I don't think it can be enforced. Because some other Link function can set next to another type of Link (possibly a base Link). It is up to the designer of DerivedLink to ensure that next always points to a descendent of DerivedLink. In this case, I think it's a requirement to use covariant functions. However, it *would* be advantageous to declare getNext as not altering the const contract of the caller. That is, it returns the same constancy that it is called with (in this example, inout implies const): inout(Link) getNext() { return next; } inout; For the second requirement, I can see value in things like call-chaining: class C { inout doSomething() inout { return this;} } class D : C { inout doSomethingElse() inout { return this;} } auto d = new D; d.doSomething().doSomethingElse(); However, the return value MUST be exactly the same value as the input. If it's only the same type, we lose all compiler enforcibility. So does it make sense to split these two requirements? I propose to use inout only to deal with const contracts, and use another syntax to signify which parameter will be returned: inout(X) foo(inout(Y) y); // X and Y are unrelated types a stab at 'returning this exact parameter' X foo(return X x); // The return value from foo will be x, so the compiler is free to upcast automatically. inout(X) foo(return inout(X) x); // combination, signifies that I will return x, and I promise not to modify it in the function. With this scheme, I think possibly inout might not be as clear... -Steve
Oct 14 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 These are similar, if you consider that const is a 'base type' of its 
 mutable or invariant version.  But there is one caveat that is hard to 
 explain using this terminology.  const is a type modifier, not a type.  So 
 it's not really a base type of a type, and it can be applied to any type in 
 existance.  Not the same as specifying a base type.

Base type and supertype are not to be confused. By base type I understand you mean you actually type class Super{} and class Sub:Super{}. The subtyping relation is more general and applies to any two types Super and Sub that satisfy a number of constraints. That const(T) is a supertype of both T and invariant(T) is essential and not happenstance. It enshrines the fact that you can always pass a T or an invariant(T) into a function. The fact that there is no difference in layout and that const is not assignable also means that arrays of const(T) are supertypes of both arrays of T and invariant(T), with important consequences. Andrei
Oct 14 2008
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 These are similar, if you consider that const is a 'base type' of its 
 mutable or invariant version.  But there is one caveat that is hard to 
 explain using this terminology.  const is a type modifier, not a type. 
 So it's not really a base type of a type, and it can be applied to any 
 type in existance.  Not the same as specifying a base type.

Base type and supertype are not to be confused. By base type I understand you mean you actually type class Super{} and class Sub:Super{}. The subtyping relation is more general and applies to any two types Super and Sub that satisfy a number of constraints. That const(T) is a supertype of both T and invariant(T) is essential and not happenstance. It enshrines the fact that you can always pass a T or an invariant(T) into a function. The fact that there is no difference in layout and that const is not assignable also means that arrays of const(T) are supertypes of both arrays of T and invariant(T), with important consequences.

But a type invariant(T) is not a subtype of const(U). How do you solve this problem: class U { private T field public T property() { return field;} public invariant(T) property() { return field; } invariant; public const(T) property() {return field; } const; } Because T is completely unrelated to U, so the inout contract only has to do with constancy, not the types. -Steve
Oct 14 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 These are similar, if you consider that const is a 'base type' of its 
 mutable or invariant version.  But there is one caveat that is hard to 
 explain using this terminology.  const is a type modifier, not a type. 
 So it's not really a base type of a type, and it can be applied to any 
 type in existance.  Not the same as specifying a base type.

you mean you actually type class Super{} and class Sub:Super{}. The subtyping relation is more general and applies to any two types Super and Sub that satisfy a number of constraints. That const(T) is a supertype of both T and invariant(T) is essential and not happenstance. It enshrines the fact that you can always pass a T or an invariant(T) into a function. The fact that there is no difference in layout and that const is not assignable also means that arrays of const(T) are supertypes of both arrays of T and invariant(T), with important consequences.

But a type invariant(T) is not a subtype of const(U).

Well I agree. I was just pointing out a minute fact.
 How do you solve this 
 problem:
 
 class U
 {
    private T field
    public T property() { return field;}
    public invariant(T) property() { return field; } invariant;
    public const(T) property() {return field; } const;
 }
 
 Because T is completely unrelated to U, so the inout contract only has to do 
 with constancy, not the types.

Yah, that's where the PassQual template is needed. I'd agree without joy that, if handling true equivariance is too unwieldy, we can resign to solving only equivariance in qualifier. Andrei
Oct 14 2008
next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
Andrei Alexandrescu wrote:
 Yah, that's where the PassQual template is needed. I'd agree without joy 
 that, if handling true equivariance is too unwieldy, we can resign to 
 solving only equivariance in qualifier.

FWIW, people have done without type eqivariance w/o complaint since static typing was invented. People have been complaining about const equivariance since at least C++ and D makes it worse.
Oct 14 2008
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Robert Fraser wrote:
 Andrei Alexandrescu wrote:
 Yah, that's where the PassQual template is needed. I'd agree without 
 joy that, if handling true equivariance is too unwieldy, we can resign 
 to solving only equivariance in qualifier.

FWIW, people have done without type eqivariance w/o complaint since static typing was invented. People have been complaining about const equivariance since at least C++ and D makes it worse.

Well Scala, Eiffel and probably other languages have support for at least equivariance of the current object. I always forget the name of that feature. Andrei
Oct 14 2008
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Bill Baxter wrote:
 On Wed, Oct 15, 2008 at 6:23 AM, Denis Koroskin <2korden gmail.com> wrote:
 On Wed, 15 Oct 2008 01:18:40 +0400, Robert Fraser
 <fraserofthenight gmail.com> wrote:

 Andrei Alexandrescu wrote:
 Yah, that's where the PassQual template is needed. I'd agree without joy
 that, if handling true equivariance is too unwieldy, we can resign to
 solving only equivariance in qualifier.

typing was invented. People have been complaining about const equivariance since at least C++ and D makes it worse.

I mean, human needs grow over time as they discover something useful.

Agreed. There are uses for equivariance if you have it. I think especially so when thinking about functional style programming where you are chaining many functions together. It's nice if the chain you create doesn't lose the input object's true most derived identity. You can fake it with templates if you really have to, but then if they are member functions they become non-virtual.

Yah. What's the name of the feature? I was told a couple of times; I always forget it. Andrei
Oct 14 2008
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Bill Baxter wrote:
 On Wed, Oct 15, 2008 at 11:02 AM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 inout(X) fun(inout(Y) function(inout(Z)) gun);

 Now you tell me which inout goes where.

Ew. Yeh, that is sticky. This will not be that common though. Maybe you could allow arguments/indexes on the inout for those cases? inout[0](X) fun(inout[0](Y) function(inout[1](Z)) gun);

If you'd ask for my opinion, I'd say the show of inout stops here.
 and for 3:

 X f(return X x);

 where return means 'this function will return x'.  The return statements
 in this function are all required to return the value of x.  (x cannot be
 rebindable inside the function).

we can all be happy with whatever solution we find for the general case. It's not that much to type afterall.

So what's the use case you have in mind that that doesn't satisfy?

Too many. I mean most all. Essentially you can only implement identity and min/max :o). Think of stripl when you need to return a slice of the incoming argument. Andrei
Oct 14 2008
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Bill Baxter wrote:
 Hmmm, looking at this:
 
 class Owner
 {
   const?(A) a() const?{...}
   const?(B) b() const?{...}
   const?(C) c() const?{...}
   const?(D) d() const?{...}
   const?(E) e() const?{...}
   const?(F) f() const?{...}
   const?(G) g() const?{...}
   const?(H) h() const?{...}
   const?(I) i() const?{...}
   const?(J) j() const?{...}
 }
 
 makes me think if we go with that syntax, Andrei is sooner or later
 going to complain about his D code asking him too many questions.  :-)
 Eh, I guess he can edit the emacs mode to display const? as
 smiley-faces or something.  :-) :-)

At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad. Andrei
Oct 15 2008
next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
Andrei Alexandrescu wrote:
 Bill Baxter wrote:
 Hmmm, looking at this:

 class Owner
 {
   const?(A) a() const?{...}
   const?(B) b() const?{...}
   const?(C) c() const?{...}
   const?(D) d() const?{...}
   const?(E) e() const?{...}
   const?(F) f() const?{...}
   const?(G) g() const?{...}
   const?(H) h() const?{...}
   const?(I) i() const?{...}
   const?(J) j() const?{...}
 }

 makes me think if we go with that syntax, Andrei is sooner or later
 going to complain about his D code asking him too many questions.  :-)
 Eh, I guess he can edit the emacs mode to display const? as
 smiley-faces or something.  :-) :-)

At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad. Andrei

Huh? But class Owner { const // without the “?” { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } only apply const on the function, but not their return type (i.e. they become A a() const { ... } // etc. but not const(A) a() const { ... } // etc. )
Oct 15 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
KennyTM~ wrote:
 Andrei Alexandrescu wrote:
 Bill Baxter wrote:
 Hmmm, looking at this:

 class Owner
 {
   const?(A) a() const?{...}
   const?(B) b() const?{...}
   const?(C) c() const?{...}
   const?(D) d() const?{...}
   const?(E) e() const?{...}
   const?(F) f() const?{...}
   const?(G) g() const?{...}
   const?(H) h() const?{...}
   const?(I) i() const?{...}
   const?(J) j() const?{...}
 }

 makes me think if we go with that syntax, Andrei is sooner or later
 going to complain about his D code asking him too many questions.  :-)
 Eh, I guess he can edit the emacs mode to display const? as
 smiley-faces or something.  :-) :-)

At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad. Andrei

Huh? But class Owner { const // without the “?” { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } only apply const on the function, but not their return type (i.e. they become A a() const { ... } // etc. but not const(A) a() const { ... } // etc. )

Yah, but it does not make sense to not apply const? to the return value. Andrei
Oct 15 2008
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 KennyTM~ wrote:
 Andrei Alexandrescu wrote:
 Bill Baxter wrote:
 Hmmm, looking at this:

 class Owner
 {
   const?(A) a() const?{...}
   const?(B) b() const?{...}
   const?(C) c() const?{...}
   const?(D) d() const?{...}
   const?(E) e() const?{...}
   const?(F) f() const?{...}
   const?(G) g() const?{...}
   const?(H) h() const?{...}
   const?(I) i() const?{...}
   const?(J) j() const?{...}
 }

 makes me think if we go with that syntax, Andrei is sooner or later
 going to complain about his D code asking him too many questions.  :-)
 Eh, I guess he can edit the emacs mode to display const? as
 smiley-faces or something.  :-) :-)

At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad. Andrei

Huh? But class Owner { const // without the "?" { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } only apply const on the function, but not their return type (i.e. they become A a() const { ... } // etc. but not const(A) a() const { ... } // etc. )

Yah, but it does not make sense to not apply const? to the return value.

compromise:
 class Owner
 {
    const?
    {
       const?(A)  a()  {...}
       const?(B)  b()  {...}
       const?(C)  c()  {...}
       const?(D)  d()  {...}
       const?(E)  e()  {...}
       const?(F)  f()  {...}
       const?(G)  g()  {...}
       const?(H)  h()  {...}
       const?(I)  i()  {...}
      const?(J)  j()  {...}
    }
 }



Also still not terrible. Whatever we come up with should be consistent with the other type modifiers. Otherwise, you get into sticky situations like this: const? { A a(X x) } Should this translate to: const?(A) a(const?(X) x) const? Sidenote: seeing that const? at the end really looks weird. Not sure I like this so much any more. I think I'd rather see a new keyword... -Steve
Oct 15 2008
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Bill Baxter wrote:
 On Thu, Oct 16, 2008 at 4:10 AM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 Bill Baxter wrote:
 Hmmm, looking at this:

 class Owner
 {
  const?(A) a() const?{...}
  const?(B) b() const?{...}
  const?(C) c() const?{...}
  const?(D) d() const?{...}
  const?(E) e() const?{...}
  const?(F) f() const?{...}
  const?(G) g() const?{...}
  const?(H) h() const?{...}
  const?(I) i() const?{...}
  const?(J) j() const?{...}
 }

 makes me think if we go with that syntax, Andrei is sooner or later
 going to complain about his D code asking him too many questions.  :-)
 Eh, I guess he can edit the emacs mode to display const? as
 smiley-faces or something.  :-) :-)

class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad.

I was thinking about that too. But I'm not a big fan of blocks that change the meaning of declarations in a non-local way. It looks quite readable in toy examples, but after you insert all the bodies and documentation comments for everything in the block, it becomes very easy to lose sight of the tag way back at the beginning of the block.

I disagree. A block is a block is a block. Indentation shows it's not non-local. I understand how you could formulate an argument against labels. They do change meaning in a non-obvious way. Block indentation makes all the difference. I'm in fact surprised to hear that from a pythonboi :o). Andrei
Oct 15 2008
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Bill Baxter wrote:
 If the top of the block is off the screen it's not necessarily obvious
 that it's a block.

By the same argument it's not obvious whether you're in a class, function, template etc. definition. All of them do change the context of what's going on. Andrei
Oct 15 2008
prev sibling parent "Bill Baxter" <wbaxter gmail.com> writes:
On Thu, Oct 16, 2008 at 4:10 AM, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:
 Bill Baxter wrote:
 Hmmm, looking at this:

 class Owner
 {
  const?(A) a() const?{...}
  const?(B) b() const?{...}
  const?(C) c() const?{...}
  const?(D) d() const?{...}
  const?(E) e() const?{...}
  const?(F) f() const?{...}
  const?(G) g() const?{...}
  const?(H) h() const?{...}
  const?(I) i() const?{...}
  const?(J) j() const?{...}
 }

 makes me think if we go with that syntax, Andrei is sooner or later
 going to complain about his D code asking him too many questions.  :-)
 Eh, I guess he can edit the emacs mode to display const? as
 smiley-faces or something.  :-) :-)

At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad.

I was thinking about that too. But I'm not a big fan of blocks that change the meaning of declarations in a non-local way. It looks quite readable in toy examples, but after you insert all the bodies and documentation comments for everything in the block, it becomes very easy to lose sight of the tag way back at the beginning of the block. --bb
Oct 15 2008
prev sibling parent "Bill Baxter" <wbaxter gmail.com> writes:
On Wed, Oct 15, 2008 at 11:20 AM, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:
 Bill Baxter wrote:
 On Wed, Oct 15, 2008 at 11:02 AM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 inout(X) fun(inout(Y) function(inout(Z)) gun);

 Now you tell me which inout goes where.

Ew. Yeh, that is sticky. This will not be that common though. Maybe you could allow arguments/indexes on the inout for those cases? inout[0](X) fun(inout[0](Y) function(inout[1](Z)) gun);

If you'd ask for my opinion, I'd say the show of inout stops here.
 and for 3:

 X f(return X x);

 where return means 'this function will return x'.  The return statements
 in this function are all required to return the value of x.  (x cannot
 be
 rebindable inside the function).

I think this is a bit too particular and too restrictive to be useful. Maybe we can all be happy with whatever solution we find for the general case. It's not that much to type afterall.

So what's the use case you have in mind that that doesn't satisfy?

Too many. I mean most all. Essentially you can only implement identity and min/max :o). Think of stripl when you need to return a slice of the incoming argument.

But stripl just needs constness propagation, right? So it's handled by Steve's inout proposal. The return thing just handles propagation of base type. I assume he has some way to combine inout with return when you want to manipulate both constness and base type. Maybe I've missed something though... --bb
Oct 14 2008
prev sibling parent reply "Bill Baxter" <wbaxter gmail.com> writes:
On Thu, Oct 16, 2008 at 12:46 AM, Steven Schveighoffer
<schveiguy yahoo.com> wrote:
 "Bill Baxter"wrote
 On Wed, Oct 15, 2008 at 10:13 AM, Steven Schveighoffer
 I'm not a fan of complexity where it is not required.

 You have outlined pretty much exactly the only use cases for this
 feature.
 So why do you want to continually write large typedefs that are identical
 in
 front of all your functions, when you can do so with a succinct syntax?

Think of it this way: right now to create variations on const you have to declare the method three times. This would reduce that to one short preamble + one declaration.

An example class with 10 accessors (forget about the implementations): Your way: class Owner { typedef { Const : const } Const(A) a() Const {...} [...] // written by another author who prefers a different identifier for Const typedef { Konst : const } Konst(J) j() Konst {...} } My way: class Owner { inout(A) a() inout {...} [...] } It might be just me, but my way seems cleaner and easier to implement.

Good example. Definitely seems easier to implement. And agreed that for common cases it has less clutter. I could suggest making some class-scoped version of the typedef{Const : const} thing, but at that point it would just become a different name for your "inout", and would have the disadvantage of different people giving different names to something that will be used in just about every class. Hmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)
 Here's something that comes up -- iterators in C++ usually end up
 needing to come in const and non-const flavors.  The "head" of both is
 mutable, but the "tail" is const on the const flavor.  How do you
 write this pair of functions as one?:

You can't cast an iterator to 'head const' (another nagging problem, but a separate one), so the only viable option is templates.

I don't understand this comment. Why would you need to cast iterators to "head const"? I think head const is not supported by the current const design at all -- via casting or otherwise. --bb
Oct 15 2008
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Bill Baxter" wrote
 On Thu, Oct 16, 2008 at 12:46 AM, Steven Schveighoffer
 <schveiguy yahoo.com> wrote:
 "Bill Baxter"wrote
 On Wed, Oct 15, 2008 at 10:13 AM, Steven Schveighoffer
 I'm not a fan of complexity where it is not required.

 You have outlined pretty much exactly the only use cases for this
 feature.
 So why do you want to continually write large typedefs that are 
 identical
 in
 front of all your functions, when you can do so with a succinct syntax?

Think of it this way: right now to create variations on const you have to declare the method three times. This would reduce that to one short preamble + one declaration.

An example class with 10 accessors (forget about the implementations): Your way: class Owner { typedef { Const : const } Const(A) a() Const {...} [...] // written by another author who prefers a different identifier for Const typedef { Konst : const } Konst(J) j() Konst {...} } My way: class Owner { inout(A) a() inout {...} [...] } It might be just me, but my way seems cleaner and easier to implement.

Good example. Definitely seems easier to implement. And agreed that for common cases it has less clutter. I could suggest making some class-scoped version of the typedef{Const : const} thing, but at that point it would just become a different name for your "inout", and would have the disadvantage of different people giving different names to something that will be used in just about every class. Hmmm, looking at this: class Owner { const?(A) a() const?{...} const?(B) b() const?{...} const?(C) c() const?{...} const?(D) d() const?{...} const?(E) e() const?{...} const?(F) f() const?{...} const?(G) g() const?{...} const?(H) h() const?{...} const?(I) i() const?{...} const?(J) j() const?{...} } makes me think if we go with that syntax, Andrei is sooner or later going to complain about his D code asking him too many questions. :-) Eh, I guess he can edit the emacs mode to display const? as smiley-faces or something. :-) :-)

lol! or should I say lol? I'm pretty sure this isn't taken: const!(x) and his emacs module could translate it to: const <<x>> (Yes, I have no idea how to type those weird characters ;)
 Here's something that comes up -- iterators in C++ usually end up
 needing to come in const and non-const flavors.  The "head" of both is
 mutable, but the "tail" is const on the const flavor.  How do you
 write this pair of functions as one?:

You can't cast an iterator to 'head const' (another nagging problem, but a separate one), so the only viable option is templates.

I don't understand this comment. Why would you need to cast iterators to "head const"? I think head const is not supported by the current const design at all -- via casting or otherwise.

Damn! I always do that! swap head const with tail const, sorry ;) -Steve
Oct 15 2008
prev sibling next sibling parent "Bill Baxter" <wbaxter gmail.com> writes:
On Wed, Oct 15, 2008 at 11:02 AM, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:
 Steven Schveighoffer wrote:
 I'll reiterate my proposal:

 inout(X) f(inout(Y) y, inout(Z) z)

 where inout means 'the greatest common constancy of all variables that are
 declared inout'.  Again, not in love with inout, but inout kind of reads
 'you get out what you put in', and it is a 'free' keyword.

Ugh. This doesn't quite work because it mimics the "true" solution (= bind an alias to the qualifier) by always calling that alias "inout" or whatever keyword name. This problem becomes apparent when you try to nest declarations: inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.

Ew. Yeh, that is sticky. This will not be that common though. Maybe you could allow arguments/indexes on the inout for those cases? inout[0](X) fun(inout[0](Y) function(inout[1](Z)) gun);
 and for 3:

 X f(return X x);

 where return means 'this function will return x'.  The return statements
 in this function are all required to return the value of x.  (x cannot be
 rebindable inside the function).

I think this is a bit too particular and too restrictive to be useful. Maybe we can all be happy with whatever solution we find for the general case. It's not that much to type afterall.

So what's the use case you have in mind that that doesn't satisfy? --bb
Oct 14 2008
prev sibling parent reply Sergey Gromov <snake.scaly gmail.com> writes:
Tue, 14 Oct 2008 16:31:15 -0400,
Steven Schveighoffer wrote:
 Damn, I think there is some confusion here, because we are trying to solve 
 two related, but unequal problems.
 
 First is const equivariance (is that the right term?).  I want to specify a 
 function that treats an argument as const, but does not affect the const 
 contract that the caller has with that argument (i.e. my original scoped 
 const proposal).
 
 The second is type equivariance.  I want to specify that I will treat an 
 argument as a base type, but I will not change the derived type that the 
 caller is using.
 
 These are similar, if you consider that const is a 'base type' of its 
 mutable or invariant version.  But there is one caveat that is hard to 
 explain using this terminology.  const is a type modifier, not a type.  So 
 it's not really a base type of a type, and it can be applied to any type in 
 existance.  Not the same as specifying a base type.

This is a very good point. I think these cases are different, too. And I think there is another confusion which leads to obscure syntaxes. Any function argument has two types: the call-site and the function-site, the latter is made from the former by an implicit cast. You must specify function-site type for the function body to compile properly, and you want to know a call-site type for equi-trickery. But that's not all. An equi-function should have two return types: one function-site for the body to compile properly, and one call-site for the type system to work there. Let's experiment with an explicit syntax. I'll define a calltype() built-in function similar to typeof() but yielding a call-site type in a function definition context. And I'll use cast() to specify the call- site return type of a function. cast(calltype(s)) const(char)[] stripl(const(char)[] s); Here cast() explicitly defines this function as a variant function. class A { cast(calltype(this)) A clone() { return new A; } } Now a verbose one: cast(PassQual!(calltype(a), calltype(b))(char)[]) const(char)[] choose(const(char)[] a, const(char)[] b, bool which); Now that I look at this, all the magic is concentrated within the cast() block and is actually quite close to Bill Baxter's typedef {} proposal. And it seems like the call-site types are only needed there, and they're the only types needed. So typeof() can be used there with modified semantics. I even think that inout() can be used instead of cast() with pretty much the same effect on readability. So a simple covariant function would look like this: inout(typeof(s)) const(char)[] foo(const(char)[] s) { return s[1..$]; } which is translated into: T foo(T)(T s) { return cast(T) _foo_impl(s); } const(char)[] _foo_impl(const(char)[] s) { return s[1..$]; } Though I'd prefer cast() or var() or covar() as a covariant modifier.
Oct 15 2008
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Sergey Gromov" wrote
 Tue, 14 Oct 2008 16:31:15 -0400,
 Steven Schveighoffer wrote:
 Damn, I think there is some confusion here, because we are trying to 
 solve
 two related, but unequal problems.

 First is const equivariance (is that the right term?).  I want to specify 
 a
 function that treats an argument as const, but does not affect the const
 contract that the caller has with that argument (i.e. my original scoped
 const proposal).

 The second is type equivariance.  I want to specify that I will treat an
 argument as a base type, but I will not change the derived type that the
 caller is using.

 These are similar, if you consider that const is a 'base type' of its
 mutable or invariant version.  But there is one caveat that is hard to
 explain using this terminology.  const is a type modifier, not a type. 
 So
 it's not really a base type of a type, and it can be applied to any type 
 in
 existance.  Not the same as specifying a base type.

This is a very good point. I think these cases are different, too. And I think there is another confusion which leads to obscure syntaxes. Any function argument has two types: the call-site and the function-site, the latter is made from the former by an implicit cast. You must specify function-site type for the function body to compile properly, and you want to know a call-site type for equi-trickery. But that's not all. An equi-function should have two return types: one function-site for the body to compile properly, and one call-site for the type system to work there. Let's experiment with an explicit syntax. I'll define a calltype() built-in function similar to typeof() but yielding a call-site type in a function definition context. And I'll use cast() to specify the call- site return type of a function. cast(calltype(s)) const(char)[] stripl(const(char)[] s); Here cast() explicitly defines this function as a variant function. class A { cast(calltype(this)) A clone() { return new A; } } Now a verbose one: cast(PassQual!(calltype(a), calltype(b))(char)[]) const(char)[] choose(const(char)[] a, const(char)[] b, bool which); Now that I look at this, all the magic is concentrated within the cast() block and is actually quite close to Bill Baxter's typedef {} proposal. And it seems like the call-site types are only needed there, and they're the only types needed. So typeof() can be used there with modified semantics. I even think that inout() can be used instead of cast() with pretty much the same effect on readability. So a simple covariant function would look like this: inout(typeof(s)) const(char)[] foo(const(char)[] s) { return s[1..$]; } which is translated into: T foo(T)(T s) { return cast(T) _foo_impl(s); } const(char)[] _foo_impl(const(char)[] s) { return s[1..$]; } Though I'd prefer cast() or var() or covar() as a covariant modifier.

Auto-deducing the return type is only half the problem. During the function, the compiler must make certain guarantees about s, and about the return type. For example, something cannot be implicitly cast to the type of s, since its true type is not known at compile-time, only it's base type. And since the return type must match the constancy of the input, the return type must be restricted to only the type of s. Since you can't implicitly cast to s, you must return s or a subset of s. But the return type doesn't necessarily have to be related to s, it could be just of the same constancy. Someone asked, what if you are returning a private field (i.e. through an accessor). Would you use typeof(this.privateField) in the signature? Doesn't this expose some private field name? What if there was no field that existed for the type you wanted to return? You'd have to do kludgy stuff like typeof(T.init), but then how do you flag the argument as being const-variant? I think we need something to flag the argument as const-variant, and something to flag the return value as const-variant. It's clearer that the argument is being affected, and it's clearer what you want to return. To me: inout(char)[] stripl(inout(char)[] s); is much clearer than typeof(s) stripl(const(char)[] s); I can look at each part of the signature, and know exactly what is going on. With the second syntax, everything has references to other pieces, so I have to look back and forth to see what is happening. If s is buried in a long list of arguments, it's even less clear. Note also that the typeof(T) syntax does not help functions with multiple arguments without template kludginess (and I can't see how the compiler enforces the guarantees, given a custom template), whereas using a builtin type modifier handles it perfectly. At least for const-variance, a type modifier is ideal. Some of the other ideas might benefit from a typeof (arg) syntax. -Steve
Oct 15 2008
prev sibling parent reply "Bill Baxter" <wbaxter gmail.com> writes:
On Wed, Oct 15, 2008 at 10:13 AM, Steven Schveighoffer
<schveiguy yahoo.com> wrote:
 "Bill Baxter" wrote
 I'm looking at all these funky syntaxes with their many unusual
 implicit assumptions that they require a user to keep in mind, and
 thinking it would all be a lot simpler if you could just declare some
 template-like meta-variables in a preamble to the function.

 I also don't like the looks of using a symbol in the return type that
 doesn't get declared till you see the parameter list.

 So how about introducing a "typedef block" where you can introduce
 metatypes and give them constraints.

 For constness:

 typedef {  Const : const }  // in this case means any kind of const
 Const(int) someFunc(Const(int) z) { ... }

 For Objects:

 class BaseClass {}
 class DerivedClass : BaseClass {}

 typedef { DType : BaseClass } // DType is anything passing  is(DType :
 BaseClass)
 DType someFunc(DType input)

 For both:

 typedef {
  DType : BaseClass;
  Const : const
 }
 Const(DType) someFunc(Const(DType) input)

 And with something like this you can also come up with some solution
 to the min problem
 typedef{
   ConstA : const;
   ConstB : const;
   ConstC = maxConst!(ConstA,ConstB)
 }
 ConstC(Type) max(ConstA(Type), ConstB(Type))

 maxConst would be some template-like thing func to take the "more
 const" of the two qualifiers.  Not sure how you'd implement that.

 Lots o hole's there, but the basic idea I'm suggesting is to declare
 the types and qualifiers that can vary in some sort of a preamble
 block.  To me this looks like it will be much more readable and easier
 to maintain than any of these funky syntaxes with lots of implicit
 rules to remember.

I'm not a fan of complexity where it is not required. You have outlined pretty much exactly the only use cases for this feature. So why do you want to continually write large typedefs that are identical in front of all your functions, when you can do so with a succinct syntax?

Think of it this way: right now to create variations on const you have to declare the method three times. This would reduce that to one short preamble + one declaration. And note that C++ gets along fine without any such assistance. It is far from "all your functions" that require such handling, that's why it doesn't kill C++ not to have it. For returning the input type, it has been commented "do we even really need this?" since most other popular languages don't have such a thing, and it is generally not a "buzz feature" you hear about much (unlike closures or lambdas, etc). So I don't think its use will be that common, thus a little more verbose syntax will not be an undue burden. Indeed, precisely because it will not be used frequently, it *should* have a more verbose, more explicit syntax. So that on those occasions when it is used it will A) not sneak past the casual observer's notice and B) have at least some chance that the meaning of the code can be guessed without having to look it up.
 To put the use cases in more english descriptions:

 1. I want the constancy of my return to be dependent on the constancy of the
 argument at the call site.
 2. I want the constancy of my return to be the greatest common constancy of
 multiple arguments at the call site.
 3. The return type will be exactly the same value that I passed in to the
 function, so I can use it for call chaining.  So it's OK to auto cast to the
 most derived type.
 4. I want 1 and 3

 Where greatest common constancy is defined as const if any arguments differ
 in constancy.  If they don't differ it is defined as the constancy of the
 arguments.

 My biggest concern is 1 and 2, since otherwise for 1, we are left with 3
 different versions of the same function, and for 2, we are left with 3^n
 different versions.

 3 (and 4) are of mild concern, because you are only forced to redefine once
 per class, and it's not as common a problem, since not all classes require
 handling of call chaining, whereas all classes should be const-aware.

 I'll reiterate my proposal:

 inout(X) f(inout(Y) y, inout(Z) z)

 where inout means 'the greatest common constancy of all variables that are
 declared inout'.  Again, not in love with inout, but inout kind of reads
 'you get out what you put in', and it is a 'free' keyword.

 and for 3:

 X f(return X x);

 where return means 'this function will return x'.  The return statements in
 this function are all required to return the value of x.  (x cannot be
 rebindable inside the function).

That does sound reasonable for the use cases you've outlined. I hadn't realized you were making "inout" mean "maximal const". I can't think of any use case for non-maximal const of the top of my head so aside from the "inout" being not a great word, I think I could live with this ... unless someone discovers another use case. If inout is useless now then we could retire it and add a new "anyconst" keyword. Or "vconst" for "variable/various/varying/virtual const" vconst(X) f(vconst(Y) y, vconst(Z) z) Or stick some syntax on it. Like "const~", the squiggle indicating "similar to". Or "const?". const?(X) f(const?(Y) y, const?(Z) z); --- Here's something that comes up -- iterators in C++ usually end up needing to come in const and non-const flavors. The "head" of both is mutable, but the "tail" is const on the const flavor. How do you write this pair of functions as one?: const(X) getValue(const_iterator iter); X getValue(iterator iter); I guess I can answer that myself. If you wrote an apropriate IterType!(T) template then maybe with your syntax you could say: inout(X) getValue(IterType!(inout(X)) iter); and with my suggestion it would be something like typedef {Const : const } Const(X) getValue(IterType!(Const(X)) iter); Ok, so I guess that's handled OK. --bb
Oct 14 2008
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Bill Baxter"wrote
 On Wed, Oct 15, 2008 at 10:13 AM, Steven Schveighoffer
 I'm not a fan of complexity where it is not required.

 You have outlined pretty much exactly the only use cases for this 
 feature.
 So why do you want to continually write large typedefs that are identical 
 in
 front of all your functions, when you can do so with a succinct syntax?

Think of it this way: right now to create variations on const you have to declare the method three times. This would reduce that to one short preamble + one declaration.

An example class with 10 accessors (forget about the implementations): Your way: class Owner { typedef { Const : const } Const(A) a() Const {...} typedef { Const : const } Const(B) b() Const {...} typedef { Const : const } Const(C) c() Const {...} typedef { Const : const } Const(D) d() Const {...} typedef { Const : const } Const(E) e() Const {...} typedef { Const : const } Const(F) f() Const {...} typedef { Const : const } Const(G) g() Const {...} typedef { Const : const } Const(H) h() Const {...} typedef { Const : const } Const(I) i() Const {...} // written by another author who prefers a different identifier for Const typedef { Konst : const } Konst(J) j() Konst {...} } My way: class Owner { inout(A) a() inout {...} inout(B) b() inout{...} inout(C) c() inout{...} inout(D) d() inout{...} inout(E) e() inout{...} inout(F) f() inout{...} inout(G) g() inout{...} inout(H) h() inout{...} inout(I) i() inout{...} inout(J) j() inout{...} } It might be just me, but my way seems cleaner and easier to implement.
 And note that C++ gets along fine without any such assistance.  It is
 far from "all your functions" that require such handling, that's why
 it doesn't kill C++ not to have it.

But C++ doesn't have transitive const. It is my experience that most C++ classes are not implemented as const-aware, or are but implemented const aware incorrectly. Including classes I wrote. I think Walter is right when he says that C++ const is a mess.
 For returning the input type, it has been commented "do we even really
 need this?" since most other popular languages don't have such a
 thing, and it is generally not a "buzz feature" you hear about much
 (unlike closures or lambdas, etc).  So I don't think its use will be
 that common, thus a little more verbose syntax will not be an undue
 burden.  Indeed, precisely because it will not be used frequently, it
 *should* have a more verbose, more explicit syntax.  So that on those
 occasions when it is used it will A) not sneak past the casual
 observer's notice and B) have at least some chance that the meaning of
 the code can be guessed without having to look it up.

It is not something I really am pushing. My main concern is the const variance. Something like this makes const so much more appealing (remember, no other languages have tried transitive const). However, with an additional feature that requires all derived classes to re-implement a function, this can be more useful than just 'return the input value'. Think clone(). As far as requiring a clumsy syntax, especially where changing style is possible but has no real affect on the result (i.e. using Konst instead of Const), I don't see that as a benefit.
 To put the use cases in more english descriptions:

 1. I want the constancy of my return to be dependent on the constancy of 
 the
 argument at the call site.
 2. I want the constancy of my return to be the greatest common constancy 
 of
 multiple arguments at the call site.
 3. The return type will be exactly the same value that I passed in to the
 function, so I can use it for call chaining.  So it's OK to auto cast to 
 the
 most derived type.
 4. I want 1 and 3

 Where greatest common constancy is defined as const if any arguments 
 differ
 in constancy.  If they don't differ it is defined as the constancy of the
 arguments.

 My biggest concern is 1 and 2, since otherwise for 1, we are left with 3
 different versions of the same function, and for 2, we are left with 3^n
 different versions.

 3 (and 4) are of mild concern, because you are only forced to redefine 
 once
 per class, and it's not as common a problem, since not all classes 
 require
 handling of call chaining, whereas all classes should be const-aware.

 I'll reiterate my proposal:

 inout(X) f(inout(Y) y, inout(Z) z)

 where inout means 'the greatest common constancy of all variables that 
 are
 declared inout'.  Again, not in love with inout, but inout kind of reads
 'you get out what you put in', and it is a 'free' keyword.

 and for 3:

 X f(return X x);

 where return means 'this function will return x'.  The return statements 
 in
 this function are all required to return the value of x.  (x cannot be
 rebindable inside the function).

That does sound reasonable for the use cases you've outlined. I hadn't realized you were making "inout" mean "maximal const". I can't think of any use case for non-maximal const of the top of my head so aside from the "inout" being not a great word, I think I could live with this ... unless someone discovers another use case.

I'm not super fond of inout, but it allows not adding a new keyword. Some view that as an essential part of this.
 If inout is useless now then we could retire it and add a new
 "anyconst" keyword.  Or "vconst" for "variable/various/varying/virtual
 const"

  vconst(X) f(vconst(Y) y, vconst(Z) z)

 Or stick some syntax on it.  Like "const~", the squiggle indicating
 "similar to".  Or "const?".

  const?(X) f(const?(Y) y, const?(Z) z);

All these sound like valid alternatives, I'd be fine with any of them.
 Here's something that comes up -- iterators in C++ usually end up
 needing to come in const and non-const flavors.  The "head" of both is
 mutable, but the "tail" is const on the const flavor.  How do you
 write this pair of functions as one?:

You can't cast an iterator to 'head const' (another nagging problem, but a separate one), so the only viable option is templates. -Steve
Oct 15 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
  const?(X) f(const?(Y) y, const?(Z) z);

All these sound like valid alternatives, I'd be fine with any of them.

The const? has been on the table. It is a fave of mine because it offers a consistent solution for a related issue - detecting lvalueness: void foo(ref? Widget w); This means foo should bind to a ref Widget if you pass it an lvalue, and to a Widget if offered an rvalue. There is precedent on using "x?" to mean an optional x in regexes, so I wouldn't be surprised if many figured what const? does when first seeing it. One issue have with const? is that it binds the behavior to const, thereby eliminating the chance of making things more general. Also the const? will have to do something NOT suggested by the notation, namely pass the invariant, if present, along. Anyhow, I wanted to share one more thought. On the face of it, as someone already said, we DO have a solution to avoid code duplication: templates. Maybe a fertile direction to take would be to use regular template syntax and just let the compiler figure out that it can actually define only one function instead of three. What I'm saying is that we're really trying to beat the compiler in the head until it understands that: S stripl(S s) if (is(S : const(char)[]) { stmts } really means to us: char[] stripl(char[] s) { stmts } const(char)[] stripl(const(char)[] s) { stmts } invariant(char)[] stripl(invariant(char) s) if (is(S : const(char)[]) { stmts } What if, instead of finding yet another notation for that simple fact, we actually used the "right" notation (it is right because it's already there!) and figure out whether the compiler can understand it? I was about to post when I realized that that idea won't work prettily at all with member functions... sigh :o/ Andrei
Oct 15 2008
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
  const?(X) f(const?(Y) y, const?(Z) z);

All these sound like valid alternatives, I'd be fine with any of them.

The const? has been on the table. It is a fave of mine because it offers a consistent solution for a related issue - detecting lvalueness: void foo(ref? Widget w); This means foo should bind to a ref Widget if you pass it an lvalue, and to a Widget if offered an rvalue. There is precedent on using "x?" to mean an optional x in regexes, so I wouldn't be surprised if many figured what const? does when first seeing it. One issue have with const? is that it binds the behavior to const, thereby eliminating the chance of making things more general. Also the const? will have to do something NOT suggested by the notation, namely pass the invariant, if present, along.

I've convinced myself at this point that the const piece has to be separate from the 'derived type' piece. Both can be required separately, i.e. I want to allow variations to the constancy of a specific type, but not to derivative types. Therefore, it makes the most sense to me to make each feature use a distinct syntax. So I have no problem with this notation for the const variant. As far as invariant, I think it is well established that people don't gripe or sing the praises of the const-invariant system, they generally refer to it as the const system. I think it's already a defacto standard that const in D includes invariant. They are clearly related.
 Anyhow, I wanted to share one more thought. On the face of it, as someone 
 already said, we DO have a solution to avoid code duplication: templates. 
 Maybe a fertile direction to take would be to use regular template syntax 
 and just let the compiler figure out that it can actually define only one 
 function instead of three.

 What I'm saying is that we're really trying to beat the compiler in the 
 head until it understands that:

 S stripl(S s) if (is(S : const(char)[]) { stmts }

 really means to us:

 char[] stripl(char[] s) { stmts }
 const(char)[] stripl(const(char)[] s) { stmts }
 invariant(char)[] stripl(invariant(char) s) if (is(S : const(char)[]) { 
 stmts }

 What if, instead of finding yet another notation for that simple fact, we 
 actually used the "right" notation (it is right because it's already 
 there!) and figure out whether the compiler can understand it?

 I was about to post when I realized that that idea won't work prettily at 
 all with member functions... sigh :o/

Yeah, that would be tough for functions you want to make virtual. I think it needs to be required to be a single function implementation, not left up to the compiler. -Steve
Oct 15 2008
prev sibling next sibling parent reply ore-sama <spam here.lot> writes:
Andrei Alexandrescu Wrote:

 One issue  have with const? is that it binds the behavior to const, 
 thereby eliminating the chance of making things more general. Also the 
 const? will have to do something NOT suggested by the notation, namely 
 pass the invariant, if present, along.
 

 What I'm saying is that we're really trying to beat the compiler in the 
 head until it understands that:
 
 S stripl(S s) if (is(S : const(char)[]) { stmts }

const?(X) fun(const?(Y) y); One big difference between normal and templated functions is you can provide only signature for normal function. Is it safe to typecheck/instantiate templated function without body?
Oct 16 2008
parent reply ore-sama <spam here.lot> writes:
ore-sama Wrote:

 Andrei Alexandrescu Wrote:
 S stripl(S s) if (is(S : const(char)[]) { stmts }

const?(X) fun(const?(Y) y);

just a thought: class A{} class B{ A a; } DeriveConst!(I,A) fun(I)(I b) { return b.a; } unittest { B m; fun(m); const B c; fun(c); invariant B i; invariant A a=fun(i); } template DeriveConst(A,B) { static if(is(A==invariant))alias invariant(B) DeriveConst; else static if(is(A==const))alias const(B) DeriveConst; else alias B DeriveConst; }
Oct 21 2008
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
ore-sama wrote:
 ore-sama Wrote:
 
 Andrei Alexandrescu Wrote:
 S stripl(S s) if (is(S : const(char)[]) { stmts }

const?(X) fun(const?(Y) y);

just a thought: class A{} class B{ A a; } DeriveConst!(I,A) fun(I)(I b) { return b.a; } unittest { B m; fun(m); const B c; fun(c); invariant B i; invariant A a=fun(i); } template DeriveConst(A,B) { static if(is(A==invariant))alias invariant(B) DeriveConst; else static if(is(A==const))alias const(B) DeriveConst; else alias B DeriveConst; }

Yah that was discussed under the name of PassQual. One problem is defining a palatable syntax for member functions. Andrei
Oct 21 2008
prev sibling next sibling parent Yigal Chripun <yigal100 gmail.com> writes:
Andrei Alexandrescu wrote:
 Anyhow, I wanted to share one more thought. On the face of it, as
 someone already said, we DO have a solution to avoid code duplication:
 templates. Maybe a fertile direction to take would be to use regular
 template syntax and just let the compiler figure out that it can
 actually define only one function instead of three.

here's a stab at a syntax similar to templates: T func(x : const, y : const)(X x, Y y, const Z z); in the above the return type T constancy is dependent on the constancy of x and y. z is a "regular" const. the idea is that X's *constancy* (not the entire type!) is subtype of const. here's a method: class Klass { T met(this: const, y : const)(X x, Y y, const Z z); } generic min function would look like: T min(T, x : const, y : const) (T x, T y) { ...} what do you think?
Oct 16 2008
prev sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Andrei Alexandrescu wrote:
 
 What I'm saying is that we're really trying to beat the compiler in the 
 head until it understands that:
 
 S stripl(S s) if (is(S : const(char)[]) { stmts }
 
 really means to us:
 
 char[] stripl(char[] s) { stmts }
 const(char)[] stripl(const(char)[] s) { stmts }
 invariant(char)[] stripl(invariant(char) s) if (is(S : const(char)[]) { 
 stmts }
 
 What if, instead of finding yet another notation for that simple fact, 
 we actually used the "right" notation (it is right because it's already 
 there!) and figure out whether the compiler can understand it?
 
 I was about to post when I realized that that idea won't work prettily 
 at all with member functions... sigh :o/
 
 
 Andrei

Wouldn't work prettily with member functions because one can't parameterize on the actual 'this' argument, right? -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 18 2008
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Bruno Medeiros wrote:
 Andrei Alexandrescu wrote:
 What I'm saying is that we're really trying to beat the compiler in 
 the head until it understands that:

 S stripl(S s) if (is(S : const(char)[]) { stmts }

 really means to us:

 char[] stripl(char[] s) { stmts }
 const(char)[] stripl(const(char)[] s) { stmts }
 invariant(char)[] stripl(invariant(char) s) if (is(S : const(char)[]) 
 { stmts }

 What if, instead of finding yet another notation for that simple fact, 
 we actually used the "right" notation (it is right because it's 
 already there!) and figure out whether the compiler can understand it?

 I was about to post when I realized that that idea won't work prettily 
 at all with member functions... sigh :o/


 Andrei

Wouldn't work prettily with member functions because one can't parameterize on the actual 'this' argument, right?

Yah, one can't do that unless more notation is added... Andrei
Oct 18 2008
prev sibling parent "Bill Baxter" <wbaxter gmail.com> writes:
On Wed, Oct 15, 2008 at 9:27 AM, Bill Baxter <wbaxter gmail.com> wrote:
 I'm looking at all these funky syntaxes with their many unusual
 implicit assumptions that they require a user to keep in mind, and
 thinking it would all be a lot simpler if you could just declare some
 template-like meta-variables in a preamble to the function.

 I also don't like the looks of using a symbol in the return type that
 doesn't get declared till you see the parameter list.

 So how about introducing a "typedef block" where you can introduce
 metatypes and give them constraints.

 For constness:

 typedef {  Const : const }  // in this case means any kind of const
 Const(int) someFunc(Const(int) z) { ... }

 For Objects:

 class BaseClass {}
 class DerivedClass : BaseClass {}

 typedef { DType : BaseClass } // DType is anything passing  is(DType :
 BaseClass)
 DType someFunc(DType input)

 For both:

 typedef {
  DType : BaseClass;
  Const : const
 }
 Const(DType) someFunc(Const(DType) input)

 And with something like this you can also come up with some solution
 to the min problem
 typedef{
   ConstA : const;
   ConstB : const;
   ConstC = maxConst!(ConstA,ConstB)
 }
 ConstC(Type) max(ConstA(Type), ConstB(Type))

 maxConst would be some template-like thing func to take the "more
 const" of the two qualifiers.  Not sure how you'd implement that.

 Lots o hole's there, but the basic idea I'm suggesting is to declare
 the types and qualifiers that can vary in some sort of a preamble
 block.  To me this looks like it will be much more readable and easier
 to maintain than any of these funky syntaxes with lots of implicit
 rules to remember.

Actually, let me take a step back from any concrete syntax. What I think would be an easy-to-read and easy-to-use system would look like this in pseudocode: let "Const" be any kind of constness let "BType" be any kind of object derived from BaseObject in the following declaration: Const(BType) theFunction(Const(BType) x, string y) { ... } In other words, *first* explicitly give names to the things that will vary, *then* use those names in the method signature. I think this will be much easier to use and more flexible than making up a bunch of rules with the aim of implying the same things via subtle variations of the basic declaration syntax. --bb
Oct 14 2008
prev sibling next sibling parent "Bill Baxter" <wbaxter gmail.com> writes:
On Wed, Oct 15, 2008 at 6:23 AM, Denis Koroskin <2korden gmail.com> wrote:
 On Wed, 15 Oct 2008 01:18:40 +0400, Robert Fraser
 <fraserofthenight gmail.com> wrote:

 Andrei Alexandrescu wrote:
 Yah, that's where the PassQual template is needed. I'd agree without joy
 that, if handling true equivariance is too unwieldy, we can resign to
 solving only equivariance in qualifier.

FWIW, people have done without type eqivariance w/o complaint since static typing was invented. People have been complaining about const equivariance since at least C++ and D makes it worse.

People didn't complain that they were nude until someone discovered it :) I mean, human needs grow over time as they discover something useful.

Agreed. There are uses for equivariance if you have it. I think especially so when thinking about functional style programming where you are chaining many functions together. It's nice if the chain you create doesn't lose the input object's true most derived identity. You can fake it with templates if you really have to, but then if they are member functions they become non-virtual. --bb
Oct 14 2008
prev sibling parent reply "Bill Baxter" <wbaxter gmail.com> writes:
I'm looking at all these funky syntaxes with their many unusual
implicit assumptions that they require a user to keep in mind, and
thinking it would all be a lot simpler if you could just declare some
template-like meta-variables in a preamble to the function.

I also don't like the looks of using a symbol in the return type that
doesn't get declared till you see the parameter list.

So how about introducing a "typedef block" where you can introduce
metatypes and give them constraints.

For constness:

typedef {  Const : const }  // in this case means any kind of const
Const(int) someFunc(Const(int) z) { ... }

For Objects:

class BaseClass {}
class DerivedClass : BaseClass {}

typedef { DType : BaseClass } // DType is anything passing  is(DType :
BaseClass)
DType someFunc(DType input)

For both:

typedef {
  DType : BaseClass;
  Const : const
}
Const(DType) someFunc(Const(DType) input)

And with something like this you can also come up with some solution
to the min problem
typedef{
   ConstA : const;
   ConstB : const;
   ConstC = maxConst!(ConstA,ConstB)
}
ConstC(Type) max(ConstA(Type), ConstB(Type))

maxConst would be some template-like thing func to take the "more
const" of the two qualifiers.  Not sure how you'd implement that.

Lots o hole's there, but the basic idea I'm suggesting is to declare
the types and qualifiers that can vary in some sort of a preamble
block.  To me this looks like it will be much more readable and easier
to maintain than any of these funky syntaxes with lots of implicit
rules to remember.

--bb
Oct 14 2008
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Bill Baxter" wrote
 I'm looking at all these funky syntaxes with their many unusual
 implicit assumptions that they require a user to keep in mind, and
 thinking it would all be a lot simpler if you could just declare some
 template-like meta-variables in a preamble to the function.

 I also don't like the looks of using a symbol in the return type that
 doesn't get declared till you see the parameter list.

 So how about introducing a "typedef block" where you can introduce
 metatypes and give them constraints.

 For constness:

 typedef {  Const : const }  // in this case means any kind of const
 Const(int) someFunc(Const(int) z) { ... }

 For Objects:

 class BaseClass {}
 class DerivedClass : BaseClass {}

 typedef { DType : BaseClass } // DType is anything passing  is(DType :
 BaseClass)
 DType someFunc(DType input)

 For both:

 typedef {
  DType : BaseClass;
  Const : const
 }
 Const(DType) someFunc(Const(DType) input)

 And with something like this you can also come up with some solution
 to the min problem
 typedef{
   ConstA : const;
   ConstB : const;
   ConstC = maxConst!(ConstA,ConstB)
 }
 ConstC(Type) max(ConstA(Type), ConstB(Type))

 maxConst would be some template-like thing func to take the "more
 const" of the two qualifiers.  Not sure how you'd implement that.

 Lots o hole's there, but the basic idea I'm suggesting is to declare
 the types and qualifiers that can vary in some sort of a preamble
 block.  To me this looks like it will be much more readable and easier
 to maintain than any of these funky syntaxes with lots of implicit
 rules to remember.

I'm not a fan of complexity where it is not required. You have outlined pretty much exactly the only use cases for this feature. So why do you want to continually write large typedefs that are identical in front of all your functions, when you can do so with a succinct syntax? To put the use cases in more english descriptions: 1. I want the constancy of my return to be dependent on the constancy of the argument at the call site. 2. I want the constancy of my return to be the greatest common constancy of multiple arguments at the call site. 3. The return type will be exactly the same value that I passed in to the function, so I can use it for call chaining. So it's OK to auto cast to the most derived type. 4. I want 1 and 3 Where greatest common constancy is defined as const if any arguments differ in constancy. If they don't differ it is defined as the constancy of the arguments. My biggest concern is 1 and 2, since otherwise for 1, we are left with 3 different versions of the same function, and for 2, we are left with 3^n different versions. 3 (and 4) are of mild concern, because you are only forced to redefine once per class, and it's not as common a problem, since not all classes require handling of call chaining, whereas all classes should be const-aware. I'll reiterate my proposal: inout(X) f(inout(Y) y, inout(Z) z) where inout means 'the greatest common constancy of all variables that are declared inout'. Again, not in love with inout, but inout kind of reads 'you get out what you put in', and it is a 'free' keyword. and for 3: X f(return X x); where return means 'this function will return x'. The return statements in this function are all required to return the value of x. (x cannot be rebindable inside the function). -Steve
Oct 14 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 I'll reiterate my proposal:
 
 inout(X) f(inout(Y) y, inout(Z) z)
 
 where inout means 'the greatest common constancy of all variables that are 
 declared inout'.  Again, not in love with inout, but inout kind of reads 
 'you get out what you put in', and it is a 'free' keyword.

Ugh. This doesn't quite work because it mimics the "true" solution (= bind an alias to the qualifier) by always calling that alias "inout" or whatever keyword name. This problem becomes apparent when you try to nest declarations: inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.
 and for 3:
 
 X f(return X x);
 
 where return means 'this function will return x'.  The return statements in 
 this function are all required to return the value of x.  (x cannot be 
 rebindable inside the function).

I think this is a bit too particular and too restrictive to be useful. Maybe we can all be happy with whatever solution we find for the general case. It's not that much to type afterall. Andrei
Oct 14 2008
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 I'll reiterate my proposal:

 inout(X) f(inout(Y) y, inout(Z) z)

 where inout means 'the greatest common constancy of all variables that 
 are declared inout'.  Again, not in love with inout, but inout kind of 
 reads 'you get out what you put in', and it is a 'free' keyword.

Ugh. This doesn't quite work because it mimics the "true" solution (= bind an alias to the qualifier) by always calling that alias "inout" or whatever keyword name. This problem becomes apparent when you try to nest declarations: inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.

That would be invalid. You need at least one inout argument that can be implicitly casted to const. As far as I know that can't be done with a function pointer. If you want to pass in a function pointer to a function which uses the inout constructs, then the inouts become literal inout. i.e.: inout(Y) foo(inout(Z) x) {...} inout(X) fun(inout(Z) argtofoo, inout(Y) function(inout(Z)) gun) {...} invariant(Z) z = cast(invariant) new Z; auto x = fun(z, &foo);// x is of type invariant(X) Maybe you had a different behavior in mind?
 and for 3:

 X f(return X x);

 where return means 'this function will return x'.  The return statements 
 in this function are all required to return the value of x.  (x cannot be 
 rebindable inside the function).

I think this is a bit too particular and too restrictive to be useful. Maybe we can all be happy with whatever solution we find for the general case. It's not that much to type afterall.

I agree that the usefulness is not huge. But there is no other compiler checkable way to guarantee that it can do the cast. If the function in question doesn't know about the derived type, how can it generate a new instance that can be casted to the derived type? It MUST return the exact argument. The only possible other use is for typedefs, but if you are using typedefs, there isn't a huge incentive if you want to use them exactly like the base type. And I don't want to define all functions in this style just in case someone has typedef'd the return type, and they want the compiler to auto-cast back to the typedef'd type. I'll note that I can live without case 3, as the usefulness is low. -Steve
Oct 14 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 I'll reiterate my proposal:

 inout(X) f(inout(Y) y, inout(Z) z)

 where inout means 'the greatest common constancy of all variables that 
 are declared inout'.  Again, not in love with inout, but inout kind of 
 reads 'you get out what you put in', and it is a 'free' keyword.

an alias to the qualifier) by always calling that alias "inout" or whatever keyword name. This problem becomes apparent when you try to nest declarations: inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.

That would be invalid. You need at least one inout argument that can be implicitly casted to const. As far as I know that can't be done with a function pointer. If you want to pass in a function pointer to a function which uses the inout constructs, then the inouts become literal inout. i.e.: inout(Y) foo(inout(Z) x) {...} inout(X) fun(inout(Z) argtofoo, inout(Y) function(inout(Z)) gun) {...} invariant(Z) z = cast(invariant) new Z; auto x = fun(z, &foo);// x is of type invariant(X) Maybe you had a different behavior in mind?

Well this: inout(X) fun(inout(Z) argtofoo, inout(Y) function(inout(Z)) gun) {...} could mean two things: 1. "Bind all inout occurrences to a qualifier and resolve the signature", or 2. "Whenever inout occurs in a function parameter, don't bind it." It's not that clear-cut which is the one to be chosen, and that will have to be a convention because the syntax is ambiguous. Andrei
Oct 14 2008
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 I'll reiterate my proposal:

 inout(X) f(inout(Y) y, inout(Z) z)

 where inout means 'the greatest common constancy of all variables that 
 are declared inout'.  Again, not in love with inout, but inout kind of 
 reads 'you get out what you put in', and it is a 'free' keyword.

bind an alias to the qualifier) by always calling that alias "inout" or whatever keyword name. This problem becomes apparent when you try to nest declarations: inout(X) fun(inout(Y) function(inout(Z)) gun); Now you tell me which inout goes where.

That would be invalid. You need at least one inout argument that can be implicitly casted to const. As far as I know that can't be done with a function pointer. If you want to pass in a function pointer to a function which uses the inout constructs, then the inouts become literal inout. i.e.: inout(Y) foo(inout(Z) x) {...} inout(X) fun(inout(Z) argtofoo, inout(Y) function(inout(Z)) gun) {...} invariant(Z) z = cast(invariant) new Z; auto x = fun(z, &foo);// x is of type invariant(X) Maybe you had a different behavior in mind?

Well this: inout(X) fun(inout(Z) argtofoo, inout(Y) function(inout(Z)) gun) {...} could mean two things: 1. "Bind all inout occurrences to a qualifier and resolve the signature", or 2. "Whenever inout occurs in a function parameter, don't bind it." It's not that clear-cut which is the one to be chosen, and that will have to be a convention because the syntax is ambiguous.

OK, how about this: inout(Y) function(inout(Z)) f = &foo; auto x = fun(z, f); What is f? Is this not the same as passing &foo directly? -Steve
Oct 14 2008
prev sibling parent "Bill Baxter" <wbaxter gmail.com> writes:
On Thu, Oct 16, 2008 at 5:57 AM, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:
 Bill Baxter wrote:
 If the top of the block is off the screen it's not necessarily obvious
 that it's a block.

By the same argument it's not obvious whether you're in a class, function, template etc. definition. All of them do change the context of what's going on.

The fact that I have to keep one thing in mind does not make an excuse for creating several other things I also have to keep in mind. I've heard the human mind can keep about 7 or 8 things active at once. It's a limited number. So the more info you can keep local the better. Same reason we don't declare all our variables at the tops of functions any more. Declaration at the point of use makes the type more readily available at the point in the code where you need to know it. Anyway, I've got no problem with collecting things in attribute blocks like that, as long as the blocks stay less than roughly a page in length. Similar to the commonly use rule of thumb for function length. --bb
Oct 15 2008
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 One other syntax that Janice proposed (and I later put into a bugzilla), 
 is to use the dead keyword inout.  Meaning, what you send in is what you 
 get out.  ref already completely replaces inout, so there is no need to 
 keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not 
 requiring a new keyword.

Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o)

One thing that should be mentioned, my original post implied that the 'base type' is implied to be const (since originally, this is the only problem I was trying to solve). So in my example: inout(char)[] stripl(inout(char)[] s); s is implied to be const(char) during the function. With your interpretation of inout meaning 'anything that is derived from', then I suppose the correct syntax should be: inout(const(char))[] stripl(inout(const(char))[] s); With your special trick of implying the return type looking like this: inout[] stripl(inout(const(char))[] s); Not as appealing, but it still works. -Steve
Oct 14 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Tue, 14 Oct 2008 21:04:01 +0400, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 "KennyTM~" wrote
 Andrei Alexandrescu wrote:
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant  
 functions
 without actually adding an explicit feature to the language.  
 Consider:

 typeof(s) stripl(const(char)[] s);

As another point on this, I think someone else mentioned it, but I can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

I agree. We need to look for a better notation.
 I understand the need to not change the language, but I think most  
 would
 prefer a syntax where the type modifier is specified on at least the
 argument.  People are going to be extremely confused when they can't
 treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made without
 changing the language, then I think it is more important to have this
 feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a  
 bugzilla),
 is to use the dead keyword inout.  Meaning, what you send in is what  
 you
 get out.  ref already completely replaces inout, so there is no need  
 to
 keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not
 requiring a new keyword.

Also I guess: class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b); I think this could work and doesn't look half bad. Of course, you'll be tasked with addressing protests about yet another D1/D2 incompatibility. :o) Andrei



I think that basic idea is very good! (for some reason I was bound to constness issue and didn't think about the latter example).
 I'm afraid the meaning of inout here is very unclear without  
 explanation.

You are correct. It means 'what you send in as input gets returned as output.' In reality, it's not the best name for this type of thing, but it has the benefit of using a defunct keyword -- no new keywords necessary. I can't think of a really good keyword that is short and would not require explanation. Perhaps you can? Then we have to weigh the benefits of having a new keyword vs. having to post an explanation as to what it means. My guess is we'll have to post an explanation no matter what. -Steve

I have previously proposed the 'same' keyword: same(Base) foo(same(Base) bar) { bar.callSomeBaseMethod(); return bar; } foo(new Derived()); interface IClonable { same(this) clone(); } It is short and very descriptive. Frankly speaking, I don't see the difference between inout or some other keyword. You either ditch inout and introduce FOO or obsolete inout and reuse it. Both have similar effect: some code is broken and needs fixing (because inout doesn't work anymore), a new keyword is introduced to denote the feature. The only difference is a possible name collision that any new keyword might introduce. But it is nothing to worry about, because short and meaningful keywords are more important than some potentially broken (and easily fixable) code for the language in a long run.
Oct 14 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Tue, 14 Oct 2008 22:16:33 +0400, KennyTM~ <kennytm gmail.com> wrote:

 “same” seems to be a common identifier though..

 “inout” already has an equivalent keyword. It is called “ref”.

Yeah, I know :) Thanks anyway. But is there any reason to worry about name collision? I just grep'd about 20Mb of C++ source code and didn't find *a single* usage of `same' outside of the comments.
Oct 14 2008
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Tue, 14 Oct 2008 18:51:56 +0200, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant  
 functions without actually adding an explicit feature to the  
 language. Consider:

 typeof(s) stripl(const(char)[] s);

can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

 I understand the need to not change the language, but I think most  
 would prefer a syntax where the type modifier is specified on at  
 least the argument.  People are going to be extremely confused when  
 they can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made  
 without changing the language, then I think it is more important to  
 have this feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a  
 bugzilla), is to use the dead keyword inout.  Meaning, what you send  
 in is what you get out.  ref already completely replaces inout, so  
 there is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not  
 requiring a new keyword.

class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);


Accept and return any subtype of Base. Andrei

I was first wondering how this would treat things like inout foo(Base b, int n); But then I noticed it saying "inout Base b", so only the argument(s) marked inout are considered for the return type? Also, this means a maximum of one inout argument type per function, right? As for the keyword, I feel inout is better than typeof, and good enough. Not perfect, but good enough. -- Simen
Oct 14 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Tue, 14 Oct 2008 22:38:58 +0400, Simen Kjaeraas  
<simen.kjaras gmail.com> wrote:

 On Tue, 14 Oct 2008 18:51:56 +0200, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:

 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant  
 functions without actually adding an explicit feature to the  
 language. Consider:

 typeof(s) stripl(const(char)[] s);

can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

 I understand the need to not change the language, but I think most  
 would prefer a syntax where the type modifier is specified on at  
 least the argument.  People are going to be extremely confused when  
 they can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made  
 without changing the language, then I think it is more important to  
 have this feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a  
 bugzilla), is to use the dead keyword inout.  Meaning, what you send  
 in is what you get out.  ref already completely replaces inout, so  
 there is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not  
 requiring a new keyword.

class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);


Accept and return any subtype of Base. Andrei

I was first wondering how this would treat things like inout foo(Base b, int n); But then I noticed it saying "inout Base b", so only the argument(s) marked inout are considered for the return type?

Yes
 Also, this means a maximum of one
 inout argument type per function, right?

No, there were many example of multiple (and even none) inout function arguments.
 As for the keyword, I feel inout is better than typeof, and good enough.  
 Not perfect, but good enough.

My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.
Oct 14 2008
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Tue, 14 Oct 2008 20:57:56 +0200, Denis Koroskin <2korden gmail.com>  
wrote:

 On Tue, 14 Oct 2008 22:38:58 +0400, Simen Kjaeraas  
 <simen.kjaras gmail.com> wrote:

 On Tue, 14 Oct 2008 18:51:56 +0200, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:

 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant  
 functions without actually adding an explicit feature to the  
 language. Consider:

 typeof(s) stripl(const(char)[] s);

can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

 I understand the need to not change the language, but I think most  
 would prefer a syntax where the type modifier is specified on at  
 least the argument.  People are going to be extremely confused when  
 they can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made  
 without changing the language, then I think it is more important to  
 have this feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a  
 bugzilla), is to use the dead keyword inout.  Meaning, what you  
 send in is what you get out.  ref already completely replaces  
 inout, so there is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of not  
 requiring a new keyword.

class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);


Accept and return any subtype of Base. Andrei

I was first wondering how this would treat things like inout foo(Base b, int n); But then I noticed it saying "inout Base b", so only the argument(s) marked inout are considered for the return type?

Yes
 Also, this means a maximum of one
 inout argument type per function, right?

No, there were many example of multiple (and even none) inout function arguments.

I said argument types, as inout(MyClass) in inout foo(inout(MyClass) a, inout(MyClass) b).
 As for the keyword, I feel inout is better than typeof, and good  
 enough. Not perfect, but good enough.

My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.

I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){} -- Simen
Oct 14 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Tue, 14 Oct 2008 23:12:43 +0400, Simen Kjaeraas  
<simen.kjaras gmail.com> wrote:

 On Tue, 14 Oct 2008 20:57:56 +0200, Denis Koroskin <2korden gmail.com>  
 wrote:

 On Tue, 14 Oct 2008 22:38:58 +0400, Simen Kjaeraas  
 <simen.kjaras gmail.com> wrote:

 On Tue, 14 Oct 2008 18:51:56 +0200, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:

 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I discussed with Walter a variant that implements equivariant  
 functions without actually adding an explicit feature to the  
 language. Consider:

 typeof(s) stripl(const(char)[] s);

can't find the post. I don't like the way this looks. The way it reads is 'stripl returns the same type as s', but really, the typeof(s) is actually modifying the type of the argument also. This seems very unintuitive.

 I understand the need to not change the language, but I think most  
 would prefer a syntax where the type modifier is specified on at  
 least the argument.  People are going to be extremely confused  
 when they can't treat 's' like a normal const(char)[].

 If the ultimate result is that no intuitive syntax can be made  
 without changing the language, then I think it is more important  
 to have this feature than to not change the language.

 One other syntax that Janice proposed (and I later put into a  
 bugzilla), is to use the dead keyword inout.  Meaning, what you  
 send in is what you get out.  ref already completely replaces  
 inout, so there is no need to keep it under its current meaning:

 inout(char)[] stripl(inout(char)[] s);

 I'm not in love with this completely, but it has the benefit of  
 not requiring a new keyword.

class C { Range!(inout(C)) foo() inout; } And also: class Base {} class Derived : Base {} inout foo(inout Base b);


Accept and return any subtype of Base. Andrei

I was first wondering how this would treat things like inout foo(Base b, int n); But then I noticed it saying "inout Base b", so only the argument(s) marked inout are considered for the return type?

Yes
 Also, this means a maximum of one
 inout argument type per function, right?

No, there were many example of multiple (and even none) inout function arguments.

I said argument types, as inout(MyClass) in inout foo(inout(MyClass) a, inout(MyClass) b).

Isn't the following example good enough? inout(A) min(inout(A1) a1, inout(A2) a2); // 3 different types How about another one: class Foo { inout this(inout(char)[] chars, inout(int)[] ints) { this.chars = chars; this.ints = ints; } char[] chars; int[] ints; } inout(Foo) createFoo(inout(char)[] chars, inout(int)[] ints) { return new Foo(chars, ints); } auto foo = createFoo("Hello!", [1, 1, 2, 3, 5, 8]); // typeof(foo) == invariant(Foo) The basic idea is to get some arrays of different types and return some other (corresponding) object. Their types may be completely unrelated! Also this one: class Foo { inout(Bar) getBar() inout(this) { return bar; } // takes 'this' of type inout(Foo) an returns inout(Bar). Types are unrelated. private Bar bar; }
 As for the keyword, I feel inout is better than typeof, and good  
 enough. Not perfect, but good enough.

My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.

I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){}

Ouch! This one hurts. Yet it is inconsistent with multiple argument types. Besides, what advantages of it do you see over "inout min(inout(A) a1, inout(A) a2);"?
Oct 14 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Tue, 14 Oct 2008 23:17:18 +0400, David Gileadi <foo bar.com> wrote:

 Steven Schveighoffer wrote:
 "KennyTM~" wrote

 By the explanation argument, I actually mean what a programmer first  
 sees this feature think of. Presented with Andrei's "typeof" syntax, I  
 can at least guess what the function looks like (it returns the same  
 type as "s", OK)

means that the return value is the same as what you used as s to call the function, not the type of s (which is whatever the function declares s as). The return type changes depending on what you call it with. e.g.: typeof(s) foo(const(char)[] s); .... char[] x = "hello".dup; auto x2 = foo(x); // x2 is typed as char[], even though s is declared as const(char)[]
 ; but with "inout" I just stopped with "What the heck is going on?!".

prompt you to look up how inout works in the docs. I'm not saying that inout is the ideal keyword. I'm saying that this is a novel enough concept that probably people are not going to intuitively see what is going on no matter what keyword is used. They have to learn what it's doing by reading docs. Using typeof as Andrei defined seems intuitively to mean something entirely different, and most likely will not prompt a lookup of the docs (until it doesn't behave as expected). Plus it doubles the meaning of yet another keyword, which I hate... e.g. what did you think was happening when you first saw 'mixin'? Did you intuitively think 'oh, this must take what I give it as a compile-time generated string and compile it into actual code'. Probably not, so is mixin a bad keyword choice for that feature because you didn't intuitively think of it? -Steve

FWIW I agree with KennyTM~ here--typeof(s) made sense to me, including its meaning that you describe above. This, even though it conflicts with the keyword's current meaning. I suppose it's because "typeof(s)" could stand for "some type of s", i.e. some child type of s. The thing that concerns me about using typeof(s) is your earlier comment:
 I understand the need to not change the language, but I think most  
 would prefer a syntax where the type modifier is specified on at least  
 the argument.  People are going to be extremely confused when they  
 can't treat 's' like a normal const(char)[].

-Dave

Using typeof, expression would get way too complex and involve some magic template trickery like PassQual!() to pass constancy qualifiers from one variables to another. Besides, typeof proved to be insufficient to solve the problem: typeof(t1) min(T)(T t1, typeof(t1) t2); T t1 = ...; auto t2 = ...; auto t3 = min(t1, t2); // what is the type of t3? Is it T per typeof(t1)? // Answer is - it depends.. on t2: T t1 = ...; const(T) t2 = ...; auto t3 = min(t1, t2); // typeof(t3) == const(T)
Oct 14 2008
prev sibling next sibling parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Wed, 15 Oct 2008 01:18:40 +0400, Robert Fraser  
<fraserofthenight gmail.com> wrote:

 Andrei Alexandrescu wrote:
 Yah, that's where the PassQual template is needed. I'd agree without  
 joy that, if handling true equivariance is too unwieldy, we can resign  
 to solving only equivariance in qualifier.

FWIW, people have done without type eqivariance w/o complaint since static typing was invented. People have been complaining about const equivariance since at least C++ and D makes it worse.

People didn't complain that they were nude until someone discovered it :) I mean, human needs grow over time as they discover something useful.
Oct 14 2008
parent "Bill Baxter" <wbaxter gmail.com> writes:
On Thu, Oct 16, 2008 at 4:39 AM, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:
 Bill Baxter wrote:
 On Thu, Oct 16, 2008 at 4:10 AM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 Bill Baxter wrote:
 Hmmm, looking at this:

 class Owner
 {
  const?(A) a() const?{...}
  const?(B) b() const?{...}
  const?(C) c() const?{...}
  const?(D) d() const?{...}
  const?(E) e() const?{...}
  const?(F) f() const?{...}
  const?(G) g() const?{...}
  const?(H) h() const?{...}
  const?(I) i() const?{...}
  const?(J) j() const?{...}
 }

 makes me think if we go with that syntax, Andrei is sooner or later
 going to complain about his D code asking him too many questions.  :-)
 Eh, I guess he can edit the emacs mode to display const? as
 smiley-faces or something.  :-) :-)

At this point Walter will intervene with: class Owner { const? { A a() {...} B b() {...} C c() {...} D d() {...} E e() {...} F f() {...} G g() {...} H h() {...} I i() {...} J j() {...} } } which isn't half bad.

I was thinking about that too. But I'm not a big fan of blocks that change the meaning of declarations in a non-local way. It looks quite readable in toy examples, but after you insert all the bodies and documentation comments for everything in the block, it becomes very easy to lose sight of the tag way back at the beginning of the block.

I disagree. A block is a block is a block. Indentation shows it's not non-local. I understand how you could formulate an argument against labels. They do change meaning in a non-obvious way. Block indentation makes all the difference. I'm in fact surprised to hear that from a pythonboi :o).

If the top of the block is off the screen it's not necessarily obvious that it's a block. And even if you do realize it's a block, you don't know what special attributes are in play without scrolling back up to see what it says. That's why I don't like em. Not sure what Python has to do with it. I think a method signature in Python can pretty much always be read and understood without having to look at some tag that exists further up the screen. Closest thing I can think of in Python is the decorators, but I think those always have to appear right before the method signature that they modify. --bb
Oct 15 2008
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Tue, 14 Oct 2008 21:30:47 +0200, Denis Koroskin <2korden gmail.com>  
wrote:

 On Tue, 14 Oct 2008 23:12:43 +0400, Simen Kjaeraas  
 <simen.kjaras gmail.com> wrote:

 On Tue, 14 Oct 2008 20:57:56 +0200, Denis Koroskin <2korden gmail.com>  
 wrote:

 On Tue, 14 Oct 2008 22:38:58 +0400, Simen Kjaeraas  
 <simen.kjaras gmail.com> wrote:

 Also, this means a maximum of one
 inout argument type per function, right?

No, there were many example of multiple (and even none) inout function arguments.

I said argument types, as inout(MyClass) in inout foo(inout(MyClass) a, inout(MyClass) b).

Isn't the following example good enough? inout(A) min(inout(A1) a1, inout(A2) a2); // 3 different types

It is, now that I reread it, and see that it's not all A's. :p
 As for the keyword, I feel inout is better than typeof, and good  
 enough. Not perfect, but good enough.

My concern is that it than 'inout' doesn't express that all the arguments marked as inout as bound to each other and actual type is deduced from them all. There is a relationship between their types. inout(A) min(inout(A1) a1, inout(A2) a2); A1 a1; A2 a2; const(A1) ca1; const(A2) ca2; invariant(A1) ia1; invariant(A1) ia2; auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1 nor ia2 of that type auto a = min(a1, a2); // typeof(a) == A You see, in the example above the return type is changed because type of 2nd argument is changed. Not only the return type but types of all the arguments, too. auto a = min(ia1, ia2); // typeof(a) == invariant(A). Now return type is changed per 1st argument type change.

I don't really see this as a problem. Returning mutable or invariant would be worse. Anyways, if you need the two arguments to be of the same type, I'd prefer this syntax: inout min(inout(A) a1, typeof(a1) a2){}

Ouch! This one hurts. Yet it is inconsistent with multiple argument types.

With my newfound knowledge of this working on multiple argument types, I agree.
 Besides, what advantages of it do you see over "inout min(inout(A) a1,  
 inout(A) a2);"?

Mainly that it says "the type of a2 is the same as the type of a1", while inout says "the return type is somehow dependant upon the types of a1 and a2" (probably the lowest common denominator). If I feed mutable and invariant data into a function, and can receive either of those back, I expect it to be const. If I want to feed data of the same type/ constancy to a function, and want it to inform me if I'm doing it wrong, I would state that they should be of the same type/constancy, not that the return value is dependant upon both of them. -- Simen
Oct 14 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Wed, 15 Oct 2008 00:31:15 +0400, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 What about returning a member?  i.e.:

 inout(typeof(s.ptr)) getptr(inout const(char)[] s) { return s.ptr;}

Yah there is a recurring problem - we need to find a notation that works nicely and expressively for member functions as well as free functions. For free functions an easy-to-explain-and-understand way is to have "inout" mark the incoming / outgoing TYPE entirely, not only its qualifier. Then: inout stripl(inout const(char)[] s); means: accept any subtype of const(char)[] and call that type "inout" in the return type. Then it's easy to access dependent stuff: typeof(inout.ptr) at(inout const(char)[] s); Notice that for one-parameter functions there's not even a need to specify the inout in the argument list because it's unambiguous: inout stripl(const(char)[] s); typeof(inout.ptr) at(const(char)[] s); When you get into member functions things aren't quite nice: class A { private int a; inout clone() inout; // ehm typeof(&inout.a) getPtrToA() inout; // ehm }
 is that what you had in mind?

 this syntax is going to be used often, since it's what you would use  
 for
 an accessor.  So it should be simple to understand if possible.

Yah I agree. At this point I don't have any solid solution for notation... please continue rolling out ideas.

Damn, I think there is some confusion here, because we are trying to solve two related, but unequal problems. First is const equivariance (is that the right term?). I want to specify a function that treats an argument as const, but does not affect the const contract that the caller has with that argument (i.e. my original scoped const proposal). The second is type equivariance. I want to specify that I will treat an argument as a base type, but I will not change the derived type that the caller is using. These are similar, if you consider that const is a 'base type' of its mutable or invariant version. But there is one caveat that is hard to explain using this terminology. const is a type modifier, not a type. So it's not really a base type of a type, and it can be applied to any type in existance. Not the same as specifying a base type. So the first requirement (const equivariance) does not require that I return a derivative of the argument, it requires that I return an argument that is part of the input, but contains the same const contract as the passed in variable at the *call site*. It does not require that the return type is derived from the input. It is this first requirement that is the only requirement necessary for functions which return unrelated types from their arguments. i.e., for an accessor, which is not returning something of the same type as its argument, the only thing that is important to keep the same at the call site is the const modifiers. For example, you can return a base type that is automatically converted to a derived type, but that must be accomplished with an overridden function. That is already handled using covariance, and I really don't think there is a way to enforce this in the compiler. An example, a linked list. class Link { private Link next; Link getNext() { return next;} } class DerivedLink : Link { int someExtraData; } We currently solve this by adding a covariant getNext() to DerivedLink. But would it be enough to just change the getNext function in the base type to: inout getNext() { return next; } inout; I don't think it can be enforced. Because some other Link function can set next to another type of Link (possibly a base Link). It is up to the designer of DerivedLink to ensure that next always points to a descendent of DerivedLink. In this case, I think it's a requirement to use covariant functions. However, it *would* be advantageous to declare getNext as not altering the const contract of the caller. That is, it returns the same constancy that it is called with (in this example, inout implies const): inout(Link) getNext() { return next; } inout; For the second requirement, I can see value in things like call-chaining: class C { inout doSomething() inout { return this;} } class D : C { inout doSomethingElse() inout { return this;} } auto d = new D; d.doSomething().doSomethingElse(); However, the return value MUST be exactly the same value as the input. If it's only the same type, we lose all compiler enforcibility. So does it make sense to split these two requirements? I propose to use inout only to deal with const contracts, and use another syntax to signify which parameter will be returned: inout(X) foo(inout(Y) y); // X and Y are unrelated types a stab at 'returning this exact parameter' X foo(return X x); // The return value from foo will be x, so the compiler is free to upcast automatically. inout(X) foo(return inout(X) x); // combination, signifies that I will return x, and I promise not to modify it in the function. With this scheme, I think possibly inout might not be as clear... -Steve

I think you brought very interesting topic with a second example! However, I believe it may be solved easily. Let's start with your example: class A { final A foo() { // do something return this; } } class B : A { } It is obvious that for any type B which is a subclass of A it is safe to do the following: B b = ...; b = cast(B)b.foo(); // this is guarantied to be safe This, however, is only true if the foo() method is final (I talk about current D for now). So you say, why not to allow this without casting by making some rules so that compiler could ensure code correctness. I believe this is possible and the method signature could be as Andrei proposed: class A { final typeof(this) foo() { // do something return this; } } This is consistent with the behaviour: return type is always covariant with the this type at callsite. Once again, this operation is guarantied to be safe if method returns 'this' and is final. But we want to take it one step forward! For example, we may want to make it virtual, not final. In this case we can lift the restriction to return this as well, subject to the following constrains: 1) Method may be made final if and only if it returns this. 2) If base class method is not final then the derived class have to override the method. 3) Class may return some other pointer, not only this, but it should statically check that the pointer type is covariant with this type. Note1: Class could also be allowed to not override non-final method of the base class if it returns this (it is safe to do so). But this greatly increases checking rules complexity. In some cases body might not be available so compiler can't whether we return this and therefore are forced to override it anyway. Note2: method that returns null might be also allowed to be final. Let's put some more examples: module example1; class A { final typeof(this) foo() { return this; } } class B // ok { void bar(); } (new B()).foo().bar(); module example2; class A { final typeof(this) foo1(); // body is in some other file, guarantied to return this typeof(this) foo2(); } class B { typeof(this) foo2() { super.foo2(); return this; } void bar() {} } (new B()).foo2().foo1().bar(); module example3; class Link { typeof(this) next() { return _next; // ok, typeof(next) == typeof(this). Note that we can't make this method final } private Link _next; } class DerivedLink1 : Link { int someExtraData; // fails to compile: class doesn't override typeof(this) next(); } class DerivedLink2 : Link { int someExtraData; typeof(this) next() { return cast(DerivedLink)super.next(); // ok, might return null } } auto data = (new DerivedLink2()).next().next().someExtraData; module example4; class A { typeof(this) clone() { return new A(this); // note that we can't make this method final } } class B1 : A { // fails to compile, *have to* override clone() } class B2 : A { /*final*/ typeof(this) clone() { return null; // it's ok. But shall we allow final methods to return null? } } class B3 : A { typeof(this) clone() { return new A(); // error, is (A : typeof(this)) is false } } class B4 : A { typeof(this) clone() { return new B4(); } void bar() {} } (new B4()).clone().clone().bar(); I think this may work.
Oct 14 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Wed, 15 Oct 2008 00:59:44 +0400, Robert Fraser  
<fraserofthenight gmail.com> wrote:

 Denis Koroskin wrote:
 [snip]
  My concern is that it than 'inout' doesn't express that all the  
 arguments marked as inout as bound to each other and actual type is  
 deduced from them all. There is a relationship between their types.
  inout(A) min(inout(A1) a1, inout(A2) a2);
  A1 a1;
 A2 a2;
  const(A1) ca1;
 const(A2) ca2;
  invariant(A1) ia1;
 invariant(A1) ia2;
  auto a = min(a1, ia2); // typeof(a) == const(A) even though neither a1  
 nor ia2 of that type
 auto a = min(a1, a2);  // typeof(a) == A
  You see, in the example above the return type is changed because type  
 of 2nd argument is changed. Not only the return type but types of all  
 the arguments, too.
  auto a = min(ia1, ia2);  // typeof(a) == invariant(A). Now return type  
 is changed per 1st argument type change.

Er.... isn't that the point. If I pass two mutable arguments to min(), I want a mutable back. If I pass two invariant (immutable, hopefully!) arguments to min() I want an invariant back. Otherwise, I want a const back. That's why this syntax is needed at all.

Yes, it's all about this. My point is that 'inout' keyword is not very descriptive in this situation. The behaviour, however, is fine.
Oct 14 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Tue, 14 Oct 2008 23:45:32 +0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Simen Kjaeraas wrote:
 I don't really see this as a problem. Returning mutable or invariant  
 would be worse. Anyways, if you need the two arguments to be of the  
 same type, I'd prefer this syntax:
    inout min(inout(A) a1, typeof(a1) a2){}

IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei

IMHO, the solution should be consistent and general enough to cover templates, too, as well as zero, single and multiple input arguments. Templates would benefit from it, too, reducing the generated file size (there are many complains about this issue).
Oct 14 2008
prev sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Wed, 15 Oct 2008 05:30:52 +0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Denis Koroskin wrote:
 On Tue, 14 Oct 2008 23:45:32 +0400, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:

 Simen Kjaeraas wrote:
 I don't really see this as a problem. Returning mutable or invariant  
 would be worse. Anyways, if you need the two arguments to be of the  
 same type, I'd prefer this syntax:
    inout min(inout(A) a1, typeof(a1) a2){}

IMHO we could simplify by discounting min. The major need is to pass the type of one argument only. Min and max are templates anyway, and for templates we have other ways to make things work. Andrei

templates, too, as well as zero, single and multiple input arguments. Templates would benefit from it, too, reducing the generated file size (there are many complains about this issue).

If you can find a solution that is simple and general enough, my hat is off to you. Andrei

We are here to discuss it. I made many suggestions, but I don't know whether they fine, too complex or just lame untill someone comments it (interesting enough, some reply without reading). What's wrong with typeof(this) (I just generalized your clone() idea combined with implicit upcasting suggested by Steven)? The "I don't like it" comment would be useful, too. What's wrong with inout/whatever? So far you brought just one 'problematic' example: inout(C) foo(inout(B) function(inout(A)) fn); I believe this is as meaningless as inout(A) foo(); // what does it return? A, const(A) or invariant(A)? and thus should be statically disallowed. I mean, inout(return) doesn't have any sense unless a function accepts some inout(parameter): inout(B) foo(inout(A) a); // ok // your example with an added inout(in) parameter. Now it is fine inout(C) foo(inout(C) c, inout(B) function(inout(A) a) fn); In the last example, constancy of return value matches constancy of input parameter.
Oct 14 2008
prev sibling next sibling parent Benji Smith <dlanguage benjismith.net> writes:
Andrei Alexandrescu wrote:
 What do you think? I'm almost afraid to post this.

I vote "no" on the whole feature. As far is I can tell, it doesn't add any new capabilities to the language, except for working around difficult corner cases in the cost system, and I think it's a mistake to add features whose sole purpose is to workaround the deficiencies in other features. If the const system has holes in it, I vote for patching those holes by redesigning the const system, not by adding a workaround. Or maybe I'm mistaken. Are there any compelling examples which don't revolve around constancy? --benji
Oct 14 2008
prev sibling parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Many functions return one of their parameters regardless of the way it  
 was qualified.

 char[] stripl(char[] s);
 const(char)[] stripl(const(char)[] s);
 invariant(char)[] stripl(invariant(char)[] s);

 Stripl is not a particularly good example because it needs to work on  
 wchar and dchar too, but let's ignore that aspect for now.

 There's been several proposals in this group on tackling that problem.

 In unrelated proposals and discussions, people mentioned the need for  
 functions that return the exact type of this:

 class A { A clone(); }
 class B : A { B clone(); }

 How can we declare A.clone such that all of its derived classes have it  
 return their own type?

 It took me a while to realize they are really very related. This is easy  
 to figure out if you think that invariant(char)[] and char[] are  
 subtypes of const(char)[]!

 I discussed with Walter a variant that implements equivariant functions  
 without actually adding an explicit feature to the language. Consider:

 typeof(s) stripl(const(char)[] s);

 This signature states that it returns the same type as an argument. I  
 propose that that pattern means stripl can accept _any_ subtype of  
 const(char)[] and return that exact type. Inside the function, however,  
 the type of s is the type declared, thus restricting its use.

 I need to convince myself that function bodies of this type can be  
 reliably typechecked, but first I wanted to run it by everyone to get a  
 feel of it.

 Equivariant functions are not (necessarily) templates and can be used as  
 virtual functions. Only one body is generated for one equivariant  
 function, unless other template mechanisms are in vigor.

 Here are some examples:

 a) Simple equivariance

 typeof(s) stripl(const(char)[] s);

 b) Parameterized equivariance

 typeof(s) stripl(S)(S s) if (isSomeString!S);

 c) Equivariance of field:

 typeof(s.ptr) getpointer(const(char)[] s);

 d) Equivariance inside a class/struct declaration:

 class S
 {
      typeof(this) clone();
      typeof(this.field) getfield();
      int field;
 }

 What do you think? I'm almost afraid to post this.


 Andrei

Now that I thought about it a little more (please, see and comment my post about typeof(this) nearby), I agree that the issues are related. However, the best solution could be a combination of both. For example, I agree that interface IClonable should be as follows: interface IClonable { typeof(this) clone() const; } But how about this scenario: class Bar : IClonable { this(const(Bar) proto) { ... } typeof(this) clone() const { return new Bar(this); } } const(Bar) bar = ...; auto barClone = bar.clone(); // typeof(barClone) is const(Bar)! Knowing you, I expect you will suggest to change the IClonable interface as follows: interface IClonable { DropConst!(typeof(this)) clone() const; } Although I think that templates would be very useful here, I think in many cases they make things more complex than it could be. I believe things could get better if we would decouple the issue into two orthogonal concepts: a) typeof(this) is a type of 'foo' in a foo.bar() expression without any qualifiers applied. b) 'same' gives the qualifier which is common across all arguments 'same' is applied to (for example, void foo(same(A) a, same(B) b, same(C) c);, same is none, const or invariant). Besides, I would be great if typeof(this) would be interchangeble with the actual type of this with little or no loss of meaning. For example, with typeof(this) returning class type with all the qualifiers and no 'same', we would write: class Foo { typeof(this._bar) bar() const { return _bar; } private Bar _bar; } This have two negative sides: 1) we expose our internal structure 2) we need some template trickery if there is no member of type Bar inside Foo: interface Foo { PassQual!(typeof(this), Bar) bar(); } 3) the typeof(this._bar) is not interchangeble with Bar (qualifiers are lost) and function doesn't work anymore (can't cast const(Bar) to Bar implicitly) I also believe it is important to do the trick *without* templates, because there are people that don't like templates or don't know about them (e.g. starters). With 'same' returning qualifiers of the arguments (hidden ones included), we could write as follows: interface Foo { same(Bar) bar() same(this); } interface IClonable { typeof(this) clone(); // nothing else is needed! typeof(this) doesn't contain qualifiers. } note that changing typeof(this) -> IClonable works well here, too. Another example. We will use both of the concepts in pair now: class A { same(typeof(this)) foo() same(this) { // do something return this; } } class B : A { same(typeof(this)) foo() same(this) { super.foo(); bar(); return this; } void bar() {} } Little is changed if we replace typeof(this) with A and B in first and second classes respectively. A short summary: - typeof(s) is not very suitable for deducing qualifiers. It is quite ugly and not intuitive. See Christopher's comment: On Wed, 15 Oct 2008 04:09:59 +0400, Christopher Wright <dhasenan gmail.com> wrote:
 typeof(s) foo(const(char)[] s)

 This looks like it's shorthand for:
 const(char)[] foo(const(char)[] s)

 And the examples with typeof(A), where A is a type, just didn't make any  
 sense.

- There are cases where typeof(s) can't be applied at all (e.g. multiple argument parameters). inout/same is essential here - typeof(s) requires templates in way too many cases. same could be used to pass qualifiers without templates - dividing the concepts into two orthogonal may make it both easier to implement and easier to understand - this will also not overload the meaning of typeof() with too much different concepts This is just an idea, what do you think?
Oct 14 2008
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Denis Koroskin" wrote
 Now that I thought about it a little more (please, see and comment my post 
 about typeof(this) nearby), I agree that the issues are related.

 However, the best solution could be a combination of both.

 For example, I agree that interface IClonable should be as follows:

 interface IClonable { typeof(this) clone() const; }

No. IClonable should be: IClonable clone() const; or if you prefer: Object clone() const; It can't be anything else, because IClonable cannot define how many levels deep it goes. For example, if I have: class A : IClonable { typeof(this) clone() const { return new A();} } class B : A { } B b = new B; B x = b.clone(); Oops, b.clone() really only returns A, so this should fail. The real signature in A should be: class A : IClonable { A clone() const { return new A(); } } IClonable is a perfect candidate for covariant functions, which is already defined. -Steve
Oct 14 2008
next sibling parent reply Benji Smith <dlanguage benjismith.net> writes:
Steven Schveighoffer wrote:
 class A : IClonable
 {
   typeof(this) clone() const { return new A();}
 }
 
 class B : A
 {
 }
 
 B b = new B;
 B x = b.clone();
 
 Oops, b.clone() really only returns A, so this should fail.  The real 
 signature in A should be:

No, b.clone() definitely returns an instance of B. It's purely coincidental (from the caller's perspective) that B's implementation is identical to that of A. Though I always thought the correct implementation of ICloneable was like this: interface ICloneable(T) { T clone(); } A : ICloneable!(A) { public A clone() { ... } } I really don't see what all the fuss is about with the equivariance stuff, except that it introduces a lot of confusing rules to fix the holes in the (already complex and confusing) const system. Any other use case where equivariance might apply (other than the const stuff) seems to already have a simple, straightforward solution using either templates, interfaces, or plain old overloads. --benji
Oct 14 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Benji Smith wrote:
 I really don't see what all the fuss is about with the equivariance 
 stuff, except that it introduces a lot of confusing rules to fix the 
 holes in the (already complex and confusing) const system.

Please stop perpetuating misunderstanding and apprehension about the const system through statements strong on opinion and vague on detail. If you're willing to mention holes, complexity, and confusion about the const system, please explain where the holes are, where the complexities lie, and what aspects you are confused about. Suggestions for fixing said issues would also be welcome. Andrei
Oct 14 2008
parent reply Benji Smith <dlanguage benjismith.net> writes:
Andrei Alexandrescu wrote:
 Benji Smith wrote:
 I really don't see what all the fuss is about with the equivariance 
 stuff, except that it introduces a lot of confusing rules to fix the 
 holes in the (already complex and confusing) const system.

Please stop perpetuating misunderstanding and apprehension about the const system through statements strong on opinion and vague on detail. If you're willing to mention holes, complexity, and confusion about the const system, please explain where the holes are, where the complexities lie, and what aspects you are confused about. Suggestions for fixing said issues would also be welcome.

Fair enough. Here are the reasons I describe the const system as "complex": * Comparable code without const qualifiers contains quantatitively fewer semantic constructs than code that uses those qualifiers. Smaller function prototypes are easier to read and comprehend: char[] join(char[] str2, char[] str2) { ... } const(char)[] join(const(char)[] str1, const(char)[] str1) { ... } * Nestable const type constructors require me to keep a more detailed mental model of the type system. Am I using a "const(char)[]" or a "const(char[])"? * Remembering the difference between "const" and "invariant" requires more mental energy than using a system (D1) where such distinctions don't exist. * The idea that "const" is a supertype of "mutable" and "invariant" is utterly bizarre to me, since it violates the "is a" rule of type hierarchies. I understand that, for the sake of overloading, both "mutable" and "invariant" can be implicitly cast to "const", but the other implications of that relationship are beyond my grasp at the moment. * The transitivity of const qualifiers requires me to consider not just the object I'm passing around, but the references to other objects contained within the const-qualified object. * Both "const" and "invariant" can apply to value-type objects. But "const" doesn't make any sense in that context, so in my mind I have to use the "invariant" concept instead whenever applying the concept to a primitive value. * The equivariance proposal, at least in some of its forms, requires me to imagine the existence of implicit typedefs at the function-call site, and to know that the declared type in my method signature might not be the actual type of the arguments received by the function. The "holes" I described in the const system are the symptoms being addressed by the equivariance proposal. Without equivariance, the const system sometimes requires verbatim duplication of code within function bodies whose only difference is their signature. Without the const system, the equivariance proposal wouldn't be necessary. And the reason I describe the const system as "confusing"? Because it confuses me. I understand some of the allure of having a robust, provably correct const system. Automatic memoization would be cool. Automatic parallel execution would be nice. And, in theory, it'd be nice to avoid errors that occur when a piece of code modifies a piece of data that it shouldn't. But in my experience (using languages that lack any sort of const system, except for enum-style declarations), it just doesn't matter much. When I need memoization or parallel execution, I write the code explicitly. And when I need an immutable object, I design its interface to make external mutation impossible (see also: java.lang.String). It's indisputable that the D2 const system is measurably more complex than the D1 const system. For me, the cost of that complexity significantly outweighs the benefit. --benji
Oct 14 2008
next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
Benji Smith wrote:
 Andrei Alexandrescu wrote:
 Benji Smith wrote:
 I really don't see what all the fuss is about with the equivariance 
 stuff, except that it introduces a lot of confusing rules to fix the 
 holes in the (already complex and confusing) const system.

Please stop perpetuating misunderstanding and apprehension about the const system through statements strong on opinion and vague on detail. If you're willing to mention holes, complexity, and confusion about the const system, please explain where the holes are, where the complexities lie, and what aspects you are confused about. Suggestions for fixing said issues would also be welcome.

Fair enough. Here are the reasons I describe the const system as "complex": * Comparable code without const qualifiers contains quantatitively fewer semantic constructs than code that uses those qualifiers. Smaller function prototypes are easier to read and comprehend: char[] join(char[] str2, char[] str2) { ... } const(char)[] join(const(char)[] str1, const(char)[] str1) { ... } * Nestable const type constructors require me to keep a more detailed mental model of the type system. Am I using a "const(char)[]" or a "const(char[])"? * Remembering the difference between "const" and "invariant" requires more mental energy than using a system (D1) where such distinctions don't exist. * The idea that "const" is a supertype of "mutable" and "invariant" is utterly bizarre to me, since it violates the "is a" rule of type hierarchies. I understand that, for the sake of overloading, both "mutable" and "invariant" can be implicitly cast to "const", but the other implications of that relationship are beyond my grasp at the moment. * The transitivity of const qualifiers requires me to consider not just the object I'm passing around, but the references to other objects contained within the const-qualified object. * Both "const" and "invariant" can apply to value-type objects. But "const" doesn't make any sense in that context, so in my mind I have to use the "invariant" concept instead whenever applying the concept to a primitive value. * The equivariance proposal, at least in some of its forms, requires me to imagine the existence of implicit typedefs at the function-call site, and to know that the declared type in my method signature might not be the actual type of the arguments received by the function. The "holes" I described in the const system are the symptoms being addressed by the equivariance proposal. Without equivariance, the const system sometimes requires verbatim duplication of code within function bodies whose only difference is their signature. Without the const system, the equivariance proposal wouldn't be necessary. And the reason I describe the const system as "confusing"? Because it confuses me. I understand some of the allure of having a robust, provably correct const system. Automatic memoization would be cool. Automatic parallel execution would be nice. And, in theory, it'd be nice to avoid errors that occur when a piece of code modifies a piece of data that it shouldn't. But in my experience (using languages that lack any sort of const system, except for enum-style declarations), it just doesn't matter much. When I need memoization or parallel execution, I write the code explicitly. And when I need an immutable object, I design its interface to make external mutation impossible (see also: java.lang.String). It's indisputable that the D2 const system is measurably more complex than the D1 const system. For me, the cost of that complexity significantly outweighs the benefit. --benji

In my ideal language, I'd like a const system where instead of the instances of types (primitive or not) being qualified, the types themselves were. So a "const class" or "const struct" would mean that every instance of that type is constant after construction. So you'd have: --- class MutableType { int x; this(int _x) { this.x = _x; } } const class ConstType { int x; this(int _x) { this.x = _x; } } MutableType m = new MutableType(5); ConstType c = new ConstType(5); m.x = 10; // OK c.x = 10; // ERROR --- All classes that extend a const class would also need to be const. As for primitives, they would either be manifest-constant or mutable like in D1. This covers most common use cases very well (except for constant arrays... hmmmm....) and is easy for the user to understand. Plus, a "const class" under this system is the same as an "invariant" under the current system, so no synchronization required. Making invariant classes (classes that are final and where all members are final) are a common design pattern in Java. But I'm dreaming here since Walter seems dead-set on this system.
Oct 15 2008
parent Robert Fraser <fraserofthenight gmail.com> writes:
Simen Kjaeraas wrote:
 Robert Fraser <fraserofthenight gmail.com> wrote:
 
 In my ideal language, I'd like a const system where instead of the 
 instances of types (primitive or not) being qualified, the types 
 themselves were. So a "const class" or "const struct" would mean that 
 every instance of that type is constant after construction.

 So you'd have:
 ---
 class MutableType
 {
      int x;
      this(int _x) { this.x = _x; }
 }
 const class ConstType {
      int x;
      this(int _x) { this.x = _x; }
 }
 MutableType m = new MutableType(5);
 ConstType c = new ConstType(5);
 m.x = 10; // OK
 c.x = 10; // ERROR
 ---

 All classes that extend a const class would also need to be const. As 
 for primitives, they would either be manifest-constant or mutable like 
 in D1. This covers most common use cases very well (except for 
 constant arrays... hmmmm....) and is easy for the user to understand. 
 Plus, a "const class" under this system is the same as an "invariant" 
 under the current system, so no synchronization required. Making 
 invariant classes (classes that are final and where all members are 
 final) are a common design pattern in Java.

 But I'm dreaming here since Walter seems dead-set on this system.

Like in current d2, you mean? const class foo { } class bar : foo { } void main() { auto a = new foo(); auto b = new bar(); foo f = new foo(); writefln(typeof(a).stringof); // prints const(foo) writefln(typeof(b).stringof); // prints const(bar) writefln(typeof(f).stringof); // prints const(foo) }

Well, yes and no. That is typeof(a) would be just "foo", there would be no such thing as a const(foo). Instances of foo just couldn't be changed after construction, and the compiler could apply invariant optimizations to it as usual. That is, there would be no user-visible const except for the ability to declare constant classes/structs.
Oct 16 2008
prev sibling next sibling parent Gide Nwawudu <gide btinternet.com> writes:
On Tue, 14 Oct 2008 22:51:46 -0400, Benji Smith
<dlanguage benjismith.net> wrote:

Andrei Alexandrescu wrote:
 Benji Smith wrote:
 I really don't see what all the fuss is about with the equivariance 
 stuff, except that it introduces a lot of confusing rules to fix the 
 holes in the (already complex and confusing) const system.

Please stop perpetuating misunderstanding and apprehension about the const system through statements strong on opinion and vague on detail. If you're willing to mention holes, complexity, and confusion about the const system, please explain where the holes are, where the complexities lie, and what aspects you are confused about. Suggestions for fixing said issues would also be welcome.

Fair enough. Here are the reasons I describe the const system as "complex": * Comparable code without const qualifiers contains quantatitively fewer semantic constructs than code that uses those qualifiers. Smaller function prototypes are easier to read and comprehend: char[] join(char[] str2, char[] str2) { ... }

But how would you know that the parameters are not modified? This could lead to unnecessary dups in the client code. Personally, I think it is easier to read a function signature, however complex, than to read the function body. The syntax is very important to the understanding of the concept, so far I like the idea of 'const?' and the typedef{} idea, but maybe reusing the keyword 'auto' could also work? class S { auto(this) clone(); auto(this.field) getfield(); int field; } Gide
Oct 15 2008
prev sibling next sibling parent Gide Nwawudu <gide btinternet.com> writes:
On Tue, 14 Oct 2008 22:51:46 -0400, Benji Smith
<dlanguage benjismith.net> wrote:

[snip]
It's indisputable that the D2 const system is measurably more complex 
than the D1 const system.

For me, the cost of that complexity significantly outweighs the benefit.

--benji

The following bug illustrates the issues users can have with D1's const system. http://d.puremagic.com/issues/show_bug.cgi?id=2418 Gide
Oct 15 2008
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Benji Smith wrote:
 Andrei Alexandrescu wrote:
 Benji Smith wrote:
 I really don't see what all the fuss is about with the equivariance 
 stuff, except that it introduces a lot of confusing rules to fix the 
 holes in the (already complex and confusing) const system.

Please stop perpetuating misunderstanding and apprehension about the const system through statements strong on opinion and vague on detail. If you're willing to mention holes, complexity, and confusion about the const system, please explain where the holes are, where the complexities lie, and what aspects you are confused about. Suggestions for fixing said issues would also be welcome.

Fair enough. Here are the reasons I describe the const system as "complex":

I see where you're coming from. If your opinion is that the const system does not bring enough benefits to justify its learning, you are of course entitled to it. But talking about complexity implies that there would be simpler ways to achieve its charter that we missed, and talking about holes that we made mistakes of principle that go beyond bugs in the compiler or incompleteness of implementation.
 * Comparable code without const qualifiers contains quantatitively fewer 
 semantic constructs than code that uses those qualifiers. Smaller 
 function prototypes are easier to read and comprehend:
 
    char[] join(char[] str2, char[] str2) { ... }
 
    const(char)[] join(const(char)[] str1, const(char)[] str1) { ... }

Complexity should be judged in proportion to the goal achieved. Of course an AM radio will be less complex than an AM/FM radio; that does not reveal an intrinsic problem with the latter. And the more complex signature tells you that join will not change its arguments. As Gide mentioned, one source of bugs in a D program is that attention must be paid to duplicating aliased slices properly. That wouldn't be bad if such mistakes were easily detected. Unfortunately, forgetting to call .dup causes long-distance coupling that make changing one piece of data in one place influence the change in an unrelated part, just because they share some history.
 * Nestable const type constructors require me to keep a more detailed 
 mental model of the type system. Am I using a "const(char)[]" or a 
 "const(char[])"?

You don't need to keep a more detailed mental model of the type system - whatever that is :o). On the contrary, const allows you to free your mind of many details about the data flow in your application. If you make a mistake regarding const(char)[] vs. const(char[]), the compiler will tell you about it, not one night of debugging.
 * Remembering the difference between "const" and "invariant" requires 
 more mental energy than using a system (D1) where such distinctions 
 don't exist.

I think this reflects a misunderstanding of what constness is supposed to do for you. It's exactly the contrary. Invariant reduces the amount of mental energy you need to expend. I think you owe it to yourself to use D2. You will see that using invariant strings is a huge improvement and it alone is worth introducing constness. Literally all bugs caused by undue aliasing disappear if you use string. You will see that the net result is that your programs are easier to write and understand, not harder.
 * The idea that "const" is a supertype of "mutable" and "invariant" is 
 utterly bizarre to me, since it violates the "is a" rule of type 
 hierarchies. I understand that, for the sake of overloading, both 
 "mutable" and "invariant"  can be implicitly cast to "const", but the 
 other implications of that relationship are beyond my grasp at the moment.

You may want to reframe this as an opportunity to improve your understanding of subtyping. I recommend a classic paper by Cardelli and Wegner "On understanding Types, Data Abstraction, and Polymorphism" (http://lucacardelli.name/Papers/OnUnderstanding.A4.pdf). It's a classic. Reading through section 1.5 does not require much background and is very rewarding. That's not to say using const and invariant requires formal background. Most people would just use them for their benefits and have them just work. But if you want to lift the hood and see exactly why they work, you have to get your hands greasy a bit.
 * The transitivity of const qualifiers requires me to consider not just 
 the object I'm passing around, but the references to other objects 
 contained within the const-qualified object.

This is backwards, too. If const didn't exist or weren't transitive, you'd be required to keep in mind for mutation and aliasing bugs not just the object you're passing around, but the references to other objects.
 * Both "const" and "invariant" can apply to value-type objects. But 
 "const" doesn't make any sense in that context, so in my mind I have to 
 use the "invariant" concept instead whenever applying the concept to a 
 primitive value.

Of course it does make sense. The address of a const int has a different type than the address of a mutable int. Again the pattern is that you have less, not more to keep in mind. The type "keeps it in its mind" for you.
 * The equivariance proposal, at least in some of its forms, requires me 
 to imagine the existence of implicit typedefs at the function-call site, 
 and to know that the declared type in my method signature might not be 
 the actual type of the arguments received by the function.

Not at all. On this group many people have high competency and willing to discuss the most minute implementation details of a language feature, and my understanding is that you also engage in such discussions. On occasion, therefore, there will be discussion on exactly how sausages are being made. Do not confuse those discussions with things that complicate the experience of eating sausages. Much of the problems caused by const equivariance is just that Walter is leery of implementing aliases bound to qualifiers. If he would, there would be no problem to talk about. The issues with const equivariance, in that case, would be exactly the same as with supporting equivariance for any types in general.
 The "holes" I described in the const system are the symptoms being 
 addressed by the equivariance proposal.

You did not describe any hole above. In fact, on first reading, at this point I was expecting you'd start describing the actual holes that I know about - e.g. the impossibility of creating invariant objects without a cast. (That will be fixed.)
 Without equivariance, the const 
 system sometimes requires verbatim duplication of code within function 
 bodies whose only difference is their signature. Without the const 
 system, the equivariance proposal wouldn't be necessary.

No. Without equivariance, you'd be required to duplicate bodies of functions that need it, such as clone. It is true that const makes that need more frequent, but that does not reveal a problem intrinsic to it or a mistake in defining it.
 And the reason I describe the const system as "confusing"?
 
 Because it confuses me.

This is a fair point. One problem is that right now no good description of const exists. Worse, trying to infer it from discussions on this group is bound to scare one away because here mostly problems are discussed.
 I understand some of the allure of having a robust, provably correct 
 const system. Automatic memoization would be cool. Automatic parallel 
 execution would be nice. And, in theory, it'd be nice to avoid errors 
 that occur when a piece of code modifies a piece of data that it shouldn't.
 
 But in my experience (using languages that lack any sort of const 
 system, except for enum-style declarations), it just doesn't matter 
 much. When I need memoization or parallel execution, I write the code 
 explicitly. And when I need an immutable object, I design its interface 
 to make external mutation impossible (see also: java.lang.String).

There's been a pretty incredible resurgence in interest in functional programming languages. At ACCU 2008 (www.accu.org), previously a "no-quarters" hardcore C++ conference, who do you think gave keynotes and talks? Simon Peyton-Jones, the inventor of Haskell, and Joe Armstrong, inventor of Erlang. To better understand the size of that change, you must browse through previous years' conference programs. Only 2-3 years ago that very concept would have been unbelievable. ACCU stands for Association of C and C++ Users! Use of Haskell and Erlang is increasing exponentially. Even mainstream companies started using them. Others (big, big companies that shall not be named) are designing their own languages. And guess what - they _all_ have a notion of immutability. What do functional languages have? Do you think people like them for the monads? It's the immutability. They've been embracing and dealing with it for years. And that gives them a ready answer to today's 2000-lbs pink elephant in the room - the manycores. Having made a resolute step towards classic, deadlock-oriented programming, Java is now in the unpleasant position of having invested it money in the wrong stock, just like it did with UTF-16. So when people think manycores, they look away from Java.
 It's indisputable that the D2 const system is measurably more complex 
 than the D1 const system.
 
 For me, the cost of that complexity significantly outweighs the benefit.

It is fair to hold such a belief. I think it would be helpful to reevaluate it in wake of the above. Andrei
Oct 15 2008
parent reply Benji Smith <dlanguage benjismith.net> writes:
Hey, Andrei!

First of all, just let me say how much I appreciate your detailed 
responses. I hope you don't ever take my feedback as a personal 
criticism or as being harshly negative. The only reason I've continued 
to engage is these kinds of debates for the last five years or so is 
because D interests me very much, and I'd like to see it meet its 
fullest potential. I know you have the same end-goals in mind, though we 
sometimes disagree about the most effective route from point A to point B.

Andrei Alexandrescu wrote:
 I see where you're coming from. If your opinion is that the const system 
 does not bring enough benefits to justify its learning, you are of 
 course entitled to it. But talking about complexity implies that there 
 would be simpler ways to achieve its charter that we missed, and talking 
 about holes that we made mistakes of principle that go beyond bugs in 
 the compiler or incompleteness of implementation.

Yeah, I see where you're coming from too. I think you guys have done an admirable job thinking through the corner cases and figuring out ways to solve many of the sticky design issues that have arisen. On the other hand, there was a const proposal...maybe six months or a year ago...where all function parameters would be considered immutable by default, unless annotated as mutable. I thought that idea had a lot of merit. My first impression was that it'd provide more reliable guarantees, since it would be impossible to accidentally create a mutable param by forgetting to declare its constness. It also seemed like it would have resulted in more compact method signatures (since const arguments are generally more common than mutable arguments), and less overall risk of aliasing. For me, one of the main perks of the other proposal was less "const" litter all over the code, and yet with stronger const safety. Also, a function promising not to modify its arguments seems more like an attribute of the function, not of the arguments themselves. But I digress...
 * Nestable const type constructors require me to keep a more detailed 
 mental model of the type system. Am I using a "const(char)[]" or a 
 "const(char[])"?

You don't need to keep a more detailed mental model of the type system - whatever that is :o). On the contrary, const allows you to free your mind of many details about the data flow in your application. If you make a mistake regarding const(char)[] vs. const(char[]), the compiler will tell you about it, not one night of debugging.

The "mental model" is my cute little informal way of referring to the body of knowledge necessary to operate some contraption (like a can opener or a compiler). In order to make the gizmo do what I want it to do, I have to form a (perhaps simplified) mental picture of the cause-and-effect chain that connects the inputs with the outputs. Driving a car with a manual transmission requires a more detailed mental model than driving an automatic. Maybe the manual transmission offers other advantages, but ease-of-use is not one of them.
 * Remembering the difference between "const" and "invariant" requires 
 more mental energy than using a system (D1) where such distinctions 
 don't exist.

I think this reflects a misunderstanding of what constness is supposed to do for you. It's exactly the contrary. Invariant reduces the amount of mental energy you need to expend. I think you owe it to yourself to use D2. You will see that using invariant strings is a huge improvement and it alone is worth introducing constness. Literally all bugs caused by undue aliasing disappear if you use string. You will see that the net result is that your programs are easier to write and understand, not harder.

I actually plan on giving D2 a spin as soon as a compatible version of Tango is available. And I actually totally agree with you about invariant strings. The languages I use most often (Java, C#, Python) all have immutable strings, without the languages having a const system like D. In those languages, immutability is achieved by convention. It's a tradeoff, sure. Especially when implementing a new class, you have to think carefully about when to make defensive copies in getters and setters. And external consumers of your classes, who might not know where the defensive copies occur, might create their own defensive copies before calling one of your API methods. In that respect, a const system is alluring because it eliminates the need for those kinds of considerations. But the const system just smells bad to me. I've tried to enumerate my reasons, and maybe switching to D2 will change my mind. But right now, I'm calling it as I see it. You asked for feedback, and you got it!! I'll make an analogy. I also find the concept of range-bound numeric types very appealing. It'd be nice to declare that a function takes an argument whose value is a "double greater than zero but less than or equal to one". A lot of my statistical code would benefit from that kind of type declaration, and I'd be able to get rid of a few lines of argument-validity-checking code from the preamble of every function implementation. While we're at it, I can also think of examples where I might like to say that a function accepts a "feature vector whose magnitude is exactly equal to 1.0". Input validation is one of those nasty cross-cutting concerns, and it'd be nice to have a general-purpose, language-level solution to the problem. It doesn't take too much imagination to sketch out a design for extending the type system to allow for those kinds of value constraints. With such a type calculus in place, it would probably be possible to implement a general purpose solver that would enable a whole host of interesting optimizations. If I'm not mistaken, that's the basic idea of many functional languages... That by moving computation into the type system, the algebraic rules governing those types can be solved by applying well-known type transformations, often in parallel. Nevertheless, I think range-bound types would be a mistake to implement in D. I can't completely articulate why I feel that way, but part of it has to do with the increasing complexity of function prototype declarations. And, it's also partly because validation can already be accomplished in an "in" contract. To me, the const system has the same basic characteristics: it's a major language feature, with lots of corner cases, to solve a problem that I either don't have or have already found a reasonable (if not ideal) solution.
 * The idea that "const" is a supertype of "mutable" and "invariant" is 
 utterly bizarre to me, since it violates the "is a" rule of type 
 hierarchies. I understand that, for the sake of overloading, both 
 "mutable" and "invariant"  can be implicitly cast to "const", but the 
 other implications of that relationship are beyond my grasp at the 
 moment.

You may want to reframe this as an opportunity to improve your understanding of subtyping. I recommend a classic paper by Cardelli and Wegner "On understanding Types, Data Abstraction, and Polymorphism" (http://lucacardelli.name/Papers/OnUnderstanding.A4.pdf). It's a classic. Reading through section 1.5 does not require much background and is very rewarding.

I just read section 1.5 and don't find anything outlandish there. And I've added the paper to my reading list. Language design is fun, and I'm always looking for new good source material :)
 * The transitivity of const qualifiers requires me to consider not 
 just the object I'm passing around, but the references to other 
 objects contained within the const-qualified object.

This is backwards, too. If const didn't exist or weren't transitive, you'd be required to keep in mind for mutation and aliasing bugs not just the object you're passing around, but the references to other objects.

Again, I'd like to reaffirm my agreement that immutable objects are valuable. No disagreement from me there! I'm just used to an ecosystem where immutability is by convention, and the language-level immutability in D strikes me as somewhat heavy-handed.
 * The equivariance proposal, at least in some of its forms, requires 
 me to imagine the existence of implicit typedefs at the function-call 
 site, and to know that the declared type in my method signature might 
 not be the actual type of the arguments received by the function.

Not at all. On this group many people have high competency and willing to discuss the most minute implementation details of a language feature, and my understanding is that you also engage in such discussions. On occasion, therefore, there will be discussion on exactly how sausages are being made. Do not confuse those discussions with things that complicate the experience of eating sausages.

Yep! I love to engage in these kinds of discussions! And I hope you take this in the spirit of informed disagreement. I read about 90% of the messages in this NG, paying particular attention to the language-design discussions. I've taken the stuff I learned in this NG from geniuses like you and Walter, and used it in real-life projects, implementing a domain-specific language and compiler for one of my former employers. I'm sure you get tired of people throwing stones at your design concepts, especially without a detailed explanation. But I think many of us also get tired of being told "if you really understood, you'd agree". I may not fully understand all the details, but I understand enough to know that I'm not a fan. And I disagree about the "sausage-making". I've never successfully used a language feature, in any programming language, where I didn't understand the underlying mechanics of the implementation. Anyone writing code who doesn't know how it works under the hood is just a copy-paste programmer. Anyhow, I'll leave it at that. Since this community presumably exists to support a collaborative environment for advancing the D language (and does a remarkably good job of it in the vast majority of cases), I just wanted to put my vote in the ballot-box. :-) --benji
Oct 15 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Benji Smith wrote:
 Hey, Andrei!
 
 First of all, just let me say how much I appreciate your detailed 
 responses. I hope you don't ever take my feedback as a personal 
 criticism or as being harshly negative. The only reason I've continued 
 to engage is these kinds of debates for the last five years or so is 
 because D interests me very much, and I'd like to see it meet its 
 fullest potential. I know you have the same end-goals in mind, though we 
 sometimes disagree about the most effective route from point A to point B.

That is highly appreciated.
 Andrei Alexandrescu wrote:
 I see where you're coming from. If your opinion is that the const 
 system does not bring enough benefits to justify its learning, you are 
 of course entitled to it. But talking about complexity implies that 
 there would be simpler ways to achieve its charter that we missed, and 
 talking about holes that we made mistakes of principle that go beyond 
 bugs in the compiler or incompleteness of implementation.

Yeah, I see where you're coming from too. I think you guys have done an admirable job thinking through the corner cases and figuring out ways to solve many of the sticky design issues that have arisen. On the other hand, there was a const proposal...maybe six months or a year ago...where all function parameters would be considered immutable by default, unless annotated as mutable.

Sure. I suggested that to Walter two years ago, or maybe three. It was one of the first suggestion I made: you should make everything immutable by default, and mutable only if explicitly specified. (That's what e.g. ML and Cecil do.) He also liked it: instead of littering code with const this and immutable that, you only have var here and there - and maybe mutation is much less rarely needed than we think. In the end we rejected the idea because we thought it'll scare away people coming from C++ and Java. It was also too big a change from what D was at that point. So we had to drop that. But don't think for a moment that that approach would have been one iota simpler than what we have now. Supporting mutation and immutability in the same range is a sort of constant-sum game - you can pick your defaults, but overall the pie has the same size.
 I thought that idea had a lot of merit. My first impression was that 
 it'd provide more reliable guarantees, since it would be impossible to 
 accidentally create a mutable param by forgetting to declare its 
 constness. It also seemed like it would have resulted in more compact 
 method signatures (since const arguments are generally more common than 
 mutable arguments), and less overall risk of aliasing.
 
 For me, one of the main perks of the other proposal was less "const" 
 litter all over the code, and yet with stronger const safety.

You'll be glad then to learn that "in" means const at least in D2: void foo(in char[] s); // same as foo(const(char)[] s)
 Also, a function promising not to modify its arguments seems more like 
 an attribute of the function, not of the arguments themselves.

The signature of the function _is_ an attribute of the function.
 But I digress...

It's a good digression.
 Driving a car with a manual transmission requires a more detailed mental 
 model than driving an automatic. Maybe the manual transmission offers 
 other advantages, but ease-of-use is not one of them.

Hmmmm. I'd compare the wild west when everything is mutable at any time much more with an unwieldy manual-transmission car that leaks oil and blows a gasket every so often. That's congruent with my earlier point: you think const is more trouble for you. IMHO it's exactly, but I mean exactly, the opposite.
 I actually plan on giving D2 a spin as soon as a compatible version of 
 Tango is available.

 But the const system just smells bad to me. I've tried to enumerate my 
 reasons, and maybe switching to D2 will change my mind. But right now, 
 I'm calling it as I see it. You asked for feedback, and you got it!!

Now I'm not sure how to sugarcoat or spin positively or put nicely what I'm going to say. Let me try (actually it took me a couple of minutes to figure out a formula). At least to me, "feedback" implies a closed-loop system. Stuff happens, has consequences, the consequences are observed, and information is "fed" "back" to the system. In our case, I'd say that using const is an absolute prerequisite for feedback to be taken seriously, particularly when it's as informal as "just smells bad to me". Otherwise I think "candid opinion" is the best way I can put it. Sorry. [snip]
 Since this community presumably exists to support a collaborative 
 environment for advancing the D language (and does a remarkably good job 
 of it in the vast majority of cases), I just wanted to put my vote in 
 the ballot-box.
 
 :-)

Please get information on the candidate before voting. Andrei
Oct 15 2008
next sibling parent reply Benji Smith <dlanguage benjismith.net> writes:
Andrei Alexandrescu wrote:
 Now I'm not sure how to sugarcoat or spin positively or put nicely what 
 I'm going to say. Let me try (actually it took me a couple of minutes to 
 figure out a formula).
 
 At least to me, "feedback" implies a closed-loop system. Stuff happens, 
 has consequences, the consequences are observed, and information is 
 "fed" "back" to the system. In our case, I'd say that using const is an 
 absolute prerequisite for feedback to be taken seriously, particularly 
 when it's as informal as "just smells bad to me". Otherwise I think 
 "candid opinion" is the best way I can put it. Sorry.
 
 [snip]
 Since this community presumably exists to support a collaborative 
 environment for advancing the D language (and does a remarkably good 
 job of it in the vast majority of cases), I just wanted to put my vote 
 in the ballot-box.

 :-)

Please get information on the candidate before voting.

Point taken. I withdraw my comments about the const system, and from now on, I'll only critique stuff I've actually used, rather than proposals I've read and whose issues I've only imagined. And, as always, no harm no foul. --benji
Oct 15 2008
parent reply John Reimer <terminal.node gmail.com> writes:
Hello Benji,

 Andrei Alexandrescu wrote:
 
 Now I'm not sure how to sugarcoat or spin positively or put nicely
 what I'm going to say. Let me try (actually it took me a couple of
 minutes to figure out a formula).
 
 At least to me, "feedback" implies a closed-loop system. Stuff
 happens, has consequences, the consequences are observed, and
 information is "fed" "back" to the system. In our case, I'd say that
 using const is an absolute prerequisite for feedback to be taken
 seriously, particularly when it's as informal as "just smells bad to
 me". Otherwise I think "candid opinion" is the best way I can put it.
 Sorry.
 
 [snip]
 
 Since this community presumably exists to support a collaborative
 environment for advancing the D language (and does a remarkably good
 job of it in the vast majority of cases), I just wanted to put my
 vote in the ballot-box.
 
 :-)
 


I withdraw my comments about the const system, and from now on, I'll only critique stuff I've actually used, rather than proposals I've read and whose issues I've only imagined. And, as always, no harm no foul. --benji

Sorry, can't help it: Bravo! Well done, you two! :) It's a joy to read posts that are respectful and considerate, yet without sacrificing a certain amount of directness. It a skill I'm still trying to develop. Thanks for setting the example, guys. -JJR
Oct 15 2008
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
John Reimer wrote:
 Hello Benji,
 
 Andrei Alexandrescu wrote:

 Now I'm not sure how to sugarcoat or spin positively or put nicely
 what I'm going to say. Let me try (actually it took me a couple of
 minutes to figure out a formula).

 At least to me, "feedback" implies a closed-loop system. Stuff
 happens, has consequences, the consequences are observed, and
 information is "fed" "back" to the system. In our case, I'd say that
 using const is an absolute prerequisite for feedback to be taken
 seriously, particularly when it's as informal as "just smells bad to
 me". Otherwise I think "candid opinion" is the best way I can put it.
 Sorry.

 [snip]

 Since this community presumably exists to support a collaborative
 environment for advancing the D language (and does a remarkably good
 job of it in the vast majority of cases), I just wanted to put my
 vote in the ballot-box.

 :-)


I withdraw my comments about the const system, and from now on, I'll only critique stuff I've actually used, rather than proposals I've read and whose issues I've only imagined. And, as always, no harm no foul. --benji

Sorry, can't help it: Bravo! Well done, you two! :) It's a joy to read posts that are respectful and considerate, yet without sacrificing a certain amount of directness. It a skill I'm still trying to develop. Thanks for setting the example, guys. -JJR

Agreed. I'm telling you: this is so nice, I thought there for a moment that someone was impersonating me :o). Andrei
Oct 15 2008
prev sibling next sibling parent Sean Kelly <sean invisibleduck.org> writes:
Andrei Alexandrescu wrote:
 Benji Smith wrote:
 For me, one of the main perks of the other proposal was less "const" 
 litter all over the code, and yet with stronger const safety.

You'll be glad then to learn that "in" means const at least in D2: void foo(in char[] s); // same as foo(const(char)[] s)

This has been an absolute godsend for writing D1->D2 portable code. So much so that I'm not sure I'd have even attempted it without this feature.
 Driving a car with a manual transmission requires a more detailed 
 mental model than driving an automatic. Maybe the manual transmission 
 offers other advantages, but ease-of-use is not one of them.

Hmmmm. I'd compare the wild west when everything is mutable at any time much more with an unwieldy manual-transmission car that leaks oil and blows a gasket every so often. That's congruent with my earlier point: you think const is more trouble for you. IMHO it's exactly, but I mean exactly, the opposite.

I think being const-free results in more succinct code, and I'd argue it's more easily readable as well, because of the syntactic dissonance that the const labels introduce. But your wild west analogy is an apt one, because it makes data protection largely a matter of convention, while at least the switches and knobs of the manual transmission car provide a means to ensure safety at the design level. I think one could argue the benefits of each.
 I actually plan on giving D2 a spin as soon as a compatible version of 
 Tango is available.

 But the const system just smells bad to me. I've tried to enumerate my 
 reasons, and maybe switching to D2 will change my mind. But right now, 
 I'm calling it as I see it. You asked for feedback, and you got it!!

Now I'm not sure how to sugarcoat or spin positively or put nicely what I'm going to say. Let me try (actually it took me a couple of minutes to figure out a formula). At least to me, "feedback" implies a closed-loop system. Stuff happens, has consequences, the consequences are observed, and information is "fed" "back" to the system. In our case, I'd say that using const is an absolute prerequisite for feedback to be taken seriously, particularly when it's as informal as "just smells bad to me". Otherwise I think "candid opinion" is the best way I can put it. Sorry.

I've been a fairly vocal opponent of the const system in the past, largely because I'm not terribly happy with the complexity it can introduce in user code. However, I think it's important to note that I said /can/ rather than /will/. A great deal still comes down to programmer skill, and the rest, hopefully, will be addressed by discussions such as this. Sean
Oct 15 2008
prev sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Andrei Alexandrescu wrote:
 
 You'll be glad then to learn that "in" means const at least in D2:
 
 void foo(in char[] s); // same as foo(const(char)[] s)
 

What?? Whoa, at first I thought you were mistaken, and meant 'const(char[]) s' instead (since that is what is the same as 'const char[] s'), but I fired up my editor and tried it out, and it works as you described! Even more surprising, it works the same way when using a class type: class Foo { int x; } void func(in Foo foo, const scope Foo foo2) { foo = null; // Ok! //foo2 = null; //Compile error! //foo.x = 0; // Compile error! pragma(msg, (typeof(foo)).stringof ~ " " ~ (typeof(foo2)).stringof); } Which means 'in' works exactly as headconst! Is this another easter egg, or a bug? It's certainly not according to the spec at least. -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 18 2008
next sibling parent ore-sama <spam here.lot> writes:
Bruno Medeiros Wrote:

 Which means 'in' works exactly as headconst! Is this another easter egg, 
 or a bug? It's certainly not according to the spec at least.

bug in docs. in can't be scope. docs: For dynamic array and object parameters, which are passed by reference, in/out/ref apply only to the reference and not the contents.
Oct 18 2008
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Bill Baxter wrote:
 On Sun, Oct 19, 2008 at 3:45 AM, Bruno Medeiros
 <brunodomedeiros+spam com.gmail> wrote:
 Andrei Alexandrescu wrote:
 You'll be glad then to learn that "in" means const at least in D2:

 void foo(in char[] s); // same as foo(const(char)[] s)

Whoa, at first I thought you were mistaken, and meant 'const(char[]) s' instead (since that is what is the same as 'const char[] s'), but I fired up my editor and tried it out, and it works as you described! Even more surprising, it works the same way when using a class type: class Foo { int x; } void func(in Foo foo, const scope Foo foo2) { foo = null; // Ok! //foo2 = null; //Compile error! //foo.x = 0; // Compile error! pragma(msg, (typeof(foo)).stringof ~ " " ~ (typeof(foo2)).stringof); } Which means 'in' works exactly as headconst! Is this another easter egg, or a bug? It's certainly not according to the spec at least.

I think you mean "tailconst". The head is not const, the tail is. --bb

Yes, exactly, I meant 'tailconst'. -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 19 2008
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Benji Smith wrote:
 
 On the other hand, there was a const proposal...maybe six months or a 
 year ago...where all function parameters would be considered immutable 
 by default, unless annotated as mutable.
 

Just a minor correction: the proposal was for all function parameters to be *const* by default, not immutable. (it would be a pain if it was like that, immutable by default) -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 18 2008
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Benji Smith wrote:
 
 * Comparable code without const qualifiers contains quantatitively fewer 
 semantic constructs than code that uses those qualifiers. Smaller 
 function prototypes are easier to read and comprehend:
 
    char[] join(char[] str2, char[] str2) { ... }
 
    const(char)[] join(const(char)[] str1, const(char)[] str1) { ... }
 

Together with the existing 'string', I've created aliases of my own: 'mstring', short for mutable string, same as 'char[]', and 'cstring' for const string, same as 'const(char)[]'; I've found they bring not only less typing, but also less mental workload (like, easier to read). cstring join(cstring str1, cstring str1) { ... } Although that's mostly because of the fact that I don't have to immediately think of them as arrays of characters.
 
 * The idea that "const" is a supertype of "mutable" and "invariant" is 
 utterly bizarre to me, since it violates the "is a" rule of type 
 hierarchies. I understand that, for the sake of overloading, both 
 "mutable" and "invariant"  can be implicitly cast to "const", but the 
 other implications of that relationship are beyond my grasp at the moment.
 

Like Andrei said, it does not violate the "is a" rule of type hierarchies. An invariant type can be used as a const type without breaking any of the contracts of invariant. Thus, it's a subtype as per the Liskov substitution principle. (and the same with mutable) -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 18 2008
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Benji Smith" wrote
 Steven Schveighoffer wrote:
 class A : IClonable
 {
   typeof(this) clone() const { return new A();}
 }

 class B : A
 {
 }

 B b = new B;
 B x = b.clone();

 Oops, b.clone() really only returns A, so this should fail.  The real 
 signature in A should be:

No, b.clone() definitely returns an instance of B. It's purely coincidental (from the caller's perspective) that B's implementation is identical to that of A.

B is definitely not the same as A. B could have a different implementation, I just didn't give it one. e.g.: class A : IClonable { typeof(this) clone() const { return new A(); } void foo() {writefln("This is A");} } class B : A { void foo() {writefln("This is B");} } B b = new B; b.clone().foo(); // prints "This is A" It can't work any other way, or else the type system would be broken. -Steve
Oct 14 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 "Benji Smith" wrote
 Steven Schveighoffer wrote:
 class A : IClonable
 {
   typeof(this) clone() const { return new A();}
 }

 class B : A
 {
 }

 B b = new B;
 B x = b.clone();

 Oops, b.clone() really only returns A, so this should fail.  The real 
 signature in A should be:

coincidental (from the caller's perspective) that B's implementation is identical to that of A.

B is definitely not the same as A. B could have a different implementation, I just didn't give it one. e.g.: class A : IClonable { typeof(this) clone() const { return new A(); } void foo() {writefln("This is A");} } class B : A { void foo() {writefln("This is B");} } B b = new B; b.clone().foo(); // prints "This is A" It can't work any other way, or else the type system would be broken.

Clone is different in two ways: 1. It must be implemented in *all* derived classes 2. For each of those classes, it must return the type of that class, not the type of any ancestor. Statically enforcing both 1 and 2 is the ideal case. Some languages allow it by allowing classes to express the actual (leaf) type of "this". D doesn't. I am not sure to what extent the feature is necessary. I would have been happy to capture it together with const-equivariance, but if that doesn't work, then what can I do. Andrei
Oct 14 2008
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 "Benji Smith" wrote
 Steven Schveighoffer wrote:
 class A : IClonable
 {
   typeof(this) clone() const { return new A();}
 }

 class B : A
 {
 }

 B b = new B;
 B x = b.clone();

 Oops, b.clone() really only returns A, so this should fail.  The real 
 signature in A should be:

coincidental (from the caller's perspective) that B's implementation is identical to that of A.

B is definitely not the same as A. B could have a different implementation, I just didn't give it one. e.g.: class A : IClonable { typeof(this) clone() const { return new A(); } void foo() {writefln("This is A");} } class B : A { void foo() {writefln("This is B");} } B b = new B; b.clone().foo(); // prints "This is A" It can't work any other way, or else the type system would be broken.

Clone is different in two ways: 1. It must be implemented in *all* derived classes 2. For each of those classes, it must return the type of that class, not the type of any ancestor. Statically enforcing both 1 and 2 is the ideal case. Some languages allow it by allowing classes to express the actual (leaf) type of "this". D doesn't. I am not sure to what extent the feature is necessary. I would have been happy to capture it together with const-equivariance, but if that doesn't work, then what can I do.

I'm sure it can be done, but I wasn't thinking of that when you were suggesting equivariance. That sounds more like implementation rules (similar to if you implement an interface, you must implement all functions in the interface). What I was thinking of is putting into the signature of the function what the compiler has guaranteed about the return type. i.e. the return type is built from argument x. The typeof(this) seems like it has a single purpose though. Only as the return type of a class member function. It makes no sense as a struct member function, as you can't derive from it, and likewise for a free function. I think you are talking about a third feature other than the two I identified elsewhere. However, this might add to the usability of the 'return exact parameter' syntax. For example, for a function foo that takes a base type A, it can't guarantee that it can return type that was derived from A unless it returns the argument itself. But if an A class has the typeof(this) as one of its returns for a method, then A is guaranteeing that the return type is at least of the most derived type (and the compiler has already statically guaranteed that). So here's what I think we could do: 1. inout (or whatever keyword people like) defines how to deal with multiple constancies with one function. 2. typeof(this) as a return type on an interface or class method dictates that a. all derived classes must re-implement the given function b. the derived class must return the most derived type in that function. 3. typeof(arg) guarantees that either a. the function is returning that exact arg b. typeof(arg) is an interface or class, and the function is returning a result from arg.f, where arg.f returns typeof(this). In light of this possibility, I retract the proposal for return int x in the parameter list, as it would be difficult to declare a temporary variable of that type unambiguously. -Steve
Oct 14 2008
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 "Denis Koroskin" wrote
 Now that I thought about it a little more (please, see and comment my post 
 about typeof(this) nearby), I agree that the issues are related.

 However, the best solution could be a combination of both.

 For example, I agree that interface IClonable should be as follows:

 interface IClonable { typeof(this) clone() const; }

No. IClonable should be: IClonable clone() const;

Nah, the whole goal is to have clone() in the base enforce that derivees implement it with the right signature... Andrei
Oct 14 2008
prev sibling next sibling parent Benji Smith <dlanguage benjismith.net> writes:
Denis Koroskin wrote:
 Nope, you missed the idea. Was my post too long?

Oops. You're right. Sorry about that :) --benji
Oct 14 2008
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Denis Koroskin" wrote
 On Wed, 15 Oct 2008 05:44:22 +0400, Steven Schveighoffer 
 <schveiguy yahoo.com> wrote:

 "Denis Koroskin" wrote
 Now that I thought about it a little more (please, see and comment my 
 post
 about typeof(this) nearby), I agree that the issues are related.

 However, the best solution could be a combination of both.

 For example, I agree that interface IClonable should be as follows:

 interface IClonable { typeof(this) clone() const; }

No. IClonable should be: IClonable clone() const; or if you prefer: Object clone() const; It can't be anything else, because IClonable cannot define how many levels deep it goes.


Yes, I did miss the idea. My apologies, I was not looking at equivariance meaning 'forcing derived classes to implement certain functions'. See my later post in reply to Andrei. Sorry for the confusion. -Steve
Oct 14 2008
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Wed, 15 Oct 2008 05:44:22 +0400, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 "Denis Koroskin" wrote
 Now that I thought about it a little more (please, see and comment my  
 post
 about typeof(this) nearby), I agree that the issues are related.

 However, the best solution could be a combination of both.

 For example, I agree that interface IClonable should be as follows:

 interface IClonable { typeof(this) clone() const; }

No. IClonable should be: IClonable clone() const; or if you prefer: Object clone() const; It can't be anything else, because IClonable cannot define how many levels deep it goes.

 For example, if I have:

 class A : IClonable
 {
   typeof(this) clone() const { return new A();}
 }

 class B : A
 {
 }

This shouldn't compile - Error: class B doesn't implement typeof(this) clone() method.
 B b = new B;
 B x = b.clone();

Error: can't instantiate abstract class B.
 Oops, b.clone() really only returns A, so this should fail.  The real
 signature in A should be:

 class A : IClonable
 {
    A clone() const { return new A(); }
 }

No, you missed the idea of the discussion.
 IClonable is a perfect candidate for covariant functions, which is  
 already defined.

 -Steve

Oct 14 2008
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
Robert Fraser <fraserofthenight gmail.com> wrote:

 In my ideal language, I'd like a const system where instead of the  
 instances of types (primitive or not) being qualified, the types  
 themselves were. So a "const class" or "const struct" would mean that  
 every instance of that type is constant after construction.

 So you'd have:
 ---
 class MutableType
 {
      int x;
      this(int _x) { this.x = _x; }
 }
 const class ConstType {
      int x;
      this(int _x) { this.x = _x; }
 }
 MutableType m = new MutableType(5);
 ConstType c = new ConstType(5);
 m.x = 10; // OK
 c.x = 10; // ERROR
 ---

 All classes that extend a const class would also need to be const. As  
 for primitives, they would either be manifest-constant or mutable like  
 in D1. This covers most common use cases very well (except for constant  
 arrays... hmmmm....) and is easy for the user to understand. Plus, a  
 "const class" under this system is the same as an "invariant" under the  
 current system, so no synchronization required. Making invariant classes  
 (classes that are final and where all members are final) are a common  
 design pattern in Java.

 But I'm dreaming here since Walter seems dead-set on this system.

Like in current d2, you mean? const class foo { } class bar : foo { } void main() { auto a = new foo(); auto b = new bar(); foo f = new foo(); writefln(typeof(a).stringof); // prints const(foo) writefln(typeof(b).stringof); // prints const(bar) writefln(typeof(f).stringof); // prints const(foo) } -- Simen
Oct 15 2008
prev sibling parent "Bill Baxter" <wbaxter gmail.com> writes:
On Sun, Oct 19, 2008 at 3:45 AM, Bruno Medeiros
<brunodomedeiros+spam com.gmail> wrote:
 Andrei Alexandrescu wrote:
 You'll be glad then to learn that "in" means const at least in D2:

 void foo(in char[] s); // same as foo(const(char)[] s)

What?? Whoa, at first I thought you were mistaken, and meant 'const(char[]) s' instead (since that is what is the same as 'const char[] s'), but I fired up my editor and tried it out, and it works as you described! Even more surprising, it works the same way when using a class type: class Foo { int x; } void func(in Foo foo, const scope Foo foo2) { foo = null; // Ok! //foo2 = null; //Compile error! //foo.x = 0; // Compile error! pragma(msg, (typeof(foo)).stringof ~ " " ~ (typeof(foo2)).stringof); } Which means 'in' works exactly as headconst! Is this another easter egg, or a bug? It's certainly not according to the spec at least.

I think you mean "tailconst". The head is not const, the tail is. --bb
Oct 18 2008