www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Why D const is annoying

reply Mehrdad <wfunction hotmail.com> writes:
I just thought I'd give D another try, after having given up on it for a 
while.

Lo and behold... the same old kind of problem from a year ago is still 
here. :(

Simple stuff like this:

     import std.algorithm;
     void main() {
         const arr = [1, 2, 3];
         reduce!"a*b"(arr);   // You'd think it'd work...
     }

Results in ridiculously annoying errors like:
     // algorithm.d(728): Error: can only initialize const member 
_field_field_0 inside constructor

I **HIGHLY** suggest that priority be given to simple problems like 
these, instead of OMG-so-cool-libraries. It really doesn't matter if 
there is an uber-awesome collections/CURL/whatever library out there, 
when problems like these exist. If a 2-line piece of code needs a 
workaround, then (IMO) people simply won't care about anything else 
that's more complicated.

I'll post more as I find them. (I'd found plenty a few months ago, now I 
just need to find them again.)
Too lazy/busy studying to post as a bug. And it feels a bit better when 
I add the complaint. :P

(Sorry for being so critical but at least I tried to make it 
constructive...)
Dec 10 2011
next sibling parent reply Mehrdad <wfunction hotmail.com> writes:
Moar coming... hey look, it's ANOTHER const-ness issue! :(

import std.algorithm;
struct Matrix
{
     size_t[] _lengths;
      property inout(size_t)[] lengths() inout { return _lengths; }
      property size_t length() inout { return reduce!q{a*b}(lengths); }
}

// Error: template std.algorithm.reduce!("a*b").reduce(Args...) if 
(Args.length > 0 && Args.length <= 2 && isIterable!(Args[__dollar - 1])) 
does not match any function template declaration
// Error: template std.algorithm.reduce!("a*b").reduce(Args...) if 
(Args.length > 0 && Args.length <= 2 && isIterable!(Args[__dollar - 1])) 
cannot deduce template function from argument types !()(inout(uint[2u]))
Dec 10 2011
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/10/2011 10:43 AM, Mehrdad wrote:
 Moar coming... hey look, it's ANOTHER const-ness issue! :(

 import std.algorithm;
 struct Matrix
 {
 size_t[] _lengths;
  property inout(size_t)[] lengths() inout { return _lengths; }
  property size_t length() inout { return reduce!q{a*b}(lengths); }
 }

 // Error: template std.algorithm.reduce!("a*b").reduce(Args...) if
 (Args.length > 0 && Args.length <= 2 && isIterable!(Args[__dollar - 1]))
 does not match any function template declaration
 // Error: template std.algorithm.reduce!("a*b").reduce(Args...) if
 (Args.length > 0 && Args.length <= 2 && isIterable!(Args[__dollar - 1]))
 cannot deduce template function from argument types !()(inout(uint[2u]))

http://d.puremagic.com/issues/show_bug.cgi?id=6809
Dec 10 2011
prev sibling next sibling parent "SomeDude" <lovelydear mailmetrash.com> writes:
On Saturday, 10 December 2011 at 09:50:27 UTC, Timon Gehr wrote:
 On 12/10/2011 10:43 AM, Mehrdad wrote:

 http://d.puremagic.com/issues/show_bug.cgi?id=6809

This one now works (I just updated the issue), but the bug with reduce! is still open.
Apr 28 2012
prev sibling parent "SomeDude" <lovelydear mailmetrash.com> writes:
On Saturday, 28 April 2012 at 09:26:36 UTC, SomeDude wrote:
 On Saturday, 10 December 2011 at 09:50:27 UTC, Timon Gehr wrote:
 On 12/10/2011 10:43 AM, Mehrdad wrote:

 http://d.puremagic.com/issues/show_bug.cgi?id=6809

This one now works (I just updated the issue), but the bug with reduce! is still open.

Never mind. I just read the answer from Jonathan Davis. Still, this should be documented somewhere, because the fact that you have to slice a const array is not immediately obvious.
Apr 28 2012
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/10/2011 10:37 AM, Mehrdad wrote:
 I just thought I'd give D another try, after having given up on it for a
 while.

 Lo and behold... the same old kind of problem from a year ago is still
 here. :(

 Simple stuff like this:

 import std.algorithm;
 void main() {
 const arr = [1, 2, 3];
 reduce!"a*b"(arr); // You'd think it'd work...
 }

 Results in ridiculously annoying errors like:
 // algorithm.d(728): Error: can only initialize const member
 _field_field_0 inside constructor

 I **HIGHLY** suggest that priority be given to simple problems like
 these, instead of OMG-so-cool-libraries.

It is a *library bug*. Just have a look at the source code.
 It really doesn't matter if
 there is an uber-awesome collections/CURL/whatever library out there,
 when problems like these exist. If a 2-line piece of code needs a
 workaround, then (IMO) people simply won't care about anything else
 that's more complicated.

 I'll post more as I find them. (I'd found plenty a few months ago, now I
 just need to find them again.)
 Too lazy/busy studying to post as a bug. And it feels a bit better when
 I add the complaint. :P

 (Sorry for being so critical but at least I tried to make it
 constructive...)

Just slice the const array to get a range. The specialization for ranges does not have the bug. import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr[]); // It works. }
Dec 10 2011
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Timon Gehr:

 Just slice the const array to get a range. The specialization for ranges 
 does not have the bug.
 
 import std.algorithm;
 void main() {
      const arr = [1, 2, 3];
      reduce!"a*b"(arr[]);   // It works.
 }

Wasn't arr a range already? Bye, bearophile
Dec 10 2011
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/10/2011 11:45 AM, bearophile wrote:
 Timon Gehr:

 Just slice the const array to get a range. The specialization for ranges
 does not have the bug.

 import std.algorithm;
 void main() {
       const arr = [1, 2, 3];
       reduce!"a*b"(arr[]);   // It works.
 }

Wasn't arr a range already? Bye, bearophile

No, popFront is mutating and const(int[]) cannot be mutated.
Dec 10 2011
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Timon Gehr:

 No, popFront is mutating and const(int[]) cannot be mutated.

So, is it impossible by design to iterate immutable collections? Bye, bearophile
Dec 10 2011
next sibling parent reply Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 3:08 AM, bearophile wrote:
 Timon Gehr:

 No, popFront is mutating and const(int[]) cannot be mutated.

Bye, bearophile

I believe the answer is yes (although Timon would probably know better). That's one reason I believe const is broken...
Dec 10 2011
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/10/2011 12:10 PM, Mehrdad wrote:
 On 12/10/2011 3:08 AM, bearophile wrote:
 Timon Gehr:

 No, popFront is mutating and const(int[]) cannot be mutated.

Bye, bearophile

I believe the answer is yes (although Timon would probably know better).

You can always have a mutable range over the immutable collection contents.
 That's one reason I believe const is broken...

Well, you are essentially complaining about the fact that const/immutable variables cannot be changed. I believe that very likely this is the point of having them. If you want to mutate a variable don't make its type const or immutable.
Dec 10 2011
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/10/11 5:10 AM, Mehrdad wrote:
 On 12/10/2011 3:08 AM, bearophile wrote:
 Timon Gehr:

 No, popFront is mutating and const(int[]) cannot be mutated.

Bye, bearophile

I believe the answer is yes (although Timon would probably know better). That's one reason I believe const is broken...

That is incorrect (it's easy to design a range iterating over an immutable collection, and in particular const(T)[] iterates a const T[] no problem), but taking the time to vent about the annoyances you've encountered is highly appreciated. I suspect many people try D just like you do, with a few cool ideas (no conservative "hello world" programs), see they don't work for obscure reasons, go like "meh", and give up. So having such an experience _documented_ is of great value. I'm every bit as annoyed as you are about simple things not working with qualifiers, and I have a few ideas on how to fix it. I should add that I very strongly believe a problem with a workaround does not implicitly cease being a problem. Andrei
Dec 10 2011
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/10/2011 12:08 PM, bearophile wrote:
 Timon Gehr:

 No, popFront is mutating and const(int[]) cannot be mutated.

So, is it impossible by design to iterate immutable collections?

No, it is impossible to popFront an immutable array. Because it is, well, immutable. But you can always slice it and get a range back that can be used to iterate the array (btw thanks to the devs for fixing that, that did not work a few releases ago!). Library defined immutable collection types can/should use the same pattern.
Dec 10 2011
prev sibling next sibling parent Kai Meyer <kai unixlords.com> writes:
On 12/10/2011 03:52 AM, Timon Gehr wrote:
 On 12/10/2011 11:45 AM, bearophile wrote:
 Timon Gehr:

 Just slice the const array to get a range. The specialization for ranges
 does not have the bug.

 import std.algorithm;
 void main() {
 const arr = [1, 2, 3];
 reduce!"a*b"(arr[]); // It works.
 }

Wasn't arr a range already? Bye, bearophile

No, popFront is mutating and const(int[]) cannot be mutated.

Seems to me like popFront isn't the right tool for the job.
Dec 12 2011
prev sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, December 12, 2011 12:13:54 Kai Meyer wrote:
 On 12/10/2011 03:52 AM, Timon Gehr wrote:
 On 12/10/2011 11:45 AM, bearophile wrote:
 Timon Gehr:
 Just slice the const array to get a range. The specialization for
 ranges does not have the bug.
 
 import std.algorithm;
 void main() {
 const arr = [1, 2, 3];
 reduce!"a*b"(arr[]); // It works.
 }

Wasn't arr a range already? Bye, bearophile

No, popFront is mutating and const(int[]) cannot be mutated.

Seems to me like popFront isn't the right tool for the job.

??? That's how you iterate a range. Without it, a range is essentially useless. There _is_ no other tool for the job. Ranges _could_ have been designed more like slists and use head and tail and avoid needing to be mutated, but that's less efficient, since you have to copy the range every time that you iterate to the next item. Generally, the way to solve the issue of a const or immutable range is to get a tail-const copy of it. With arrays, that's easy - just slice it. With user- defined ranges, it requires that opSlice be defined that way, which it may or may not be. But you can't iterate over a const range anymore than you can iterate over a const iterator in C++. In both cases, you need a non-const copy if you want to iterate. There's nothing odd about it really. It's just that templates take the _exact_ type with IFTI and so don't generally work with const or immutable ranges. The proposed change to make arrays instantiate templates with their tail-const type with IFTI will fix that for arrays though. - Jonathan M Davis
Dec 12 2011
prev sibling parent Graham St Jack <Graham.StJack internode.on.net> writes:
On 11/12/11 06:55, Jonathan M Davis wrote:
 On Saturday, December 10, 2011 05:45:11 bearophile wrote:
 Timon Gehr:
 Just slice the const array to get a range. The specialization for ranges
 does not have the bug.

 import std.algorithm;
 void main() {

       const arr = [1, 2, 3];
       reduce!"a*b"(arr[]);   // It works.

 }


use them, because it's const. A const range is essentially useless, because you can't iterate over it. When a template is instantiated, it's instantiated on the exact types that it's given. So, if you given a const or immutable array, it's going to instantiate on that type, even though it _could_ theoretically instantiate itself with a mutable array with const or immutable elements. And since, a const or immutable array won't work as a range, the template instantiation fails. The range-based functions in std.array and std.string work with const and immutable arrays simply because they are either coded specifically for arrays or have specializations for them. You need a function which takes const(T)[] rather than one which takes const T[]. std.array and std.string typically take const(T)[] rather than const T[], whereas a more general range function is taking R, which in the case above is determined to be const int[], which won't work as a range. It used to be that the slice of an array was the exact type of that array, meaning Timon's solution wouldn't work without a cast, because the slice would be just as const as the original. Fortunately, that has been changed, so now a slice of a const or immutable array will have its elements be const or immutable but not the slice itself. So, now const and immutable arrays are like static arrays in that you need to slice them to use them with range-based functions, but you can't use them directly with range-based functions. A bigger problem is const or immutable ranges which are structs. Unless the programmer who defined the range type managed to have an opSlice which returned a version with const or immutable elements but where the range itself wasn't const or immutable (i.e. a tail-const slice), then you can't even get slicing to work. And even if templates were improved to the point that they would instantiate const int[] as const(int)[] (which I expect is a change which will never happen due to the difficulties in doing so), that wouldn't solve the problem for ranges which aren't arrays.

return a mutable range over its immutable elements? This would be analogous to slicing an immutable array.
 Also, const and immutable ranges which aren't arrays which have no opSlice
 will _never_ work, because there's no way to get a mutable slice of them to
 operate on, so you're stuck with a const or immutable range.

 Really what this means is that you need to slice const and immutable ranges
 when you pass them to range-based functions, and then as long as they're
 arrays or their opSlices have been defined properly, it'll work just fine.

 If ranges had been designed more like slists (i.e. they have head and tail
 rather than front and popFront), then they would have been inherently tail-
 const and we wouldn't be having these problems. But that's less efficent,
 because you have to copy the range every time that you want to remove an
 element from it. Regardless, it's too late now. Ranges are too heavily used
 with their current API for us to change it that drastically now.

 - Jonathan M Davis

-- Graham St Jack
Dec 11 2011
prev sibling next sibling parent Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 2:00 AM, Timon Gehr wrote:
 On 12/10/2011 10:37 AM, Mehrdad wrote:
 I just thought I'd give D another try, after having given up on it for a
 while.

 Lo and behold... the same old kind of problem from a year ago is still
 here. :(

 Simple stuff like this:

 import std.algorithm;
 void main() {
 const arr = [1, 2, 3];
 reduce!"a*b"(arr); // You'd think it'd work...
 }

 Results in ridiculously annoying errors like:
 // algorithm.d(728): Error: can only initialize const member
 _field_field_0 inside constructor

 I **HIGHLY** suggest that priority be given to simple problems like
 these, instead of OMG-so-cool-libraries.

It is a *library bug*. Just have a look at the source code.

Did I say something else?
 It really doesn't matter if
 there is an uber-awesome collections/CURL/whatever library out there,
 when problems like these exist. If a 2-line piece of code needs a
 workaround, then (IMO) people simply won't care about anything else
 that's more complicated.

 I'll post more as I find them. (I'd found plenty a few months ago, now I
 just need to find them again.)
 Too lazy/busy studying to post as a bug. And it feels a bit better when
 I add the complaint. :P

 (Sorry for being so critical but at least I tried to make it
 constructive...)

Just slice the const array to get a range. The specialization for ranges does not have the bug.

Dec 10 2011
prev sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, December 10, 2011 05:45:11 bearophile wrote:
 Timon Gehr:
 Just slice the const array to get a range. The specialization for ranges
 does not have the bug.
 
 import std.algorithm;
 void main() {
 
      const arr = [1, 2, 3];
      reduce!"a*b"(arr[]);   // It works.
 
 }

Wasn't arr a range already?

Per isInputRange!(typeof(arr)), no. It has the right functions, but it can't use them, because it's const. A const range is essentially useless, because you can't iterate over it. When a template is instantiated, it's instantiated on the exact types that it's given. So, if you given a const or immutable array, it's going to instantiate on that type, even though it _could_ theoretically instantiate itself with a mutable array with const or immutable elements. And since, a const or immutable array won't work as a range, the template instantiation fails. The range-based functions in std.array and std.string work with const and immutable arrays simply because they are either coded specifically for arrays or have specializations for them. You need a function which takes const(T)[] rather than one which takes const T[]. std.array and std.string typically take const(T)[] rather than const T[], whereas a more general range function is taking R, which in the case above is determined to be const int[], which won't work as a range. It used to be that the slice of an array was the exact type of that array, meaning Timon's solution wouldn't work without a cast, because the slice would be just as const as the original. Fortunately, that has been changed, so now a slice of a const or immutable array will have its elements be const or immutable but not the slice itself. So, now const and immutable arrays are like static arrays in that you need to slice them to use them with range-based functions, but you can't use them directly with range-based functions. A bigger problem is const or immutable ranges which are structs. Unless the programmer who defined the range type managed to have an opSlice which returned a version with const or immutable elements but where the range itself wasn't const or immutable (i.e. a tail-const slice), then you can't even get slicing to work. And even if templates were improved to the point that they would instantiate const int[] as const(int)[] (which I expect is a change which will never happen due to the difficulties in doing so), that wouldn't solve the problem for ranges which aren't arrays. Also, const and immutable ranges which aren't arrays which have no opSlice will _never_ work, because there's no way to get a mutable slice of them to operate on, so you're stuck with a const or immutable range. Really what this means is that you need to slice const and immutable ranges when you pass them to range-based functions, and then as long as they're arrays or their opSlices have been defined properly, it'll work just fine. If ranges had been designed more like slists (i.e. they have head and tail rather than front and popFront), then they would have been inherently tail- const and we wouldn't be having these problems. But that's less efficent, because you have to copy the range every time that you want to remove an element from it. Regardless, it's too late now. Ranges are too heavily used with their current API for us to change it that drastically now. - Jonathan M Davis
Dec 10 2011
parent bearophile <bearophileHUGS lycos.com> writes:
Jonathan M Davis:

 Per isInputRange!(typeof(arr)), no. It has the right functions, but it can't 
 use them, because it's const. 
 [... snip]
 
 Ranges are too heavily used 
 with their current API for us to change it that drastically now.

Thank you Jonathan for taking the time to explain me all that :-) I am taking years to learn D well :-) Bye, bearophile
Dec 10 2011
prev sibling next sibling parent reply Mehrdad <wfunction hotmail.com> writes:
... and another...

struct S(T, int N)
{ public auto opBinary(string op)(in S!(T, N) other) const { return 
this; } }
void main() { S!(int, 1) m; m = m * m; }

Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)
Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)
Dec 10 2011
next sibling parent reply so <so so.so> writes:
On Sat, 10 Dec 2011 12:18:31 +0200, Mehrdad <wfunction hotmail.com> wrote:

 ... and another...

 struct S(T, int N)
 { public auto opBinary(string op)(in S!(T, N) other) const { return  
 this; } }
 void main() { S!(int, 1) m; m = m * m; }

 Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)
 Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)

Oh fantastic, so you are writing the first bug-free software of our time?
Dec 10 2011
parent Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 2:28 AM, so wrote:
 On Sat, 10 Dec 2011 12:18:31 +0200, Mehrdad <wfunction hotmail.com> 
 wrote:

 ... and another...

 struct S(T, int N)
 { public auto opBinary(string op)(in S!(T, N) other) const { return 
 this; } }
 void main() { S!(int, 1) m; m = m * m; }

 Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)
 Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)

Oh fantastic, so you are writing the first bug-free software of our time?

Nope, sorry. :( I'm listing a bunch of bugs I've found. And yes, I'm [semi-]complaining about them in the process.
Dec 10 2011
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/10/2011 11:18 AM, Mehrdad wrote:
 ... and another...

 struct S(T, int N)
 { public auto opBinary(string op)(in S!(T, N) other) const { return
 this; } }
 void main() { S!(int, 1) m; m = m * m; }

 Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)
 Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)

It is unrelated to const, this gives the same error. struct S(T, int N) { public auto opBinary(string op)(S!(T, N) other) { return this; } } void main() { S!(int, 1) m; m = m * m; } And this works: struct S(T, int N) { public auto opBinary(string op)(in S other) const { return this; } } void main() { S!(int, 1) m; m = m * m; } You have to make sure that the signature of your template actually compiles. Maybe the compiler should emit a diagnostic for that case. If you blame all your compile errors on const, I can understand why you think its design is broken.
Dec 10 2011
parent reply Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 2:46 AM, Timon Gehr wrote:
 On 12/10/2011 11:18 AM, Mehrdad wrote:
 ... and another...

 struct S(T, int N)
 { public auto opBinary(string op)(in S!(T, N) other) const { return
 this; } }
 void main() { S!(int, 1) m; m = m * m; }

 Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)
 Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)

It is unrelated to const, this gives the same error. struct S(T, int N) { public auto opBinary(string op)(S!(T, N) other) { return this; } } void main() { S!(int, 1) m; m = m * m; }

related. :P I just had the word 'const' in there because it was in my original code and I forgot to take it out.
 And this works:

 struct S(T, int N)
 { public auto opBinary(string op)(in S other) const { return this; } }
 void main() { S!(int, 1) m; m = m * m; }

 You have to make sure that the signature of your template actually 
 compiles. Maybe the compiler should emit a diagnostic for that case.

 If you blame all your compile errors on const, I can understand why 
 you think its design is broken.

Not _all_ of them (e.g. I figured this one wasn't one of them), but yes, a significant fraction of them that I've seen, only a few of which I've been in the mood to post. :) So yeah, that's why I've come to the conclusion that it's broken.
Dec 10 2011
parent Mehrdad <wfunction hotmail.com> writes:
Oh right, I see why you assumed I blamed it on const -- it's in the 
title of the post.

Nah, only the beginning was about const. The rest was the issues I saw 
when working on the same library, so I posted them here. Sorry for 
keeping the (now-misleading) title.
Dec 10 2011
prev sibling next sibling parent reply Mehrdad <wfunction hotmail.com> writes:
... and another...

struct Matrix(T, size_t N = 1)
{ public auto mul(Matrix!(T, N) other) const { return this; } }

void main()
{
     Matrix!(int, 2) m;
     m.mul(m);
}

annoy.d(7): Error: function annoy.Matrix!(int,2).Matrix.mul 
(Matrix!(int,N) other) const is not callable using argument types 
(Matrix!(int,2))
annoy.d(7): Error: cannot implicitly convert expression (m) of type 
Matrix!(int,2) to Matrix!(int,N)
Dec 10 2011
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/10/2011 11:34 AM, Mehrdad wrote:
 ... and another...

 struct Matrix(T, size_t N = 1)
 { public auto mul(Matrix!(T, N) other) const { return this; } }

 void main()
 {
 Matrix!(int, 2) m;
 m.mul(m);
 }

 annoy.d(7): Error: function annoy.Matrix!(int,2).Matrix.mul
 (Matrix!(int,N) other) const is not callable using argument types
 (Matrix!(int,2))
 annoy.d(7): Error: cannot implicitly convert expression (m) of type
 Matrix!(int,2) to Matrix!(int,N)

Again, unrelated to const. This works: struct Matrix(T, size_t N = 1) { public auto mul(Matrix other) const { return this; } } void main() { Matrix!(int, 2) m; m.mul(m); } But it seems to be a bug indeed, because this compiles: struct Matrix(T, size_t N = 1) { public auto mul(Matrix!(T, 2) other) const { return this; } } void main() { Matrix!(int, 2) m; m.mul(m); }
Dec 10 2011
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/10/2011 2:34 AM, Mehrdad wrote:
 ... and another...

 struct Matrix(T, size_t N = 1)
 { public auto mul(Matrix!(T, N) other) const { return this; } }

In general, to refer to the current template being expanded from inside of it, just give its name, as in: Matrix!(T,N) => Matrix
Dec 10 2011
parent reply Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 3:01 AM, Walter Bright wrote:
 On 12/10/2011 2:34 AM, Mehrdad wrote:
 ... and another...

 struct Matrix(T, size_t N = 1)
 { public auto mul(Matrix!(T, N) other) const { return this; } }

In general, to refer to the current template being expanded from inside of it, just give its name, as in: Matrix!(T,N) => Matrix

If you say 'int' instead of size_t, it works fine.
Dec 10 2011
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/10/2011 3:04 AM, Mehrdad wrote:
 On 12/10/2011 3:01 AM, Walter Bright wrote:
 On 12/10/2011 2:34 AM, Mehrdad wrote:
 ... and another...

 struct Matrix(T, size_t N = 1)
 { public auto mul(Matrix!(T, N) other) const { return this; } }

In general, to refer to the current template being expanded from inside of it, just give its name, as in: Matrix!(T,N) => Matrix

If you say 'int' instead of size_t, it works fine.

That's because Matrix!(int, cast(int)1) is considered a different template instantiation (and hence a different type) from Matrix!(int, cast(uint)1) Instantiation types are based on the arguments' types, not the parameters' types. If you use my suggestion for the shorthand notation, your code will work as you expect.
Dec 10 2011
next sibling parent reply Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 3:11 AM, Walter Bright wrote:
 That's because

  Matrix!(int, cast(int)1)

 is considered a different template instantiation (and hence a 
 different type) from

  Matrix!(int, cast(uint)1)

 Instantiation types are based on the arguments' types, not the 
 parameters' types.

 If you use my suggestion for the shorthand notation, your code will 
 work as you expect.

Yup, I started using it as soon as Timon mentioned it (thanks for the suggestion!). But I was referring to the bug, not to the workaround. :) (I don't understand why Matrix!(int, cast(int)1) is considered a different instantiation, when it can't even be instantiated...)
Dec 10 2011
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/10/2011 3:17 AM, Mehrdad wrote:
 Yup, I started using it as soon as Timon mentioned it (thanks for the
suggestion!).
 But I was referring to the bug, not to the workaround. :)

It isn't a bug, it is designed to work that way. The type of the instantiation is based on the argument types, not the parameter types. (Note that "2" is the argument and "N" is the parameter.) Hence the message: Error: cannot implicitly convert expression (m) of type Matrix!(int,2) to Matrix!(int,N)
 (I don't understand
 why Matrix!(int, cast(int)1) is considered a different instantiation, when it
 can't even be instantiated...)

Yes, it can be instantiated. But it cannot be implicitly converted to type Matrix!(int, cast(uint)1) because they are different types.
Dec 10 2011
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/10/2011 12:27 PM, Walter Bright wrote:
 On 12/10/2011 3:17 AM, Mehrdad wrote:
 Yup, I started using it as soon as Timon mentioned it (thanks for the
 suggestion!).
 But I was referring to the bug, not to the workaround. :)

It isn't a bug, it is designed to work that way.

I think the design might contain a bug and should be revisited.
 The type of the
 instantiation is based on the argument types, not the parameter types.
 (Note that "2" is the argument and "N" is the parameter.)

 Hence the message:

 Error: cannot implicitly convert expression (m) of type Matrix!(int,2)
 to Matrix!(int,N)


 (I don't understand
 why Matrix!(int, cast(int)1) is considered a different instantiation,
 when it
 can't even be instantiated...)

Yes, it can be instantiated. But it cannot be implicitly converted to type Matrix!(int, cast(uint)1) because they are different types.

What is the benefit of having this apparent mis-feature? It is similar to having the calling conventions of a function depend on the actual arguments instead of the formal parameters. It would likely result in a segmentation fault. For templates, luckily the compiler only blows up with an error message in counter-intuitive ways. I think there must be a very good reason to keep this.
Dec 10 2011
prev sibling next sibling parent Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 3:27 AM, Walter Bright wrote:
 (I don't understand
 why Matrix!(int, cast(int)1) is considered a different instantiation, 
 when it
 can't even be instantiated...)

Yes, it can be instantiated.

Uh, what? How can it be instantiated? There's no such template in my code...
Dec 10 2011
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/10/11 5:27 AM, Walter Bright wrote:
 On 12/10/2011 3:17 AM, Mehrdad wrote:
 Yup, I started using it as soon as Timon mentioned it (thanks for the
 suggestion!).
 But I was referring to the bug, not to the workaround. :)

It isn't a bug, it is designed to work that way.

The design is wrong.
 The type of the
 instantiation is based on the argument types, not the parameter types.

This is wrong. The type of parameter is size_t, no two ways about it. It is NOT alias.
 (Note that "2" is the argument and "N" is the parameter.)

Not that N has type size_t.
 Hence the message:

 Error: cannot implicitly convert expression (m) of type Matrix!(int,2)
 to Matrix!(int,N)

The message reflects a mistake.
 (I don't understand
 why Matrix!(int, cast(int)1) is considered a different instantiation,
 when it
 can't even be instantiated...)

Yes, it can be instantiated. But it cannot be implicitly converted to type Matrix!(int, cast(uint)1) because they are different types.

They shouldn't. Andrei
Dec 10 2011
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, December 10, 2011 13:12:06 Andrei Alexandrescu wrote:
 On 12/10/11 5:27 AM, Walter Bright wrote:
 On 12/10/2011 3:17 AM, Mehrdad wrote:
 Yup, I started using it as soon as Timon mentioned it (thanks for the
 suggestion!).
 But I was referring to the bug, not to the workaround. :)

It isn't a bug, it is designed to work that way.

The design is wrong.
 The type of the
 instantiation is based on the argument types, not the parameter types.

This is wrong. The type of parameter is size_t, no two ways about it. It is NOT alias.
 (Note that "2" is the argument and "N" is the parameter.)

Not that N has type size_t.
 Hence the message:
 
 Error: cannot implicitly convert expression (m) of type Matrix!(int,2)
 to Matrix!(int,N)

The message reflects a mistake.
 (I don't understand
 why Matrix!(int, cast(int)1) is considered a different instantiation,
 when it
 can't even be instantiated...)

Yes, it can be instantiated. But it cannot be implicitly converted to type Matrix!(int, cast(uint)1) because they are different types.

They shouldn't.

Templates should _always_ be instantiated on the _value_ of their arguments. In the case such as template t(T) {} t!int the template argument is a type, so the template is instantiated on that type. In the case of template t(int i) {} t!5 the argument is the value 5, so the template should be instatiated on the value 5, regardless of whether the argument is an int or uint or any other type implicitly convertible to the template's parameter's type. I do not understand how Walter could think otherwise. Having template t(int i) {} t!5 t!5u create two separate templates makes no sense. I don't see what value it could possible have. And with the addition of CTFE functions or other templates whose result is auto, it could cause real problems. Bug for this: http://d.puremagic.com/issues/show_bug.cgi?id=3467 - Jonathan M Davis
Dec 10 2011
prev sibling next sibling parent reply David Nadlinger <see klickverbot.at> writes:
On 12/10/11 12:11 PM, Walter Bright wrote:
 That's because

 Matrix!(int, cast(int)1)

 is considered a different template instantiation (and hence a different
 type) from

 Matrix!(int, cast(uint)1)

 Instantiation types are based on the arguments' types, not the
 parameters' types.

Which is, in my not-so-humble opinion, just plain broken. Template value parameters are, well, values, and there shouldn't ever be a difference between whether a value of an integral type represents 1 or 1u (if it accepts both). This is a long-standing bug, and makes unsigned template value parameters and, to a lesser extent, integral template value parameters in general virtually unusable, because you hit hard to debug issues with confusing error messages all over the place. It gets even worse once CTFE comes into play (or compile-time values in general, as opposed to directly passing number literals) for two reasons: First, the error messages become totally useless, i.e. in most cases something similar to »Foo!(n) can't be implicitly converted to Foo!(n)«. Second, even if you litter your code with explicit casts all over the place, it is not always possible to get it to behave like intended because of what I presume are issues with constant folding. I know this first hand, because I worked on a units-of-measurement library which relies on units being uniquely represented as types, because the unit type becomes part of the type of quantities as a template parameter (I presented it at the NG a while back, by the way: http://www.digitalmars.com/d/archives/digitalmars/D/RFC_Units_of_measurement_for_D_ hobos_134590.html). For derived units (e.g. kg m/s^2), you need to »normalize« the exponents using compile time rational arithmetic, which was really hard to get to work because of the issue discussed here (I don't even remember all the details). But even with my own unit tests passing, I now once in a while get a message saying something like: »Hey, I checked out your units library prototype, and while it works nice for most cases, I encountered this situation where … [some situation in which 1 != 1 for DMD]«. I tried hard to work around this, but I couldn't find a reliable solution. Also, I'd like to note that I am not alone with this opinion; Don, Kenji and Jonathan, among others, agree that the current behavior is confusing. A few reports of this issue (and related ones): http://d.puremagic.com/issues/show_bug.cgi?id=3467 http://d.puremagic.com/issues/show_bug.cgi?id=2257 http://d.puremagic.com/issues/show_bug.cgi?id=2550 Kenji even came up with a fix in form of a pull request, which you turned down without further explanation: https://github.com/D-Programming-Language/dmd/pull/449 So, could you please at least elaborate on why you think the current behavior is correct, and how its advantages outweigh the massive confusion it causes? Thanks, David
Dec 10 2011
parent Walter Bright <newshound2 digitalmars.com> writes:
On 12/10/2011 5:04 AM, David Nadlinger wrote:
 So, could you please at least elaborate on why you think the current behavior
is
 correct, and how its advantages outweigh the massive confusion it causes?

The original idea was that you could add specializations without breaking existing binaries. However, experience seems to indicate that this isn't worth it.
Dec 10 2011
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/10/11 5:11 AM, Walter Bright wrote:
 On 12/10/2011 3:04 AM, Mehrdad wrote:
 On 12/10/2011 3:01 AM, Walter Bright wrote:
 On 12/10/2011 2:34 AM, Mehrdad wrote:
 ... and another...

 struct Matrix(T, size_t N = 1)
 { public auto mul(Matrix!(T, N) other) const { return this; } }

In general, to refer to the current template being expanded from inside of it, just give its name, as in: Matrix!(T,N) => Matrix

If you say 'int' instead of size_t, it works fine.

That's because Matrix!(int, cast(int)1) is considered a different template instantiation (and hence a different type) from Matrix!(int, cast(uint)1) Instantiation types are based on the arguments' types, not the parameters' types. If you use my suggestion for the shorthand notation, your code will work as you expect.

Please let's not deflect the issue. If there's a patentable way to annoy an annoyed user even more, this must be it. A bug is a bug is a bug. Matrix can _only_ be instantiated with size_t, it says so in the definition: struct Matrix(T, size_t N = 1) { ... } THAT IS NOT: struct Matrix(T, alias N = cast(size_t) 1) { ... } as the current compiler seems to wrongly implement it. The fact that one can actually instantiate it with int or whatnot should first convert the int to size_t, and then instantiate the matrix with size_t. http://d.puremagic.com/issues/show_bug.cgi?id=7090 Thanks Mehrdad. Andrei
Dec 10 2011
prev sibling next sibling parent Trass3r <un known.com> writes:
Am 10.12.2011, 12:27 Uhr, schrieb Walter Bright  
<newshound2 digitalmars.com>:

 On 12/10/2011 3:17 AM, Mehrdad wrote:
 Yup, I started using it as soon as Timon mentioned it (thanks for the  
 suggestion!).
 But I was referring to the bug, not to the workaround. :)

It isn't a bug, it is designed to work that way. The type of the instantiation is based on the argument types, not the parameter types. (Note that "2" is the argument and "N" is the parameter.) Hence the message: Error: cannot implicitly convert expression (m) of type Matrix!(int,2) to Matrix!(int,N)

That error message is crap and struck me as well some time ago. It hides the real cause: int != size_t 1 is implicitly convertible to size_t so what's the problem with using this conversion if no explicit template specialization with int exists?
Dec 10 2011
prev sibling next sibling parent "Bernard Helyer" <b.helyer gmail.com> writes:
On Saturday, 10 December 2011 at 11:11:44 UTC, Walter Bright 
wrote:
 On 12/10/2011 3:04 AM, Mehrdad wrote:
 On 12/10/2011 3:01 AM, Walter Bright wrote:
 On 12/10/2011 2:34 AM, Mehrdad wrote:
 ... and another...

 struct Matrix(T, size_t N = 1)
 { public auto mul(Matrix!(T, N) other) const { return this; 
 } }

In general, to refer to the current template being expanded from inside of it, just give its name, as in: Matrix!(T,N) => Matrix

If you say 'int' instead of size_t, it works fine.

That's because Matrix!(int, cast(int)1) is considered a different template instantiation (and hence a different type) from Matrix!(int, cast(uint)1)

Madness. I can't believe you think this is sane behaviour.
Dec 10 2011
prev sibling next sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Saturday, 10 December 2011 at 12:15:14 UTC, David Nadlinger 
wrote:
 So, could you please at least elaborate why you think the 
 current behavior is correct, and what advantages it has to 
 outweigh the massive confusion it causes?

This. I don't see how it makes any sense from a design perspective.
Dec 10 2011
prev sibling next sibling parent Trass3r <un known.com> writes:
 This is a long-standing bug, and makes unsigned template value  
 parameters and, to a lesser extent, integral template value parameters  
 in general virtually unusable, because you hit hard to debug issues with  
 confusing error messages all over the place.

I absolutely agree.
Dec 10 2011
prev sibling parent "SomeDude" <lovelydear mailmetrash.com> writes:
On Saturday, 10 December 2011 at 10:34:28 UTC, Mehrdad wrote:
 ... and another...

 struct Matrix(T, size_t N = 1)
 { public auto mul(Matrix!(T, N) other) const { return this; } }

 void main()
 {
     Matrix!(int, 2) m;
     m.mul(m);
 }

 annoy.d(7): Error: function annoy.Matrix!(int,2).Matrix.mul 
 (Matrix!(int,N) other) const is not callable using argument 
 types (Matrix!(int,2))
 annoy.d(7): Error: cannot implicitly convert expression (m) of 
 type Matrix!(int,2) to Matrix!(int,N)

This one now works. :)
Apr 28 2012
prev sibling next sibling parent Mehrdad <wfunction hotmail.com> writes:
... and another...

struct S(L...) {
     enum N = L[0];
     S!(N) me() { return this; }
}
alias S!(1) S2;

annoy.d(3): Error: template instance annoy.S!(N) recursive expansion
Dec 10 2011
prev sibling next sibling parent reply Mehrdad <wfunction hotmail.com> writes:
... and another... (yes, this one _IS_ a const issue)

struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { 
return 0; } }
void main()
{
     foreach (i; S()) { }
}

Error: inout on parameter means inout must be on return type as well (if 
from D1 code, replace with 'ref')

--------------

I'll go sleep now... but I could probably go on for a while more. I'll 
try to post more of these bugs when I get the chance (assuming they're 
welcome...).
Dec 10 2011
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/10/2011 3:14 AM, Mehrdad wrote:
 ... and another... (yes, this one _IS_ a const issue)

 struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { return
0; } }
 void main()
 {
 foreach (i; S()) { }
 }

 Error: inout on parameter means inout must be on return type as well (if from
D1
 code, replace with 'ref')

Right. inout has no point if it also does not appear on the return type. The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having: { a + b; } Which is an error in D because such is pointless.
Dec 10 2011
next sibling parent reply Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 3:33 AM, Walter Bright wrote:
 On 12/10/2011 3:14 AM, Mehrdad wrote:
 ... and another... (yes, this one _IS_ a const issue)

 struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { 
 return 0; } }
 void main()
 {
 foreach (i; S()) { }
 }

 Error: inout on parameter means inout must be on return type as well 
 (if from D1
 code, replace with 'ref')

Right. inout has no point if it also does not appear on the return type. The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having: { a + b; } Which is an error in D because such is pointless.

So how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?
Dec 10 2011
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/10/2011 11:03 AM, Mehrdad wrote:
 So how are you supposed to implement opApply on a container (or e.g. here, a
 matrix)? Copy/paste the code for const- and non-const versions?

Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.
Dec 10 2011
parent reply Mafi <mafi example.org> writes:
Am 10.12.2011 21:25, schrieb Walter Bright:
 On 12/10/2011 11:03 AM, Mehrdad wrote:
 So how are you supposed to implement opApply on a container (or e.g.
 here, a
 matrix)? Copy/paste the code for const- and non-const versions?

Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.

But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something? Mafi
Dec 11 2011
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/11/2011 9:07 AM, Mafi wrote:
 But what about:
 void f(ref inout(int)* a, inout(int)* b) { a = b; }
 This cant work with const because that would violate the const system. I think
 the rule should be that either the return type must be inout or at least one
 ref/out parameter.
 Am I overlooking something?

It should work with const.
Dec 11 2011
parent reply Mafi <mafi example.org> writes:
Am 11.12.2011 19:00, schrieb Walter Bright:
 On 12/11/2011 9:07 AM, Mafi wrote:
 But what about:
 void f(ref inout(int)* a, inout(int)* b) { a = b; }
 This cant work with const because that would violate the const system.
 I think
 the rule should be that either the return type must be inout or at
 least one
 ref/out parameter.
 Am I overlooking something?

It should work with const.

But allowing a variable of type abc(int)* where abc is immutable or nothing (ie mutable) to be passed to a parameter of the type ref/out const(int)* breaks the type system. void f(ref const(int)* a, const(int)* b) {a = b; } void main() { immutable(int)* a; auto b = (new int[](5)).ptr; f(a, b); //if this compiles I have just assigned a mutable pointer //to an immutable one } With the above definition using inout, such a call would be disallowed.
Dec 11 2011
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/11/11 12:40 PM, Mafi wrote:
 void f(ref const(int)* a, const(int)* b) {a = b; }
 void main() {
    immutable(int)* a;
    auto b = (new int[](5)).ptr;
    f(a, b);
    //if this compiles I have just assigned a mutable pointer
    //to an immutable one
 }

immutable(int)* does not convert to immutable(const)*. Andrei
Dec 11 2011
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/11/2011 08:35 PM, Andrei Alexandrescu wrote:
 On 12/11/11 12:40 PM, Mafi wrote:
 void f(ref const(int)* a, const(int)* b) {a = b; }
 void main() {
 immutable(int)* a;
 auto b = (new int[](5)).ptr;
 f(a, b);
 //if this compiles I have just assigned a mutable pointer
 //to an immutable one
 }

immutable(int)* does not convert to immutable(const)*. Andrei

I think you meant an lvalue of type immutable(int)* does not convert to ref const(int)*. DMD currently allows it. http://d.puremagic.com/issues/show_bug.cgi?id=4251
Dec 11 2011
prev sibling parent Mafi <mafi example.org> writes:
Am 11.12.2011 20:35, schrieb Andrei Alexandrescu:
 On 12/11/11 12:40 PM, Mafi wrote:
 void f(ref const(int)* a, const(int)* b) {a = b; }
 void main() {
 immutable(int)* a;
 auto b = (new int[](5)).ptr;
 f(a, b);
 //if this compiles I have just assigned a mutable pointer
 //to an immutable one
 }

immutable(int)* does not convert to immutable(const)*. Andrei

But look: I want a function f(a,b) so that it copies int* b into a. I want this function to be const correct and not to be a template. If I'm not mistaken, in the planned D (ie without bugs and with everything implememnted correctly) there's no way to express such a function. I see two solutions: 1. Allow calling void f(ref const(int)* a, const(int)* b) {a = b; } with immutable parameter a. 2. Allow void f(ref inout(int)* a, inout(int)* b) { a = b; } using my rule I suggested before. I think Walter suggests number 1 but as I showed it violates the const system. I think allowing inout with ref/out parameters is right even without the return value being inout. These ref/out parameters are conceptionally results. I should also be allowed if delegate/function has a inout parameter. So void g(inout int a, delegate(inout(int)*)) should be allowed. This delegate is a callback and it's parameter is conceptionally the result of this function. In my opinion we definitly have to change the current strict inout-rule. Mafi
Dec 11 2011
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/13/2011 01:58 AM, Jesse Phillips wrote:
 On Sun, 11 Dec 2011 19:40:42 +0100
 Mafi<mafi example.org>  wrote:

 void f(ref const(int)* a, const(int)* b) {a = b; }
 void main() {
     immutable(int)* a;
     auto b = (new int[](5)).ptr;
     f(a, b);
     //if this compiles I have just assigned a mutable pointer
     //to an immutable one
 }

 With the above definition using inout, such a call would be
 disallowed.

Just gave this code a try: void f(ref const(int)* a, const(int)* b) {a = b; } void main() { const(int)* a; immutable(int)* b; auto c = (new int[](5)).ptr; f(a, c); f(b, c); //test.d(7): Error: cast(const(int)*)b is not an lvalue }

Interesting. That is another bug that masks the one you were trying to exploit. :o) Consider: void f(const ref int* a) { } void main() { immutable(int)* a; f(a); // same error, but should compile } Anyway this does the same bad stuff as your example was trying to do and currently compiles: void f(const(int)** a, const(int)* b) {*a = b; } void main() { const(int)* a; immutable(int)* b; auto c = (new int[](5)).ptr; f(&a, c); f(&b, c); // now goes through }
Dec 13 2011
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:
 On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:

 Am 10.12.2011 21:25, schrieb Walter Bright:
 On 12/10/2011 11:03 AM, Mehrdad wrote:
 So how are you supposed to implement opApply on a container (or e.g.
 here, a
 matrix)? Copy/paste the code for const- and non-const versions?

Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.

But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?

That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -Steve

This currently compiles: inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;} void main(){ immutable int a=2; int *x; immutable(int)* y=&a; very_bad(x,y); *x=1; assert(*y==a); // fail } How does the design catch this error? Will inout** = inout** assignments be disallowed?
Dec 12 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/12/2011 03:46 PM, Timon Gehr wrote:
 On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:
 On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:

 Am 10.12.2011 21:25, schrieb Walter Bright:
 On 12/10/2011 11:03 AM, Mehrdad wrote:
 So how are you supposed to implement opApply on a container (or e.g.
 here, a
 matrix)? Copy/paste the code for const- and non-const versions?

Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.

But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?

That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -Steve

This currently compiles: inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;} void main(){ immutable int a=2; int *x; immutable(int)* y=&a; very_bad(x,y); *x=1; assert(*y==a); // fail } How does the design catch this error? Will inout** = inout** assignments be disallowed?

Probably it is better to catch it at the call site. But if that is implemented then the requirement to have inout on the return value gets nonsensical.
Dec 12 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/12/2011 04:08 PM, Timon Gehr wrote:
 On 12/12/2011 03:46 PM, Timon Gehr wrote:
 On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:
 On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:

 Am 10.12.2011 21:25, schrieb Walter Bright:
 On 12/10/2011 11:03 AM, Mehrdad wrote:
 So how are you supposed to implement opApply on a container (or e.g.
 here, a
 matrix)? Copy/paste the code for const- and non-const versions?

Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.

But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?

That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -Steve

This currently compiles: inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;} void main(){ immutable int a=2; int *x; immutable(int)* y=&a; very_bad(x,y); *x=1; assert(*y==a); // fail } How does the design catch this error? Will inout** = inout** assignments be disallowed?

Probably it is better to catch it at the call site. But if that is implemented then the requirement to have inout on the return value gets nonsensical.

OK, got it. What you call 'bad' should compile: void bad(ref inout(int)* a, ref inout(int)* b); int* x; immutable(int)* y; const(int)* z; bad(x,x); // fine bad(y,y); // fine bad(z,z); // fine bad(x,y); // inout is deduced to const, ergo neither x nor y convert to the parameter type -> compile error bad(x,z); // now only error for x ... The requirement of having inout on the return type should be removed for more expressiveness.
Dec 12 2011
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/12/2011 05:46 PM, Steven Schveighoffer wrote:
 On Mon, 12 Dec 2011 10:21:35 -0500, Timon Gehr <timon.gehr gmx.ch> wrote:

 On 12/12/2011 04:08 PM, Timon Gehr wrote:
 On 12/12/2011 03:46 PM, Timon Gehr wrote:
 On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:
 On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:

 Am 10.12.2011 21:25, schrieb Walter Bright:
 On 12/10/2011 11:03 AM, Mehrdad wrote:
 So how are you supposed to implement opApply on a container (or
 e.g.
 here, a
 matrix)? Copy/paste the code for const- and non-const versions?

Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.

But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?

That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -Steve

This currently compiles: inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;} void main(){ immutable int a=2; int *x; immutable(int)* y=&a; very_bad(x,y); *x=1; assert(*y==a); // fail } How does the design catch this error? Will inout** = inout** assignments be disallowed?

Probably it is better to catch it at the call site. But if that is implemented then the requirement to have inout on the return value gets nonsensical.

OK, got it. What you call 'bad' should compile: void bad(ref inout(int)* a, ref inout(int)* b); int* x; immutable(int)* y; const(int)* z; bad(x,x); // fine bad(y,y); // fine bad(z,z); // fine bad(x,y); // inout is deduced to const, ergo neither x nor y convert to the parameter type -> compile error bad(x,z); // now only error for x ... The requirement of having inout on the return type should be removed for more expressiveness.

I've thought about this for a few minutes, and I can't find a flaw in it. But I'm not seeing a huge benefit to it either. Why is it advantageous to use a ref parameter for something that should be a return? Can you show a good use case for this?

For anything that is both input and output, for example: void swap(T)(ref inout(T)[] x, ref inout(T)[] y); void apply(T)(inout(T)[] delegate(inout(T)[]) dg, ref inout(T)[] arg); I think it should be a no-brainer, all that is needed is to remove the check if inout is present on the return type. Every other part of the type checking is identical to when it is there (this is shown by the fact that qualifying void with inout makes the examples compile.)
 I still am uneasy with
 allowing applying a wildcard to a reference underneath to mutable
 references. I know it doesn't work for const.

 -Steve

There is nothing being applied, inout is just matched. After the matching, inout vanishes (it is replaced by nothing, const or immutable) and if that _inout-free_ parameter list still typechecks with the arguments, then it can be invoked. It is simply a case of parametric polymorphism. void good(ref inout(int)* a, ref inout(int)* b); is simultaneously these three functions: (duplicating the code that way is the poor man's inout) void good(ref int* a, ref int* b){ a = b; } void good(ref immutable(int)* a, ref immutable(int)* b){ a = b; } void good(ref const(int)* a, ref const(int)* b){ a = b; } None of these applies any type modifier at any level. It suffices if any of those three typechecks for the call to the polymorphic one to succeed safely. (because the body of 'good' treats its arguments like const).
Dec 12 2011
prev sibling parent reply Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 3:33 AM, Walter Bright wrote:
 On 12/10/2011 3:14 AM, Mehrdad wrote:
 ... and another... (yes, this one _IS_ a const issue)

 struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { 
 return 0; } }
 void main()
 {
 foreach (i; S()) { }
 }

 Error: inout on parameter means inout must be on return type as well 
 (if from D1
 code, replace with 'ref')

Right. inout has no point if it also does not appear on the return type. The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having: { a + b; } Which is an error in D because such is pointless.

Oh yeah, I just realized -- I think you missed the _second_ 'inout'. It's indeed on a parameter! It's just on the implicit 'this' parameter, not on an explicit one. This is a bug (IMO, arising from the fact that inout() is treated like a type constructor, whereas it shouldn't be).
Dec 10 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/10/2011 08:47 PM, Mehrdad wrote:
 On 12/10/2011 3:33 AM, Walter Bright wrote:
 On 12/10/2011 3:14 AM, Mehrdad wrote:
 ... and another... (yes, this one _IS_ a const issue)

 struct S { int opApply(scope int delegate(ref inout(int)) dg) inout {
 return 0; } }
 void main()
 {
 foreach (i; S()) { }
 }

 Error: inout on parameter means inout must be on return type as well
 (if from D1
 code, replace with 'ref')

Right. inout has no point if it also does not appear on the return type. The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having: { a + b; } Which is an error in D because such is pointless.

Oh yeah, I just realized -- I think you missed the _second_ 'inout'. It's indeed on a parameter! It's just on the implicit 'this' parameter, not on an explicit one. This is a bug (IMO, arising from the fact that inout() is treated like a type constructor, whereas it shouldn't be).

Yah, the issue is that *BOTH* your inouts are on a parameter. Just change them to const, without copying any code and call it a day...
Dec 10 2011
parent reply Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 1:00 PM, Timon Gehr wrote:
 Yah, the issue is that *BOTH* your inouts are on a parameter. Just 
 change them to const, without copying any code and call it a day...

I don't see what's wrong with that, but yes, that's what I did. And that caused the next bug that I posted. :P
Dec 10 2011
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/10/2011 10:04 PM, Mehrdad wrote:
 On 12/10/2011 1:00 PM, Timon Gehr wrote:
 Yah, the issue is that *BOTH* your inouts are on a parameter. Just
 change them to const, without copying any code and call it a day...

I don't see what's wrong with that, but yes, that's what I did. And that caused the next bug that I posted. :P

No, that is not what you did. You did copy the code.
Dec 10 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sat, 10 Dec 2011 14:47:15 -0500, Mehrdad <wfunction hotmail.com> wrote:

 On 12/10/2011 3:33 AM, Walter Bright wrote:
 On 12/10/2011 3:14 AM, Mehrdad wrote:
 ... and another... (yes, this one _IS_ a const issue)

 struct S { int opApply(scope int delegate(ref inout(int)) dg) inout {  
 return 0; } }
 void main()
 {
 foreach (i; S()) { }
 }

 Error: inout on parameter means inout must be on return type as well  
 (if from D1
 code, replace with 'ref')

Right. inout has no point if it also does not appear on the return type. The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having: { a + b; } Which is an error in D because such is pointless.

Oh yeah, I just realized -- I think you missed the _second_ 'inout'. It's indeed on a parameter! It's just on the implicit 'this' parameter, not on an explicit one. This is a bug (IMO, arising from the fact that inout() is treated like a type constructor, whereas it shouldn't be).

I'll split it out so it's easier to see: alias int delegate(ref inout(int)) dgtype; int opApply(dgtype dg) inout; Notice that neither dgtype or opapply has inout on the return type. Hence the error. However, I know what you are trying to do. You are trying to say that "while inside the delegate, use the constancy of the actual type". It won't work, because inout applies as const the *entire time* you are inside an inout function. Even when calling an external delegate. opApply is not like a range, it executes the foreach loop entirely inside the context of opApply. That is one advantage of ranges (but it's hard to take advantage, since you would currently have to define a range for each const type). AFAIK, the only way to do this properly is to have two different versions, one for const, one for non-const. And yes, inout should be a type constructor. It has to be, I don't know where you got the idea it shouldn't. -Steve
Dec 11 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:

 Am 10.12.2011 21:25, schrieb Walter Bright:
 On 12/10/2011 11:03 AM, Mehrdad wrote:
 So how are you supposed to implement opApply on a container (or e.g.
 here, a
 matrix)? Copy/paste the code for const- and non-const versions?

Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.

But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?

That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -Steve
Dec 12 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 12 Dec 2011 10:21:35 -0500, Timon Gehr <timon.gehr gmx.ch> wrote:

 On 12/12/2011 04:08 PM, Timon Gehr wrote:
 On 12/12/2011 03:46 PM, Timon Gehr wrote:
 On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:
 On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:

 Am 10.12.2011 21:25, schrieb Walter Bright:
 On 12/10/2011 11:03 AM, Mehrdad wrote:
 So how are you supposed to implement opApply on a container (or  
 e.g.
 here, a
 matrix)? Copy/paste the code for const- and non-const versions?

Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.

But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?

That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -Steve

This currently compiles: inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;} void main(){ immutable int a=2; int *x; immutable(int)* y=&a; very_bad(x,y); *x=1; assert(*y==a); // fail } How does the design catch this error? Will inout** = inout** assignments be disallowed?

Probably it is better to catch it at the call site. But if that is implemented then the requirement to have inout on the return value gets nonsensical.

OK, got it. What you call 'bad' should compile: void bad(ref inout(int)* a, ref inout(int)* b); int* x; immutable(int)* y; const(int)* z; bad(x,x); // fine bad(y,y); // fine bad(z,z); // fine bad(x,y); // inout is deduced to const, ergo neither x nor y convert to the parameter type -> compile error bad(x,z); // now only error for x ... The requirement of having inout on the return type should be removed for more expressiveness.

I've thought about this for a few minutes, and I can't find a flaw in it. But I'm not seeing a huge benefit to it either. Why is it advantageous to use a ref parameter for something that should be a return? Can you show a good use case for this? I still am uneasy with allowing applying a wildcard to a reference underneath to mutable references. I know it doesn't work for const. -Steve
Dec 12 2011
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 12 Dec 2011 12:49:11 -0500, Timon Gehr <timon.gehr gmx.ch> wrote:

 On 12/12/2011 05:46 PM, Steven Schveighoffer wrote:

 I still am uneasy with
 allowing applying a wildcard to a reference underneath to mutable
 references. I know it doesn't work for const.

There is nothing being applied, inout is just matched. After the matching, inout vanishes (it is replaced by nothing, const or immutable) and if that _inout-free_ parameter list still typechecks with the arguments, then it can be invoked. It is simply a case of parametric polymorphism. void good(ref inout(int)* a, ref inout(int)* b); is simultaneously these three functions: (duplicating the code that way is the poor man's inout) void good(ref int* a, ref int* b){ a = b; } void good(ref immutable(int)* a, ref immutable(int)* b){ a = b; } void good(ref const(int)* a, ref const(int)* b){ a = b; } None of these applies any type modifier at any level. It suffices if any of those three typechecks for the call to the polymorphic one to succeed safely. (because the body of 'good' treats its arguments like const).

This is a great way to think about inout in general: 1. all inout parmeters are matched against int, immutable, and inout. If all of them match that qualifier, then that qualifier is substituted for inout. 2. If 1. does not match any of those, const substituted for inout. 3. Normal function call rules apply. That is, just because a match is found doesn't mean the function can be called (as you have demonstrated). 4. Anything that is inout inside a function is treated the same as it is now (similarly to immutable, except for const(inout(...)) ). I always thought of inout as being something performed by the compiler, while calling the function. But it's almost just a way to repaint the parameters/return type of an existing function before applying normal function call rules. I'm cautiously optimistic about this. I want to see what Kenji thinks, at this point he probably understands inout better than me. I think actually his first patch did not require inout on the return type, and I insisted it was required, that might have been a mistake to do that (removing this requirement indirectly fixes 6809 as well). -Steve
Dec 12 2011
prev sibling next sibling parent reply Mehrdad <wfunction hotmail.com> writes:
Oh, nice, here's an ICE for y'all :)


struct Matrix(T)
{
      property T[] data() { return null; }

     int opApply(scope int delegate(ref size_t[], ref T) dg) { return 0; }
     int opApply(scope int delegate(ref const(size_t[]), ref const(T)) 
dg) const { return 0; }

     Matrix!(typeof(mixin("data[0] " ~ op ~ " data[0]"))) 
opBinary(string op)(Matrix other)
     {
         auto result = typeof(return)();
         foreach (i, ref val; this)
         { mixin("result[i] = val " ~ op ~ " other[i];"); }
         return result;
     }
}

void main() { auto m = Matrix!size_t(); m = m * m; }
Dec 10 2011
next sibling parent Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 11:43 AM, Mehrdad wrote:
 Oh, nice, here's an ICE for y'all :)


 struct Matrix(T)
 {
      property T[] data() { return null; }

     int opApply(scope int delegate(ref size_t[], ref T) dg) { return 0; }
     int opApply(scope int delegate(ref const(size_t[]), ref const(T)) 
 dg) const { return 0; }

     Matrix!(typeof(mixin("data[0] " ~ op ~ " data[0]"))) 
 opBinary(string op)(Matrix other)
     {
         auto result = typeof(return)();
         foreach (i, ref val; this)
         { mixin("result[i] = val " ~ op ~ " other[i];"); }
         return result;
     }
 }

 void main() { auto m = Matrix!size_t(); m = m * m; }

BTW this _DOES_ look like another issue related to const. Oh and the error is annoy.d(12): Error: cannot uniquely infer foreach argument types Assertion failure: '0' on line 144 in file 'statement.c'
Dec 10 2011
prev sibling next sibling parent reply Trass3r <un known.com> writes:
http://d.puremagic.com/issues/show_bug.cgi?id=7091
Dec 10 2011
parent reply Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 11:52 AM, Trass3r wrote:
 http://d.puremagic.com/issues/show_bug.cgi?id=7091

for exams and whatnot. (I really shouldn't be even working on this right now. :P) Also, I just wanted to note: I've found a bunch of these bugs as a result of trying to *work around* the previous bugs (!!!). For example, this one was a result of trying to fix the opApply-with-inout issue, by copy/pasting the code for the const and non-const versions. But I guess it turned out mixins didn't play well with that? _This_ fact (where working around one bug causing another) is why D's const looks broken to me. Hope it makes sense why I've been complaining about it so much!
Dec 10 2011
parent Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 12:01 PM, Trass3r wrote:
 http://d.puremagic.com/issues/show_bug.cgi?id=7091

studying for exams and whatnot. (I really shouldn't be even working on this right now. :P)

Then I think posting it directly to bugzilla instead would be better. Things get easily lost in the newsgroups.

See, but, it's a tradeoff: then I wouldn't be able to complain about it :'(
Dec 10 2011
prev sibling next sibling parent Trass3r <un known.com> writes:
 http://d.puremagic.com/issues/show_bug.cgi?id=7091

for exams and whatnot. (I really shouldn't be even working on this right now. :P)

Then I think posting it directly to bugzilla instead would be better. Things get easily lost in the newsgroups.
Dec 10 2011
prev sibling parent "SomeDude" <lovelydear mailmetrash.com> writes:
On Saturday, 10 December 2011 at 19:43:51 UTC, Mehrdad wrote:
 Oh, nice, here's an ICE for y'all :)

No more ICE on 2.059 :)
Apr 28 2012
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/10/2011 1:37 AM, Mehrdad wrote:
 (Sorry for being so critical but at least I tried to make it constructive...)

Actually, I think your post is very constructive and worthwhile. Thanks for taking the time to be specific about what issues you're running in to.
Dec 10 2011
parent Mehrdad <wfunction hotmail.com> writes:
On 12/10/2011 12:27 PM, Walter Bright wrote:
 On 12/10/2011 1:37 AM, Mehrdad wrote:
 (Sorry for being so critical but at least I tried to make it 
 constructive...)

Actually, I think your post is very constructive and worthwhile. Thanks for taking the time to be specific about what issues you're running in to.

Thanks! Glad to know it's helpful. :) Will post more when I get the chance (probably after exams, lol).
Dec 10 2011
prev sibling next sibling parent reply Mehrdad <wfunction hotmail.com> writes:
Moar bugs/ICEs...

     struct S(int N) { this(T!N) { } }
     alias S!1 M;

annoy.d(1): Error: template instance T!(N) template 'T' is not defined, 
did you mean M?
annoy.d(1): Error: T!(N) is used as a type
ty = 35
Assertion failure: '0' on line 145 in file 'mtype.c
Dec 10 2011
parent "SomeDude" <lovelydear mailmetrash.com> writes:
On Sunday, 11 December 2011 at 04:16:04 UTC, Mehrdad wrote:
 Moar bugs/ICEs...

     struct S(int N) { this(T!N) { } }
     alias S!1 M;

 annoy.d(1): Error: template instance T!(N) template 'T' is not 
 defined, did you mean M?
 annoy.d(1): Error: T!(N) is used as a type
 ty = 35
 Assertion failure: '0' on line 145 in file 'mtype.c

No more ICE on 2.059 Win32
Apr 28 2012
prev sibling next sibling parent reply Mehrdad <wfunction hotmail.com> writes:
Another bug, this time regarding invariant()....

(I think this is related to the previous one about int vs. size_t in 
template parameters, but I'm not sure...)

     template NArray(T, size_t N)
     { static if (N > 0) { alias NArray!(T, N - 1)[] NArray; } else { 
alias T NArray; } }
     struct Tensor(T, Length...)
     {
         enum N = Length[0];
         invariant() { }
         this(scope NArray!(T, N) copyOfItems...) { }
          property T[] data() { return null; }
         Tensor!(typeof(mixin("data[0] " ~ op ~ " data[0]")), N) 
opBinary(string op)(Tensor other)
         { return typeof(return).init; }
     }
     void main()
     {
         Tensor!(size_t, 1) a;
         a = a + a;
     }

Error: this for __invariant needs to be type Tensor not type Tensor!(uint,1)
Dec 10 2011
parent "SomeDude" <lovelydear mailmetrash.com> writes:
On Sunday, 11 December 2011 at 04:27:31 UTC, Mehrdad wrote:
 Another bug, this time regarding invariant()....

 (I think this is related to the previous one about int vs. 
 size_t in template parameters, but I'm not sure...)

     template NArray(T, size_t N)
     { static if (N > 0) { alias NArray!(T, N - 1)[] NArray; } 
 else { alias T NArray; } }
     struct Tensor(T, Length...)
     {
         enum N = Length[0];
         invariant() { }
         this(scope NArray!(T, N) copyOfItems...) { }
          property T[] data() { return null; }
         Tensor!(typeof(mixin("data[0] " ~ op ~ " data[0]")), N) 
 opBinary(string op)(Tensor other)
         { return typeof(return).init; }
     }
     void main()
     {
         Tensor!(size_t, 1) a;
         a = a + a;
     }

 Error: this for __invariant needs to be type Tensor not type 
 Tensor!(uint,1)

This one is still valid.
Apr 28 2012
prev sibling next sibling parent Jesse Phillips <jessekphillips+D gmail.com> writes:
On Sun, 11 Dec 2011 19:40:42 +0100
Mafi <mafi example.org> wrote:

 void f(ref const(int)* a, const(int)* b) {a = b; }
 void main() {
    immutable(int)* a;
    auto b = (new int[](5)).ptr;
    f(a, b);
    //if this compiles I have just assigned a mutable pointer
    //to an immutable one
 }
 
 With the above definition using inout, such a call would be
 disallowed.

Just gave this code a try: void f(ref const(int)* a, const(int)* b) {a = b; } void main() { const(int)* a; immutable(int)* b; auto c = (new int[](5)).ptr; f(a, c); f(b, c); //test.d(7): Error: cast(const(int)*)b is not an lvalue }
Dec 12 2011
prev sibling next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
Okay, final exams are coming up again, and so are my bugs (I have no idea 
what the correlation is, don't ask...)
I guess I should post this on bugzilla, but oh well... I continued the 
thread instead.

Try compiling this (I did this on Windows, DMD 2.059):

void main() { Foo!(int[])([[1], [2]]); }
struct Foo(T) { auto foo() { Foo!(T[]) t; return t; } } 
Apr 27 2012
next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
Oh sorry, here's the previous thread... somehow it got detached because of 
the subject line change:
http://lists.puremagic.com/pipermail/digitalmars-d/2011-December/117172.html 
Apr 27 2012
prev sibling next sibling parent reply "James Miller" <james aatch.net> writes:
On Saturday, 28 April 2012 at 04:45:59 UTC, Mehrdad wrote:
 Okay, final exams are coming up again, and so are my bugs (I 
 have no idea what the correlation is, don't ask...)
 I guess I should post this on bugzilla, but oh well... I 
 continued the thread instead.

 Try compiling this (I did this on Windows, DMD 2.059):

 void main() { Foo!(int[])([[1], [2]]); }
 struct Foo(T) { auto foo() { Foo!(T[]) t; return t; } }

You expected that to work? Extra extra, infinite recursion is infinite! You are asking the compiler to instantiate Foo with the type int[], then use that type to instantiate Foo with int[][], which then instantiates Foo with type int[][][]. Try thinking about your code before mouthing off here. Would you fault C for causing a stack overflow in this case: int rec(int a){ return rec(a + 1); } I mean what did you expect, that the compiler could magically create infinite types? I can't even see where you might have gone wrong here, since the code is so simple. What I can see is that the constructor wouldn't work because there are no fields. I can see that you have some very strange ideas about templates, Foo(T) instantiates Foo(T[]), which is a different type, so it goes through and instantiates Foo(T[][]) which is, again, a different type. Think before declaring D to have bugs. -- James Miller
Apr 27 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
 You expected that to work?

Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }
 Try thinking about your code before mouthing off here.

responding?
Apr 27 2012
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 04/28/2012 08:03 AM, Mehrdad wrote:
 You expected that to work?

Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }

D templates are analysed eagerly upon instantiation, whereas C++ templates are analysed lazily. This is not a bug, it is a feature.
Apr 28 2012
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 04/28/2012 09:46 AM, Max Samukha wrote:
 On Saturday, 28 April 2012 at 06:03:54 UTC, Mehrdad wrote:
 You expected that to work?

Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }

dmd is not smart enough

DMD behaves according to the language specification here.
 to avoid recursion by treating f as a
 templated function. I am not sure whether it should,

Maybe, but that would be a strange special case.
 but the following should certainly work:

 struct F(T)
 {
       auto f()() { return F!(F!T)(); }
 }

 void main()
 {
       F!int().f().f().f();
 }

 Error: struct a.F(T) recursive template expansion for template
 argument - why?

The checking for infinite recursion seems to be too conservative here. You could open a bug report.
Apr 28 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 04/28/2012 12:05 PM, Max Samukha wrote:
 On Saturday, 28 April 2012 at 09:40:49 UTC, Timon Gehr wrote:
 On 04/28/2012 09:46 AM, Max Samukha wrote:
 On Saturday, 28 April 2012 at 06:03:54 UTC, Mehrdad wrote:
 You expected that to work?

Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }

dmd is not smart enough

DMD behaves according to the language specification here.


"Semantic analysis is not done until instantiated." On second thought, you are right, this is really badly phrased and does not say what it probably wants to. Anyway, the fact that templates are analysed fully eagerly upon instantiation is by design and the documentation should explicitly state that.
 to avoid recursion by treating f as a
 templated function. I am not sure whether it should,

Maybe, but that would be a strange special case.

That is arguable.

If we are talking about the same thing, then it probably is not. I thought you were proposing to automatically templatise the function iff there is recursion?
 Non-templated functions are a special (degenerate) case of templated functions.

How to 'instantiate' a non-templated function? This would necessarily be a supported operation, if there actually was such a relation between the two concepts.
 The way virtual functions work doesn't allow C++/D to fully implement that
notion.

That implies that the notion does not have merit. In D it was left out by design. And even C++ does not consistently implement it for non-virtuals.
 Mark f() 'virtual' in the C++ example and the code won't compile.

I know. Furthermore, add an invalid member function to the templated struct F and don't refer to it. There won't be any compile time error. (unless it is virtual, of course!)
 Otherwise, C++ tries to be close to the ideal

IMAO, it fails to be close to the ideal.
 and I would expect D be no worse.

Yah, and it really is not. The other issue you brought up in this thread is certainly a real issue with the implementation though.
Apr 28 2012
prev sibling next sibling parent "Max Samukha" <maxsamukha gmail.com> writes:
On Saturday, 28 April 2012 at 06:03:54 UTC, Mehrdad wrote:
 You expected that to work?

Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }

dmd is not smart enough to avoid recursion by treating f as a templated function. I am not sure whether it should, but the following should certainly work: struct F(T) { auto f()() { return F!(F!T)(); } } void main() { F!int().f().f().f(); } Error: struct a.F(T) recursive template expansion for template argument - why?
Apr 28 2012
prev sibling next sibling parent "SomeDude" <lovelydear mailmetrash.com> writes:
On Saturday, 28 April 2012 at 09:36:55 UTC, Timon Gehr wrote:
 On 04/28/2012 08:03 AM, Mehrdad wrote:
 You expected that to work?

Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }

D templates are analysed eagerly upon instantiation, whereas C++ templates are analysed lazily. This is not a bug, it is a feature.

Having browsed several hundreds bug reports lately, I've seen a number of reports complaining that recursive templates instantiation failed. I've resisted the urge to comment that they were invalid. Although I do think they are invalid.
Apr 28 2012
prev sibling next sibling parent "Max Samukha" <maxsamukha gmail.com> writes:
On Saturday, 28 April 2012 at 09:40:49 UTC, Timon Gehr wrote:
 On 04/28/2012 09:46 AM, Max Samukha wrote:
 On Saturday, 28 April 2012 at 06:03:54 UTC, Mehrdad wrote:
 You expected that to work?

Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }

dmd is not smart enough

DMD behaves according to the language specification here.

 to avoid recursion by treating f as a
 templated function. I am not sure whether it should,

Maybe, but that would be a strange special case.

That is arguable. Non-templated functions are a special (degenerate) case of templated functions. The way virtual functions work doesn't allow C++/D to fully implement that notion. Mark f() 'virtual' in the C++ example and the code won't compile. Otherwise, C++ tries to be close to the ideal and I would expect D be no worse.
 but the following should certainly work:

 struct F(T)
 {
      auto f()() { return F!(F!T)(); }
 }

 void main()
 {
      F!int().f().f().f();
 }

 Error: struct a.F(T) recursive template expansion for template
 argument - why?

The checking for infinite recursion seems to be too conservative here. You could open a bug report.

Apr 28 2012
prev sibling next sibling parent "James Miller" <james aatch.net> writes:
On Saturday, 28 April 2012 at 09:36:55 UTC, Timon Gehr wrote:
 On 04/28/2012 08:03 AM, Mehrdad wrote:
 You expected that to work?

Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }

D templates are analysed eagerly upon instantiation, whereas C++ templates are analysed lazily. This is not a bug, it is a feature.

Furthermore, eager analysis is necessary for other D features like CTFE and compile-time reflection. Honestly, in C++ I would stay away from code like the above anyway, irrelevant of whether it compiles, seems too "magic-y" for my tastes. I don't like things not being explicit. -- James Miller
Apr 28 2012
prev sibling next sibling parent "SomeDude" <lovelydear mailmetrash.com> writes:
On Sunday, 29 April 2012 at 06:22:34 UTC, James Miller wrote:
 D templates are analysed eagerly upon instantiation, whereas 
 C++ templates are analysed lazily. This is not a bug, it is a 
 feature.

Furthermore, eager analysis is necessary for other D features like CTFE and compile-time reflection.

Ah, I was wondering about that. Thank you for confirming. I will gladly ditch lazy template eval for something like CTFE.
 Honestly, in C++ I would stay away from code like the above 
 anyway, irrelevant of whether it compiles, seems too "magic-y" 
 for my tastes. I don't like things not being explicit.

+1
Apr 29 2012
prev sibling next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
Some ICE for y'all.

void filter(R)(scope bool delegate(ref BAD!R) func) { }
void main() { filter(r => r); }
Apr 30 2012
next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
Also, what exactly is wrong with this code?

private import std.range;
void filter(R)(R, bool delegate(ElementType!R)) { }
void main() { [1, 2, 3].filter(delegate bool(x) { return x < 3; }); }
Apr 30 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/01/2012 03:09 AM, Mehrdad wrote:
 Also, what exactly is wrong with this code?

 private import std.range;
 void filter(R)(R, bool delegate(ElementType!R)) { }
 void main() { [1, 2, 3].filter(delegate bool(x) { return x < 3; }); }

The current type deduction strategy for IFTI is (imho) too weak. private import std.range; void filter(R)(R, bool delegate(ElementType!R)) { } void main() { [1, 2, 3].filter!(int[])(delegate bool(x) { return x < 3; }); }
Apr 30 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
Is it just "weak", or is it outright wrong?
It's telling me "R" isn't a valid identifier...


"Timon Gehr"  wrote in message news:jnnefl$2j69$1 digitalmars.com... 

On 05/01/2012 03:09 AM, Mehrdad wrote:
 Also, what exactly is wrong with this code?

 private import std.range;
 void filter(R)(R, bool delegate(ElementType!R)) { }
 void main() { [1, 2, 3].filter(delegate bool(x) { return x < 3; }); }

The current type deduction strategy for IFTI is (imho) too weak. private import std.range; void filter(R)(R, bool delegate(ElementType!R)) { } void main() { [1, 2, 3].filter!(int[])(delegate bool(x) { return x < 3; }); }
Apr 30 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/01/2012 03:46 AM, Mehrdad wrote:
 Is it just "weak", or is it outright wrong?

Unfortunately it is by design that it does not work afaik.
 It's telling me "R" isn't a valid identifier...

That one is a diagnostics bug.
Apr 30 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
Wha..?!

I can't believe delegates work so poorly in D... they're practically 
unusable unless you're passing them as template parameters (which brings on 
its own set of bugs)...

Seems like every time I try to escape a bug somehow, another one pops up :(


"Timon Gehr"  wrote in message news:jnnfc6$2jve$2 digitalmars.com...

On 05/01/2012 03:46 AM, Mehrdad wrote:
 Is it just "weak", or is it outright wrong?

Unfortunately it is by design that it does not work afaik.
 It's telling me "R" isn't a valid identifier...

That one is a diagnostics bug.
Apr 30 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/01/2012 04:02 AM, Mehrdad wrote:
 Wha..?!

 I can't believe delegates work so poorly in D...

It is not the delegates, it is the type deduction algorithm for implicit function template instantiation. The issue is that the parameters do not cross-talk, which means R is not known during matching: How to detect the argument type from bool delegate(ElementType!R) alone if R is not known? The obvious solution would be to use an actual inference algorithm that uses information that can be gained from other parameters as well as information already known from the current parameter. You might want to file an enhancement request, because this exact thing seems to trip up many programmers. (i.e. it would be a lot more intuitive and convenient if it worked.)
 they're practically
 unusable unless you're passing them as template parameters (which brings
 on its own set of bugs)...

I haven't encountered those so far.
 Seems like every time I try to escape a bug somehow, another one pops up :(

The situation is improving. Furthermore, there is a very large subset of the language that is already very usable. Another way to make your code compile: private import std.range; void filter(R,S)(R, bool delegate(S)) if(is(S==ElementType!R)){ } void main() { [1, 2, 3].filter((int x) { return x < 3; }); }
May 01 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
I guess the problem is with type deduction, but the trouble is that half the 
reason why type deduction is useful is in the case of lambdas. They become 
effectively useless if you have to type out their entire signature, since 
you could then just make them a separate function (or just use a mixin 
instead, to create a closure... which I've done before).

This isn't really an 'enhancement', since the error message is clearly a 
bug. So I filed them both as bugs.

http://d.puremagic.com/issues/show_bug.cgi?id=8009
http://d.puremagic.com/issues/show_bug.cgi?id=8010

The other bug I was referring to was something along the lines of the 
compiler telling me, "I can't really handle closures as template parameters 
very well (something about 'local variable' or whatever), so I'll just give 
you an obscure error every now and then".
It only happened when it was inside another function, and some conditions 
were satisfied. (I don't remember the exact message, but when I searched it, 
I saw it had been already reported before...)


Oh and thanks for the alternative, it's a good workaround to know. :)

The trouble is that while it solves the bug I posted, it's still not solving 
my (actual) problem.
The actual problem was that I wanted to do something along the lines of:

private import std.range;
auto filter(R, F)(R r, F f) { /*return a filtered range*/ }
void main() { [1, 2, 3].filter(x => x < 3); }

but I resorted to 'delegates' because this didn't work.
However, when I did so, I decided to use a delegate anyway, while using 
'scope' on the delegate and its parameter to avoid a heap allocation, but 
that didn't work either...
so I just filed the bug report with a case that had less type inference. But 
the trouble is that the caller needing to say "delegate" is *already* too 
much typing, so the rest don't really help. :\


On 05/01/2012 04:02 AM, Mehrdad wrote:
 Wha..?!

 I can't believe delegates work so poorly in D...

It is not the delegates, it is the type deduction algorithm for implicit function template instantiation. The issue is that the parameters do not cross-talk, which means R is not known during matching: How to detect the argument type from bool delegate(ElementType!R) alone if R is not known? The obvious solution would be to use an actual inference algorithm that uses information that can be gained from other parameters as well as information already known from the current parameter. You might want to file an enhancement request, because this exact thing seems to trip up many programmers. (i.e. it would be a lot more intuitive and convenient if it worked.)
 they're practically
 unusable unless you're passing them as template parameters (which brings
 on its own set of bugs)...

I haven't encountered those so far.
 Seems like every time I try to escape a bug somehow, another one pops up 
 :(

The situation is improving. Furthermore, there is a very large subset of the language that is already very usable. Another way to make your code compile: private import std.range; void filter(R,S)(R, bool delegate(S)) if(is(S==ElementType!R)){ } void main() { [1, 2, 3].filter((int x) { return x < 3; }); }
May 01 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/01/2012 01:20 PM, Mehrdad wrote:
 I guess the problem is with type deduction, but the trouble is that half
 the reason why type deduction is useful is in the case of lambdas. They
 become effectively useless if you have to type out their entire
 signature, since you could then just make them a separate function (or
 just use a mixin instead, to create a closure... which I've done before).

 This isn't really an 'enhancement', since the error message is clearly a
 bug. So I filed them both as bugs.

 http://d.puremagic.com/issues/show_bug.cgi?id=8009
 http://d.puremagic.com/issues/show_bug.cgi?id=8010

 The other bug I was referring to was something along the lines of the
 compiler telling me, "I can't really handle closures as template
 parameters very well (something about 'local variable' or whatever), so
 I'll just give you an obscure error every now and then".
 It only happened when it was inside another function, and some
 conditions were satisfied. (I don't remember the exact message, but when
 I searched it, I saw it had been already reported before...)

Probably you mean this one: struct S{ int x; auto foo(alias a)(){return a(x);} } void main(){ auto s = S(2); int y = 3; writeln(s.foo!(x=>x+y)()); } Error: template instance foo!(__lambda2) cannot use local '__lambda2(__T1)' as parameter to non-global template foo(alias a) This is an arbitrary restriction and should be fixed. The 'this' pointer for 'foo' could be passed in a reserved slot at the beginning of the context for 'main'.
 Oh and thanks for the alternative, it's a good workaround to know. :)

 The trouble is that while it solves the bug I posted, it's still not
 solving my (actual) problem.
 The actual problem was that I wanted to do something along the lines of:

 private import std.range;
 auto filter(R, F)(R r, F f) { /*return a filtered range*/ }
 void main() { [1, 2, 3].filter(x => x < 3); }

private import std.range; auto filter(R, F)(R r, F f) { /*return a filtered range*/ } void main() { [1, 2, 3].filter((int x) => x < 3); }
May 01 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
Yes, that non-global issue was the exact issue I was referring to. It drives 
me nuts whenever I try to give in and use templates.


Regarding your "fix":
Is it *really* intended that user should say
   arr.filter((typeof(some_random_expression) x) => x < y);
instead of
   arr.filter(x => x < y);
?

I think it's pretty obvious why that doesn't really work in practice... 
May 01 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 05/02/2012 04:14 AM, Mehrdad wrote:
 Yes, that non-global issue was the exact issue I was referring to. It
 drives me nuts whenever I try to give in and use templates.


 Regarding your "fix":
 Is it *really* intended that user should say
 arr.filter((typeof(some_random_expression) x) => x < y);
 instead of
 arr.filter(x => x < y);
 ?

 I think it's pretty obvious why that doesn't really work in practice...

I agree that it should be fixed, but it is technically not a bug in the implementation.
May 02 2012
prev sibling parent reply Robert Clipsham <robert octarineparrot.com> writes:
On 01/05/2012 02:00, Mehrdad wrote:
 Some ICE for y'all.

 void filter(R)(scope bool delegate(ref BAD!R) func) { }
 void main() { filter(r => r); }

Is this in bugzilla? It can't get fixed if no one knows about it! (Make sure to give it the ice keyword once you've submitted so it gets bumped to the top of the queue!) -- Robert http://octarineparrot.com/
May 01 2012
parent "Mehrdad" <wfunction hotmail.com> writes:
Yeah I just posted it yesterday.
http://d.puremagic.com/issues/show_bug.cgi?id=8009

"Robert Clipsham"  wrote in message news:jnoi6g$1f79$1 digitalmars.com... 

On 01/05/2012 02:00, Mehrdad wrote:
 Some ICE for y'all.

 void filter(R)(scope bool delegate(ref BAD!R) func) { }
 void main() { filter(r => r); }

Is this in bugzilla? It can't get fixed if no one knows about it! (Make sure to give it the ice keyword once you've submitted so it gets bumped to the top of the queue!) -- Robert http://octarineparrot.com/
May 01 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
More problems... similar, but this time related to templates.

  struct Filter(R) { this(R) { } }
  template filter(R) { alias Filter!(R).__ctor filter; }
  void main() { filter([1, 2, 3]); }

Error: template linq.filter(R) is not a function template


Why?
May 01 2012
prev sibling next sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 05/02/12 04:11, Mehrdad wrote:
 More problems... similar, but this time related to templates.
 
  struct Filter(R) { this(R) { } }
  template filter(R) { alias Filter!(R).__ctor filter; }
  void main() { filter([1, 2, 3]); }
 
 Error: template linq.filter(R) is not a function template
 
 
 Why?

Because it isn't? Where would 'R' come from in your example?... auto filter(R)(R r) { return Filter!R(r); } artur
May 02 2012
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 01 May 2012 22:11:18 -0400, Mehrdad <wfunction hotmail.com> wrote:

 More problems... similar, but this time related to templates.

   struct Filter(R) { this(R) { } }
   template filter(R) { alias Filter!(R).__ctor filter; }
   void main() { filter([1, 2, 3]); }

 Error: template linq.filter(R) is not a function template

It's an annoying limitation. IFTI specifically *only* works with functions. Not aliases, not types, not anything else. Functions only. -Steve
May 02 2012
prev sibling next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
Let's say you have this hypothetical piece of code:

interface IConnection { string send(string data); }
class Student
{
    private string id;
    private string cachedName;
    private IConnection conn;

    public this(string id) { this.id = id; this.conn = ...; }

    public  property string name() const
    {
        if (!cachedName)
        { cachedName = conn.send("get_name: " ~ id); }
        return cachedName;
    }
}
void main()
{
    auto student = new immutable(Student)("142341234");
    writeln(student.name);
}


Notice that there are two const-related issues in the code that are 
literally *unsolvable* (unless you avoid const/immutable entirely, or unless 
you cast() -- but then your entire code would be filled with dangerous const 
casts...).

How does D2 plan to address these issues in the 'ideal' implementation? 
May 01 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, May 01, 2012 18:55:03 Mehrdad wrote:
 Let's say you have this hypothetical piece of code:
 
 interface IConnection { string send(string data); }
 class Student
 {
     private string id;
     private string cachedName;
     private IConnection conn;
 
     public this(string id) { this.id = id; this.conn = ...; }
 
     public  property string name() const
     {
         if (!cachedName)
         { cachedName = conn.send("get_name: " ~ id); }
         return cachedName;
     }
 }
 void main()
 {
     auto student = new immutable(Student)("142341234");
     writeln(student.name);
 }
 
 
 Notice that there are two const-related issues in the code that are
 literally *unsolvable* (unless you avoid const/immutable entirely, or unless
 you cast() -- but then your entire code would be filled with dangerous
 const casts...).
 
 How does D2 plan to address these issues in the 'ideal' implementation?

Since casting away const and mutating something would break the const system (with it being even worse with immutable), you can never used a cached value in a const function like this. The closest that you would be able to do would be to have a const and non-const version where the non-const versioned cached the value, and the const version didn't (it would likely use the value if already cached, but it would never cache it). And in the case of immutable, you should probably have a separate constructor which actually sets everything. So, something like interface IConnection { string send(string data); } class Student { private string id; private string cachedName; private IConnection conn; public this(string id) { this.id = id; this.conn = ...; } public this(string id) immutable { this.id = id; this.conn = ...; cachedName = conn.send("get_name: " ~ id); } public property string name() { if(!cachedName) cachedName = conn.send("get_name: " ~ id); return cachedName; } public property string name() const { return cachedName is null ? conn.send("get_name: " ~ id) : cachedName; } } void main() { auto student = new immutable(Student)("142341234"); writeln(student.name); } Now, if you don't care about purity at all, you can have an external hash of some kind which holds your cached values. e.g. public property string name() const { if(auto cachedName = id in globalCache) return cachedName; else { auto cachedName = conn.send("get_name: " ~ id); globalCache[id] = cachedName; return cachedName; } } But that's not exactly a pleasant solution, since the state is no longer in the object where it belongs. The first would likely be the better way to do it. However, some folks that like lazy loading such as this seem to have come to the conclusion that they should just pretty much never use const, because it's too restrictive for they want to do. - Jonathan M Davis P.S. You really should create new threads rather than resurrecting threads that are months old. It becomes very easy to ignore them when I have to scroll way up to find a new post in the threaded view. The only reason that I don't miss them entirely is the fact that my e-mail client tells me how many e-mails I have which are unread.
May 01 2012
next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
1. Wouldn't your solution
   (a) Be telling the programmer, "you *can't* lazy-load const objects"?
   (b) Require duplicating the code 3 times? (Yes, you can factor it out as 
a mixin, but who wants to do that??)

2. "Some folks that like lazy loading such as this seem to have come to the 
conclusion that they should just pretty much never use const, because it's 
too restrictive for they want to do."

Yes, that's **exactly** why I've said 'const is broken' for at least a year 
now, but it seemed like people objected to my conclusion...

The trouble, as I just showed, is that it isn't.
"Lazy-loading" and caching aren't exactly obscure or rarely-used concepts --  
which seems to be the way they are portrayed here. They are *bound* to be 
used in any nontrivial program.
So, if D doesn't let you use those techniques without 'hacking around' the 
language, then it doesn't matter how awesome D is otherwise -- C++ 
programmers _simply won't_ switch to D, and other programmers will 
definitely have trouble using const, immutable, shared, etc...

In other words, my point with this entire thread was:
These issues need to get *fixed* at some point on the _theory_ side. Who 
cares if the implementation takes an extra year or two to be finished? If 
it's wrong in theory, it's never going to be right in practice.
And it looks to me like there is no real clean 'fix' for this issue in D... 
the only solutions are along the lines of using C-style casts in C++ to 
throw-away const-ness, and we all know that isn't an actual solution...

3. Okay I'll try to avoid resurrecting threads in the future, thanks for 
letting me know. :) 
May 01 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
Okay, so let's say you're right

    void process1(Student s) { .. }
    void process2(const(Student) s) { ... }

and that what I  guess what I REALLY want is to say, "process1() won't 
change the student's name, birthday, or any other attribute, but process2() 
will".

How do you propose I make that guarantee known to the compiler/caller?
Is that not the entire point of saying "const(Student)" in the first place?




"Chris Cain"  wrote in message news:mivrzrerrqlvskxcndva forum.dlang.org...

On Wednesday, 2 May 2012 at 03:22:40 UTC, Mehrdad wrote:
 Yes, that's **exactly** why I've said 'const is broken' for at least a 
 year now, but it seemed like people objected to my conclusion...

 The trouble, as I just showed, is that it isn't.
 "Lazy-loading" and caching aren't exactly obscure or rarely-used 
 concepts --  which seems to be the way they are portrayed here. They are 
 *bound* to be used in any nontrivial program.
 So, if D doesn't let you use those techniques without 'hacking around' the 
 language, then it doesn't matter how awesome D is otherwise -- C++ 
 programmers _simply won't_ switch to D, and other programmers will 
 definitely have trouble using const, immutable, shared, etc...

I've never commented on this issue, but here's my viewpoint: If you intend to change something, it's NOT const. Don't make an object and say you won't change it then change it. It's nonsensical. 9 times out of 10, what you really mean is "I have an object that has some immutable/const data in it". A clear way to understand what I mean is a Matrix. Some people want "immutable matrices" which cache their determinant when it's time to calculate it. What they REALLY want is a matrix of immutable data. So, instead of a "immutable Matrix!int", they really need to be "Matrix!immutable(int)". Effectively it's the same thing, only it's more precisely defined and it's actually possible to do caching with it. The way C++ works is simply incorrect, and yes it might take some time to get used to the fact that const means const and immutable means immutable, but if you don't want to respect those types in the first place, there's no sense in using them.
May 01 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
"Chris Cain"  wrote in message news:nqgwunggifrgmwwhkcql forum.dlang.org...
 What about the object do you want const? How should this object normally 
 change things? It looks like the way you've coded it, your connection will 
 get information off of a server.

I don't think you answered my question. What I said (or meant to ask) was this: - OK, FINE, let's say I don't know what D's const() means, then. Maybe it isn't suitable for what I need. I just want to know: _How do you specify that the function process1() won't modify its 'student' parameter_? Your response was this: - "Does const(Student) mean that it shouldn't write to the server?" To which my answer is: - I don't know, you tell me. (?) _Should_ I use 'const' to specify this? Or should I use something else? In other words:
 Is that not the entire point of saying "const(Student)" in the first 
 place?


You answered that by saying
 "const(Student)" means I won't change _anything_ about the object. It's 
 const. So in a sense, you can say that you won't change the fields name, 
 birthday, etc. That means you can't change the fields.

The problem with your answer is that it implies D DOESN'T SUPPORT O.O.P. with const!!! If D supported OOP, then why the heck would process1() know (or _care_) how Student works internally? Why (or how) should it know (or care) that Student uses a database connection? Wasn't that information an implementation detail? All process1() needs to know is the *PUBLIC INTERFACE* of Student. And all it cares to say is: "According to the public interface of Student, none of the methods I will use will modify the object." process1() has NO IDEA what goes on internally! It has NO IDEA that Student uses a database connection. Maybe it's a network connnection instead. Or maybe it's being unit-tested, and it's actually a dummy object, with no relation to the outside world. Or maybe it's a wrapper/proxy for another object. The point is: _why should the code for process1() depend on this_?! So, I'll ask my question again: How should process1() tell the outside world that it will not be asking its parameter to manipulate itself, WITHOUT breaking the abstraction barrier and 'peeking' into the private world of Student?
May 01 2012
next sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-05-02 13:41, Chris Cain wrote:

 If that's what you want, then that's precisely what D does. "According
 to the public interface of Student, none of the methods I will use will
 modify the object." As opposed to C++'s method of "According to the
 public interface of student, these methods have comments that say that
 they say they won't change the object, but I can't tell if it does or not."

For D, it's not limited to the public interface. The rules apply to ALL methods and fields. -- /Jacob Carlborg
May 02 2012
prev sibling next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
"Chris Cain"  wrote in message news:wyqyigxytaqwwmhfhlej forum.dlang.org...
 If you want just to specify that (and not ask the compiler to check for 
 you), documentation will do.

Heh, if only the world was so ideal...
 That's _any_ kind of modification of state. If you change state, you can't 
 use immutable/const (nor would you really want to).

Okay...
 D's const/immutable require a different way of thinking of it. What are 
 they useful for? Consider a multithreaded program where some threads 
 depend on information that others have.

I guess this answers my question then: const/immutable are useful for multithreading. So it means, essentially, they're pretty useless for Objects, but only useful for structs. Why? Objects have **behavior** (not just data), and depending on the _internal details_ of that behavior would be depending on an implementation detail. Sure, the method writer can call the method 'const', but he really has no idea whether someone will override it down the road and get struck by lightning because of that. What D's const seems to be is for *data*, pure and simple (no pun intended). Using it for behavior would be trying to predict the future implementations, and that is pretty damn hard. Because of that, I think we disallow direct instantiation of const() or immutable() Objects altogether, because they're pretty useless.
 In C++, you'd be foolish to not have locks set up even for const variables 
 because they can change at any time for any reason.

Huh? Not if they're instance variables. It's kind of silly to lock against something so you can modify instance variables.
 It's hardly a guarantee and it's so common to violate (look at yourself, 
 for instance) that it means _nothing_.]

I don't think I ever violated C++'s 'const'... (?)
 I liked how it was described as a glorified comment, because that's 
 precisely how I think of it.

More like, a **CHECKED** comment. Big difference.
May 02 2012
parent "Mehrdad" <wfunction hotmail.com> writes:
Okay thanks for the replies, that clears up a bunch of things for me.

Regarding the last part:

Yes, it 'works'. The only trouble is that you can never set the interface 
methods to be 'const', because any sort of indirection can screw you over 
later. This is why const is less useful than it could be. 
May 02 2012
prev sibling next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
Could someone mention a case where it is __necessary__ to cast away const()?
How about immutable()?
How about shared()?
May 02 2012
parent reply =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <xtzgzorex gmail.com> writes:
On 02-05-2012 17:58, Mehrdad wrote:
 Could someone mention a case where it is __necessary__ to cast away
 const()?
 How about immutable()?
 How about shared()?

shared? Almost always in any non-trivial application. shared is only useful if you're dealing with templatized functions that can actually handle it, which is not the case as often as one would like.

-- - Alex
May 02 2012
parent "Mehrdad" <wfunction hotmail.com> writes:
Okay, that's for shared.

What about const though?
May 02 2012
prev sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2012-05-02 15:13:43 +0000, "Mehrdad" <wfunction hotmail.com> said:

 Yes, 'const' is part of the interface.
 
 The trouble is that when you make it part of the interface, you're 
 making the assumption that **no one** who derives from your class will 
 need mutable state.
 
 How can you ever guarantee that?

When you're making the object 'const', you're not making the assumption that no one who derives from this class need mutable state. What you're doing is asserting that bits belonging to this object needs to be 'const' to preserve sequential consistency across threads or for other reasons. If the derived class casts away const, it breaks that contract. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
May 02 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
 When you're making the object 'const', you're not making the assumption 
 that no one who derives from this class need mutable state.
 What you're doing is asserting that bits belonging to this object needs to 
 be 'const' to preserve sequential consistency across threads or for other 
 reasons.

Bits belonging to *this* object? I thought const was transitive...
 If the derived class casts away const, it breaks that contract.

So you're saying the same thing: Derived classes CANNOT have mutable state...
May 02 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-05-02 18:01, Mehrdad wrote:
 When you're making the object 'const', you're not making the
 assumption that no one who derives from this class need mutable state.
 What you're doing is asserting that bits belonging to this object
 needs to be 'const' to preserve sequential consistency across threads
 or for other reasons.

Bits belonging to *this* object? I thought const was transitive...
 If the derived class casts away const, it breaks that contract.

So you're saying the same thing: Derived classes CANNOT have mutable state...

No: interface Foo { void foo () const; } class Bar : Foo { int x; void foo () const {} void bar () { x++; } } void main() { auto bar = new Bar; bar.bar(); writeln(bar.x); } -- /Jacob Carlborg
May 02 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
Er, I guess I didn't say what I actually meant to say, my bad. x_x

What I meant that you're assuming that derived classes won't need mutable 
state in an const method that they overrode. 
May 02 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/02/2012 08:58 PM, Mehrdad wrote:
 mutable state in an const method

=O
May 02 2012
parent "Mehrdad" <wfunction hotmail.com> writes:
"Timon Gehr"  wrote in message news:jns5du$64q$1 digitalmars.com... 
On 05/02/2012 08:58 PM, Mehrdad wrote:
 mutable state in an const method


a*
May 02 2012
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-05-02 20:58, Mehrdad wrote:
 Er, I guess I didn't say what I actually meant to say, my bad. x_x

 What I meant that you're assuming that derived classes won't need
 mutable state in an const method that they overrode.

Yes, that would be the assumption. It's not possible without subverting the type system. It's like saying "I'm overriding this method but I want it to return an int instead of a string". It's part of the interface. -- /Jacob Carlborg
May 02 2012
prev sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
Also, I think you didn't notice the other problem.

The other problem was with IConnection, whose send() wasn't 'const', which 
also gives you an error due to the transitivity of const (which I've also 
claimed is broken).
And how could it? It's just a connection, not a server, so it doesn't parse 
the input... and it probably changes the state of the connection, since 
connections often have internal buffers they need to modify (if not 
prioritization, etc.).

So, what is D's solution for _that_ problem? 
May 01 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
"Steven Schveighoffer"  wrote in message 
news:op.wdokh6vteav7ka localhost.localdomain...
 There are two solutions, both are horrible.

That's what scares me lol
 There is a possible 3rd solution.  Don't use immutable/const, instead use 
 information hiding.
 In other words, Your class is already technically "immutable", since name 
 will never mutate beyond the first setting.  This requires coder 
 discipline, and has no help from the compiler.

Yup.
 But many languages that *don't* have const/immutable do well with this 
 pattern (think Java strings).

Java strings are pretty poor for performance though. :\ You shouldn't be forced to choose between O(1) performance (versus O(n)) and the correctness of your program.
 I have periodically mulled the idea of making a library-based solution for 
 logical const.  I think it would work, but you would have to be extremely 
 cautious, and you lose some compiler guarantees for it.
 The most difficult part to deal with is how to prevent concurrent access, 
 when an immutable object is always implicitly sharable.

Yeah, 'shared' also suffers from similar limitations...
 I have another idea which gets around the problem in a different way.  I 
 think it's essential to bringing arbitrary ranges in line with current 
 array features.  When I have some time to flesh it out, I'll propose it. I 
 think we absolutely can't be finished with ranges until this is 
 implemented.

Okay nice. I also think this is 100% relevant to the range issue, so I don't think we will see a true fix until this problem is solved.
May 02 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
 This is a false choice.  The performance of Java strings (BTW, which I 
 don't think is that bad) is not tied to whether they are immutable or not.

Oh yes, I think you're right. If I remember, someone told me Java strings don't re-copy data when you substring; I forgot about that. But that's definitely not the case in C#.
May 02 2012
parent "Mehrdad" <wfunction hotmail.com> writes:
 But that's definitely not the case in C#.

(which, I should clarify, was what implicitly came to my mind as an example of immutable strings)
May 02 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Mehrdad:

 Notice that there are two const-related issues in the code that 
 are literally *unsolvable* (unless you avoid const/immutable 
 entirely, or unless you cast() -- but then your entire code 
 would be filled with dangerous const casts...).

Casting away casts is rather dangerous in D (more than in C++), I kind of never do it. So just don't use const/immutable in such cases. Bye, bearophile
May 01 2012
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Wednesday, 2 May 2012 at 03:22:40 UTC, Mehrdad wrote:
 Yes, that's **exactly** why I've said 'const is broken' for at 
 least a year now, but it seemed like people objected to my 
 conclusion...

 The trouble, as I just showed, is that it isn't.
 "Lazy-loading" and caching aren't exactly obscure or 
 rarely-used concepts --  which seems to be the way they are 
 portrayed here. They are *bound* to be used in any nontrivial 
 program.
 So, if D doesn't let you use those techniques without 'hacking 
 around' the language, then it doesn't matter how awesome D is 
 otherwise -- C++ programmers _simply won't_ switch to D, and 
 other programmers will definitely have trouble using const, 
 immutable, shared, etc...

I've never commented on this issue, but here's my viewpoint: If you intend to change something, it's NOT const. Don't make an object and say you won't change it then change it. It's nonsensical. 9 times out of 10, what you really mean is "I have an object that has some immutable/const data in it". A clear way to understand what I mean is a Matrix. Some people want "immutable matrices" which cache their determinant when it's time to calculate it. What they REALLY want is a matrix of immutable data. So, instead of a "immutable Matrix!int", they really need to be "Matrix!immutable(int)". Effectively it's the same thing, only it's more precisely defined and it's actually possible to do caching with it. The way C++ works is simply incorrect, and yes it might take some time to get used to the fact that const means const and immutable means immutable, but if you don't want to respect those types in the first place, there's no sense in using them.
May 01 2012
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Wednesday, 2 May 2012 at 04:05:11 UTC, Mehrdad wrote:
 Okay, so let's say you're right

    void process1(Student s) { .. }
    void process2(const(Student) s) { ... }

 and that what I  guess what I REALLY want is to say, 
 "process1() won't change the student's name, birthday, or any 
 other attribute, but process2() will".

 How do you propose I make that guarantee known to the 
 compiler/caller?

What about the object do you want const? How should this object normally change things? It looks like the way you've coded it, your connection will get information off of a server. Does const(Student) mean that it shouldn't write to the server? If so, use something akin to a compile-time dependency injection ... specify that one of those students has a read-only connection. Something like Student!StudentDBConnection might work, and then maybe Student!const(StudentDBConnection).
 Is that not the entire point of saying "const(Student)" in the 
 first place?

object. It's const. So in a sense, you can say that you won't change the fields name, birthday, etc. That means you can't change the fields.
May 01 2012
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Wednesday, 2 May 2012 at 04:18:18 UTC, Chris Cain wrote:
 ... specify that one of those students has a read-only 
 connection. Something like Student!StudentDBConnection might 
 work, and then maybe Student!const(StudentDBConnection).

I take the second one back, I've not written any kind of connection code before, but it occurs to me that StudentDBConnection must have some kind of mutable state in order to connect to something. Maybe a ROConnection object (or a struct and use the awesomeness of D's compile-time duck typing) or something along those lines?
May 01 2012
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Wednesday, 2 May 2012 at 06:10:04 UTC, Mehrdad wrote:
 I don't think you answered my question.

 What I said (or meant to ask) was this:
 - OK, FINE, let's say I don't know what D's const() means, 
 then. Maybe it isn't suitable for what I need. I just want to 
 know:
  _How do you specify that the function process1() won't modify 
 its 'student' parameter_?

If you want just to specify that (and not ask the compiler to check for you), documentation will do.
 Your response was this:
 - "Does const(Student) mean that it shouldn't write to the 
 server?"

 To which my answer is:
 - I don't know, you tell me. (?) _Should_ I use 'const' to 
 specify this? Or should I use something else?

I can't tell you what you want. You have to tell me. But no, you shouldn't use const to specify this. const means you're object won't be changed by you. Here's a decision tree for if you can use const and immutable Will the object (including all of its fields) ever change? no -> immutable (END) yes -> Will the object (including all of its fields) be changed by you? no -> const (END) yes -> No qualifier That's _any_ kind of modification of state. If you change state, you can't use immutable/const (nor would you really want to).
 The problem with your answer is that it implies D DOESN'T 
 SUPPORT O.O.P. with const!!!

 If D supported OOP, then why the heck would process1() know (or 
 _care_) how Student works internally?
 Why (or how) should it know (or care) that Student uses a 
 database connection?

When you say "const(Student)" you're saying that whatever Student does, you want to know whatever you do won't change its internal state. I'm not sure you're using OOP properly. If you want to use OOP to solve the problem, my suggestion is closer to OOP than using const. If you don't want to talk about database connections and everything (i.e., you don't want to use D's outrageously nice templates), you can just have it be an interface instead. In fact an interface would probably be more appropriate. interface ROStudent { getName(); getAddress(); ... } process1(ROStudent student); Effectively, though, it's the same sort of thing. D's const/immutable require a different way of thinking of it. What are they useful for? Consider a multithreaded program where some threads depend on information that others have. They need the information but they won't change state (which makes it much safer/faster to work with because locking isn't as necessary ... with immutable locking is actually silly). That's what const/immutable is for. In C++, you'd be foolish to not have locks set up even for const variables because they can change at any time for any reason. It's hardly a guarantee and it's so common to violate (look at yourself, for instance) that it means _nothing_. I liked how it was described as a glorified comment, because that's precisely how I think of it.
 Wasn't that information an implementation detail?


 All process1() needs to know is the *PUBLIC INTERFACE* of 
 Student.
 And all it cares to say is: "According to the public interface 
 of Student, none of the methods I will use will modify the 
 object."
 process1() has NO IDEA what goes on internally! It has NO IDEA 
 that Student uses a database connection. Maybe it's a network 
 connnection instead. Or maybe it's being unit-tested, and it's 
 actually a dummy object, with no relation to the outside world. 
 Or maybe it's a wrapper/proxy for another object.

 The point is: _why should the code for process1() depend on 
 this_?!

If that's what you want, then that's precisely what D does. "According to the public interface of Student, none of the methods I will use will modify the object." As opposed to C++'s method of "According to the public interface of student, these methods have comments that say that they say they won't change the object, but I can't tell if it does or not." Again, using an interface ROStudent/StudentReader/etc is really your "OOP solution".
 So, I'll ask my question again:

 How should process1() tell the outside world that it will not 
 be asking its parameter to manipulate itself, WITHOUT breaking 
 the abstraction barrier and 'peeking' into the private world of 
 Student?

I'll respond again: It depends on what you want. If you won't be changing the object, then const/immutable does that. If you will be changing the object, but you want guarantees that it won't write to some external source/data structure, you'll have to come up with the more precise way you want to define that. I gave you two potential approaches. ---- Really though, the C++ way is a complete and total minefield in this regard anyway. Consider Student defined like so: Student { name, id, address, phone_number; connection; getName() const { if(!name) { // cast away const thisnonconst.name = connection.get("name"); } return name; } writeName() { connection.set("name", name); } } Now this follows one particular understanding of const (the one I think you're getting at). However, imagine another person comes along and sees that "writeName" doesn't write to Student's state. Helpfully, he adds const to the end of writeName and breaks your type system. In fact, it's actually _easier_ to violate "that kind of const" than it is to use it, because your "getName" method has to do fancy casting away of const. In fact, D's way, if you specify that Student can only use a ReadOnlyConnection (or if you use ROStudent/StudentReader interface), then the compiler will guarantee that it has a ReadOnlyConnection. If ReadOnlyConnection doesn't write to the database, then that Student is _guaranteed_ to not write to the database. If you want the compiler to check that, the D offers the ability to. C++, on the other hand (using const), can barely suggest that possibility, since it's so easy to violate.
May 02 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 01 May 2012 23:30:27 -0400, Mehrdad <wfunction hotmail.com> wrote:

 Also, I think you didn't notice the other problem.

 The other problem was with IConnection, whose send() wasn't 'const',  
 which also gives you an error due to the transitivity of const (which  
 I've also claimed is broken).
 And how could it? It's just a connection, not a server, so it doesn't  
 parse the input... and it probably changes the state of the connection,  
 since connections often have internal buffers they need to modify (if  
 not prioritization, etc.).

 So, what is D's solution for _that_ problem?

There are two solutions, both are horrible. 1. Cast. Coder beware. 2. Store conn outside the instance (i.e. in a hash lookup). Horrible. There is a possible 3rd solution. Don't use immutable/const, instead use information hiding. In other words, Your class is already technically "immutable", since name will never mutate beyond the first setting. This requires coder discipline, and has no help from the compiler. But many languages that *don't* have const/immutable do well with this pattern (think Java strings). I have periodically mulled the idea of making a library-based solution for logical const. I think it would work, but you would have to be extremely cautious, and you lose some compiler guarantees for it. The most difficult part to deal with is how to prevent concurrent access, when an immutable object is always implicitly sharable. I have another idea which gets around the problem in a different way. I think it's essential to bringing arbitrary ranges in line with current array features. When I have some time to flesh it out, I'll propose it. I think we absolutely can't be finished with ranges until this is implemented. -Steve
May 02 2012
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Wednesday, 2 May 2012 at 12:19:35 UTC, Jacob Carlborg wrote:
 For D, it's not limited to the public interface. The rules 
 apply to ALL methods and fields.

Indeed. I may have forgot to add "in any way shape form or fashion" to that particular line, but it's important to node that const means it won't change anything about the object (which includes fields). Which makes much more sense than "logical const" which has completely arbitrary meaning in almost every case I've seen.
May 02 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Mehrdad:

 "Lazy-loading" and caching aren't exactly obscure or 
 rarely-used concepts --  which seems to be the way they are 
 portrayed here. They are *bound* to be used in any nontrivial 
 program.

Right. - - - - - - - - - - Andrei Alexandrescu:
 Casting away const should be statically disallowed in  safe 
 code.

I agree. Bye, bearophile
May 02 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wednesday, 2 May 2012 at 15:21:31 UTC, Mehrdad wrote:
 "Steven Schveighoffer"  wrote in message 
 news:op.wdokh6vteav7ka localhost.localdomain...

 But many languages that *don't* have const/immutable do well 
 with this pattern (think Java strings).

Java strings are pretty poor for performance though. :\ You shouldn't be forced to choose between O(1) performance (versus O(n)) and the correctness of your program.

This is a false choice. The performance of Java strings (BTW, which I don't think is that bad) is not tied to whether they are immutable or not. -Steve
May 02 2012
prev sibling next sibling parent "David Nadlinger" <see klickverbot.at> writes:
On Wednesday, 2 May 2012 at 16:52:33 UTC, Alex Rønne Petersen 
wrote:
 shared? Almost always in any non-trivial application. shared is 
 only useful if you're dealing with templatized functions that 
 can actually handle it, which is not the case as often as one 
 would like.

Additionally, shared is currently little more than a marker for non-TLS data. David
May 02 2012
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Wednesday, 2 May 2012 at 15:35:15 UTC, Mehrdad wrote:
 I guess this answers my question then: const/immutable are 
 useful for multithreading.

Indeed, const/immutable makes multithreading much better. It provides restrictions which make it much easier to think about problems. If I pass a const reference into a function, I can be guaranteed it won't be changed by that function.
 So it means, essentially, they're pretty useless for Objects, 
 but only useful for structs.
 Why? Objects have **behavior** (not just data) ...

Structs in D have behavior as well. In fact, I've found most things I might be forced to use objects/classes in most other languages can be easily done with D's structs and all of the behavior and state is encapsulated nicely. Combine that with templates and you've got some ridiculous power and efficiency.
 Sure, the method writer can call the method 'const', but he 
 really has no idea whether someone will override it down the 
 road and get struck by lightning because of that.

If the method writer has the method be const, then he's said that the contract of that method is that it doesn't change the object's state. This is no different than having a method that takes a string and complaining that the method writer has no idea whether someone will want to override it later and want the method to take an int. You can overload the method to take an int. And you can overload the method later down the line so that you have an implementation which does mutate state and isn't const. Although, you certainly need to have a version which doesn't mutate state available still. But that's just part of the contract. If you're writing a method and you've decided "I want whoever uses this method to not cause any mutation of state when they call this method" (maybe so there's a guarantee of consistency in execution speed between calls or something), then you mark it const. Otherwise, it's not really a const method.
 Because of that, I think we disallow direct instantiation of 
 const() or immutable() Objects altogether, because they're 
 pretty useless.

Why? There's no reason to throw the baby out with the bath water. There's plenty of need for inheritance for immutable/const representation of data. Just because we can't use it for objects whose whole life is caching (which is a clear subset of OOP usage) doesn't mean it's useless.
 Huh? Not if they're instance variables.
 It's kind of silly to lock against something so you can modify 
 instance variables.

Depends. If you're just accessing the instance method directly (and it's an atomic operation), you can get away with not locking. However, If you have a C++ const object and you call a getter method on it in a context where someone else may also be calling a getter method on it, you must lock it. By your own admission, you can't depend on implementation of the object. Ergo, the getter methods may be mutating state (you can't assume it's not), and you can't tell whether or not they are because const in C++ doesn't say whether it will mutate state. Of course, if you don't depend on the correctness of the method you're free to not lock... In D, if I'm sure these const objects aren't being modified elsewhere, I can absolutely get away with not locking when I call getter methods. If it's an immutable object, it doesn't even cross my mind whether I need to lock it or not. You don't have that kind of peace of mind in C++.
 I don't think I ever violated C++'s 'const'... (?)

Well, seeing as C++ const apparently has no meaning ... it's rather hard to "violate" it, per say. Honestly, the time my professors taught me about C++'s const, they said it was for methods that don't modify state. Evidently that wasn't the case, but I never got into the mindset that const didn't mean something that doesn't modify state... so it's rather hard for me to think of a reason to use const for this. Const to me has always meant "don't mutate state." If you just want a view of the object where you only use getters, that's really what D's interfaces are for.
 More like, a **CHECKED** comment.
 Big difference.

Not exactly. It's not "checked" because const objects can be changed by anyone at anytime easily and it's common and the compiler doesn't care. It doesn't mean anything. Again, what I think you actually want is an interface of the getters, not const. There's no sense in having both do the same thing when we can actually use const to mean an object that you won't mutate. =-=-=-= In any case, would the Student-getter style interface method work for this use case or not? interface IStudentRO { string getName(); string getAddress(); real getGPA(); ... } class Student : IStudentRO { IConnection conn; string name; ... string getName() { if(!name) name = conn.get("name"); return name; } ... void setName(string newName) { name = newName; conn.set("name", newName); } ... } void process1(IStudentRO student); // Compile-time checking ... can only use getters.
May 02 2012
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Wednesday, 2 May 2012 at 20:30:10 UTC, Mehrdad wrote:
 Okay thanks for the replies, that clears up a bunch of things 
 for me.

No problem, I'm glad it's helping.
 Regarding the last part:

 Yes, it 'works'. The only trouble is that you can never set the 
 interface methods to be 'const', because any sort of 
 indirection can screw you over later. This is why const is less 
 useful than it could be.

You can set interface methods to be const, but you have to understand that you're saying that method can't mutate state. There's also nothing stopping you from specifying that both const and non-const methods are available, but it would increase the workload of people down the line especially if the const and non-const version differ significantly. I think D's const is as useful as it needs/can be. That said, I can understand why you might feel that way until you start using const/immutable correctly. Once you see how it improves your conceptual model of approaching algorithms, I won't be surprised if you end up as annoyed with C++'s const as I am. It's kind of why I pointed out the whole thing where you should lock your object even when you're just calling getters on a const object. It's actually deceptively incorrect to not do that in C++.
May 02 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 02 May 2012 12:59:34 -0400, David Nadlinger <see klickverbot.at>=
  =

wrote:

 On Wednesday, 2 May 2012 at 16:52:33 UTC, Alex R=C3=B8nne Petersen wro=

 shared? Almost always in any non-trivial application. shared is only =


 useful if you're dealing with templatized functions that can actually=


 handle it, which is not the case as often as one would like.

Additionally, shared is currently little more than a marker for non-TL=

 data.

No, it's very important that it is a type constructor. For example, it = = makes weak-pure functions possible. I think there is a large piece of shared missing/undefined, and that is,= = how do I mark something shared as "temporarily local". I think Bartosz = = proposed something like "lent". We essentially need the equivalent of const for shared. Const unifies = immutable and mutable, we need something to unify shared and thread-loca= l. The problem is, something like this needs to be combined with = thread-locks. I wonder if some kind of ARC would be useful for = automatically unlocking the data. -Steve
May 03 2012
prev sibling next sibling parent "David Nadlinger" <see klickverbot.at> writes:
On Thursday, 3 May 2012 at 13:40:41 UTC, Steven Schveighoffer 
wrote:
 On Wed, 02 May 2012 12:59:34 -0400, David Nadlinger 
 <see klickverbot.at> wrote:
 Additionally, shared is currently little more than a marker 
 for non-TLS data.

No, it's very important that it is a type constructor. For example, it makes weak-pure functions possible. I think there is a large piece of shared missing/undefined, and that is, how do I mark something shared as "temporarily local". I think Bartosz proposed something like "lent". We essentially need the equivalent of const for shared. Const unifies immutable and mutable, we need something to unify shared and thread-local. The problem is, something like this needs to be combined with thread-locks. I wonder if some kind of ARC would be useful for automatically unlocking the data.

Yes, it is important indeed, at least if we want the type system to give guarantees about multi-threaded code. I just meant that in the current state, it provides little more than a way to make sure that shared data isn't accidentally passed around/used in places where synchronization isn't properly handled, because none of the fancier related ideas have actually been implemented so far. David
May 03 2012
prev sibling parent "Oleg Kuporosov" <Oleg.Kuporosov gmail.com> writes:
Hi folks,

it was really good and productive discussion on const and 
immutability,
special thanks for those (Jonatan,Steven,Cris,etc) who answered 
hard Mehrad's questions and clarified so important topics.

Can I kindly ask you folks to update FAQ on the site to have 
ability for newcomers to see answers on

   Why const and immutable?
   Why the names const and immutable?
   How exactly is immutable related to multicores?
   Ok, I'm fine with immutable for safe data sharing among 
threads. But why do we need the uninformative const?
   Why are immutable strings favored in D 2.0?

it are still open but I feel majority of it were answered here.

"Attributes" page has no answered also "shared", "immutable", 
"inout" and "const"'s description is so uninformative taking into 
account how much details were discussed here.

It will definitelly help and reduce amount of repeated noisy 
topics in major and learn forums.

Great thanks,
Oleg.
May 04 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, May 01, 2012 20:22:40 Mehrdad wrote:
 1. Wouldn't your solution
    (a) Be telling the programmer, "you *can't* lazy-load const objects"?
    (b) Require duplicating the code 3 times? (Yes, you can factor it out as
 a mixin, but who wants to do that??)
 
 2. "Some folks that like lazy loading such as this seem to have come to the
 conclusion that they should just pretty much never use const, because it's
 too restrictive for they want to do."
 
 Yes, that's **exactly** why I've said 'const is broken' for at least a year
 now, but it seemed like people objected to my conclusion...
 
 The trouble, as I just showed, is that it isn't.
 "Lazy-loading" and caching aren't exactly obscure or rarely-used concepts --
 which seems to be the way they are portrayed here. They are *bound* to be
 used in any nontrivial program.
 So, if D doesn't let you use those techniques without 'hacking around' the
 language, then it doesn't matter how awesome D is otherwise -- C++
 programmers _simply won't_ switch to D, and other programmers will
 definitely have trouble using const, immutable, shared, etc...
 
 In other words, my point with this entire thread was:
 These issues need to get *fixed* at some point on the _theory_ side. Who
 cares if the implementation takes an extra year or two to be finished? If
 it's wrong in theory, it's never going to be right in practice.

D's const does exactly what it intends to do. Yes, that means that lazy loading and const are mutually exclusive. And I don't expect that that will ever change. To change it would be to make it so that const isn't guaranteed to be const. There is _zero_ chance of convincing Walter of anything of the sort, and many in the community will back him in that. In order for the compiler to give solid guarantees, it can't be any other way, and more importantly, the fact that we have immutable means that it can't be any other way. If that means that you can't use const in many situations (even if you might use it in those same situations in C++), then that means that you can't use const. D's const is not C++'s const and was never intended to be.
 And it looks to me like there is no real clean 'fix' for this issue in D...
 the only solutions are along the lines of using C-style casts in C++ to
 throw-away const-ness, and we all know that isn't an actual solution...

It's worse than not an actual solution. It breaks the type system, throwing away the gurantees that the compiler gives you (easily resulting in bugs if you actually do it) and risks segfaults and the like if the underlying object is actually immutable. - Jonathan M Davis
May 01 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, May 01, 2012 21:05:11 Mehrdad wrote:
 Okay, so let's say you're right
 
     void process1(Student s) { .. }
     void process2(const(Student) s) { ... }
 
 and that what I  guess what I REALLY want is to say, "process1() won't
 change the student's name, birthday, or any other attribute, but process2()
 will".
 
 How do you propose I make that guarantee known to the compiler/caller?
 Is that not the entire point of saying "const(Student)" in the first place?

Yes. And if you mark it as const, then the compiler will guarantee it. But with something like lazy loading, the object _does_ change. In D, const actually guarantees that a const object will not be altered, whereas C++'s doesn't. If you have an object which you don't intend to _logically_ alter but which whose state may change due to lazy loading, then the compiler can't give you any real guarantees anyway without having a lazy-loading mechanism of some sort built into the language. In C++, the compiler can't use it to provide any real guarantees, because the programmer is free to violate const at any time via mutable or casting away const. Walter thinks that this makes C++'s const utterly useless (his focus is very much on what the compiler can and can't guarantee). Most of the rest of us disagree (it _does_ at least stop the programmer from inadvertently mutating a const variable directly), but with Walter's take on it, there's no way that he would have made D's const the same. And C++'s const just doesn't provide the kind of guarantees that D requires anyway, since the fact that a const object could actually be immutable makes it so that you can't safely cast away const to mutate anything, regardless of whether the compiler assumes that const objects never get altered. The lack of logical const in D can be very annoying for some use cases, but the fact that D's const is guaranteed then makes it much better for other use cases. It's a tradeoff. If all you want is something which indicates that you don't intend to alter the logical state of a variable (but you may alter its state in other ways - e.g. its cache), then D's compiler can't help you with that. Not even C++'s compiler really does much for that, since its const can be circumvented so easily. It just helps prevent you from doing it accidentally. - Jonathan M Davis
May 01 2012
parent "Mehrdad" <wfunction hotmail.com> writes:
 In C++, the compiler can't use it to provide any real guarantees, because 
 the programmer is free to violate const at any time via mutable or casting 
 away const.

Maybe I'm being stupid, but how is the case any different in D?
May 01 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, May 01, 2012 23:14:06 Mehrdad wrote:
 In C++, the compiler can't use it to provide any real guarantees, because
 the programmer is free to violate const at any time via mutable or casting
 away const.

Maybe I'm being stupid, but how is the case any different in D?

D's type system assumes that const cannot be altered. As such, the compiler is free to optimize or alter its code generation based on that fact and any code which _does_ alter a variable after casting away const is breaking the type system and risking bugs. As far as the compiler is concerned, you _cannot_ alter a const variable through that variable (though another one might - unlike immutable - meaning that there are times when the compiler can't know that a const variable hasn't been mutated; it often can, however, particularly within a single function). C++ specifically allows for const variables to be altered - either via mutable or casting away const. It _guarantees_ that it work to do so. And since it considers it completely legal to alter a const variable by either of those means, it _cannot_ assume that a const variable hasn't been altered except in very restricted circumstances. So, in general, the _only_ guarantee that C++'s const gives you is that you a const variable will not be altered save by casting away const or by one of its member variables being mutable. So, it all comes down to what the compiler is free to assume and therefore guarantee. With D's type system, it's free to assume that you can never alter a const variable, so it guarantees that and any attempt to subvert that breaks the type system and whatever guarantees it gives you, risking bugs. With C++'s type system, it assumes that const _can_ be mutated via casting or mutable and guarantees that that will work, whereas it then _cannot_ assume that a const variable doesn't get mutated. - Jonathan M Davis
May 01 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
You said:
"In C++, the compiler can't use it to provide any real **guarantees**..."

Now you're telling me D doesn't guarantee it either.
And then go on telling me about how D uses const-ness to make assumptions 
and improve performance.

Isn't your answer orthogonal to your claim? o.O

Whether the compiler makes **GUARANTEES** about code is _irrelevant_ to 
whether or not the language uses 'const' to improve performance.

You could very well have a language which **enforces** const-ness (i.e. 
doesn't prevent casting it away), but which _doesn't_ use it to improve 
performance.
Or which does.


The dot product of your answer with my question was zero. :( 
May 01 2012
parent "Mehrdad" <wfunction hotmail.com> writes:
 ... i.e. doesn't prevent casting it away)

Typo, I meant "DOES prevent"
May 01 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, May 01, 2012 23:10:04 Mehrdad wrote:
 "Chris Cain"  wrote in message news:nqgwunggifrgmwwhkcql forum.dlang.org...
 
 What about the object do you want const? How should this object normally
 change things? It looks like the way you've coded it, your connection will
 get information off of a server.

I don't think you answered my question. What I said (or meant to ask) was this: - OK, FINE, let's say I don't know what D's const() means, then. Maybe it isn't suitable for what I need. I just want to know: _How do you specify that the function process1() won't modify its 'student' parameter_?

What you're thinking about here is logical const. You want a way to indicate that an object is not logically altered by the function that you're passing it to. With logical const, the inner state of the object _can_ change so long as the state of the object as seen from the outside is constante. However, D's const is _physical_ const. It guarantees that the object isn't altered _at all_. And that _is_ part of the interface, because no non-const function can be called on a const object, and every const function statically guarantees that it doesn't mutate the object that it's on. There is _no_ way in D to indicate that an object won't be altered by a function save for guaranteeing that it won't be altered _at all_ by using const. There is no way to indicate that it won't be altered logically save for comments. And in reality, having the compiler verify that an object's logical state doesn't change even when some of its internal state does is _very_ difficult (if not impossible) to statically verify. As such, the compiler can make no such guarantees. Not even C++ does that. Arguably, C++'s const is a glorified comment. Walter certainly sees it that way and doesn't think that there is _any_ value to C++'s const as a result. What it _does_ guarantee is that you won't accidentally alter the object (since you have to cast away const to alter it, or some of its member variables are going to have to be mutable), so I'd argue that it's still very useful. But it does _not_ actually guarantee that the object won't get mutated by that function. It's effectively just a comment which the compiler partially verifies. It would be _very_ cool to be able to have a compiler-checked, logical const, but it probably isn't realistically possible, except maybe in very restrictive circumstances. - Jonathan M Davis
May 01 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
In the world of OOP, when would "guarantee"ing (so to speak) 'physical' 
const-ness ever be handy?

Wouldn't "physical" const-ness be an implementation detail of the object, 
and therefore, impossible to determine by the user of the object? 
May 01 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 05/02/2012 08:48 AM, Mehrdad wrote:
 In the world of OOP,

interface Readonly{ auto read(){ ... } } class Mutable{ auto read(){ ... } void write(int x){ ... } Readonly getReadonly(){ ... } } private class Adapter: Readonly{ Mutable field; auto read(){ return field.read(); } }
 when would "guarantee"ing (so to speak) 'physical'
 const-ness ever be handy?

Concurrency?
 Wouldn't "physical" const-ness be an implementation detail of the
 object, and therefore,

If it is, don't use the const qualifier.
 impossible to determine by the user of the object?

It is possible because 'const' is part of the method interface.
May 02 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, May 01, 2012 23:44:30 Mehrdad wrote:
 You said:
 "In C++, the compiler can't use it to provide any real **guarantees**..."
 
 Now you're telling me D doesn't guarantee it either.
 And then go on telling me about how D uses const-ness to make assumptions
 and improve performance.
 
 Isn't your answer orthogonal to your claim? o.O
 
 Whether the compiler makes **GUARANTEES** about code is _irrelevant_ to
 whether or not the language uses 'const' to improve performance.

No, what the compiler guarantees is _very_ relevant to using const for improving performance. The _only_ reason that the compiler can use const for any kind of optimizations is because it can guarantee that the variable won't change. If it can't guarantee that, then it _cannot_ use const for optimizations. How could it? What would there be to optimize? Optimizations with const are based on the fact that the variable didn't change, so if the compiler can't guarantee that, how could it make any optimizations based on that? And yes, D's type system _does_ guarantee that a const object doesn't change specifically because casting away const and mutating an object is _undefined_. It goes outside of the type system. You can do it, because D is a systems language, but the compiler is free to make optimizations based on the assumption that you will never mutate a const variable. So, the compiler _can_ make the guarantee that const isn't mutated, and if you do, you _will_ have bugs - just like if you did something screwy like casting an object no void* nad then casting it to a completely different type afterwards could cause bugs. You've thrown away the type system at that point, and it's up to the programmer to maintain the compiler's guarantee in such situations, or you'll get bugs, because the compiler relies on that guarantee. C++, on the other hand, _does_ define what happens when you cast away const and mutate a variable, so its type system has to assume that a const variable _can_ change and therefore can rarely use it for optimizations (i.e. only in cases where it can statically verify that you _didn't_ cast away const and that not mutable variables are involved - which is likely to be rather rather, since even just making a function call would defeat it, since C++ doesn't do cross-function optimizations like that). The difference here is in what the compiler considers to be defined or not and therefore what assumptions it's permitted to make. - Jonathan M Davis
May 02 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
Whoa, what?

So you're saying
     "X results in UB"
means
     "Compiler guarantees X"
?


By that philosophy, C and C++ are orders of magnitude better than D, given 
how many so-called "guarantees" they make about your code... 
May 02 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/2/12 3:10 AM, Mehrdad wrote:
 Whoa, what?

 So you're saying
 "X results in UB"
 means
 "Compiler guarantees X"
 ?


 By that philosophy, C and C++ are orders of magnitude better than D,
 given how many so-called "guarantees" they make about your code...

Casting away const should be statically disallowed in safe code. Andrei
May 02 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, May 01, 2012 23:48:29 Mehrdad wrote:
 In the world of OOP, when would "guarantee"ing (so to speak) 'physical'
 const-ness ever be handy?
 
 Wouldn't "physical" const-ness be an implementation detail of the object,
 and therefore, impossible to determine by the user of the object?

No, it's not an implementation detail. When you mark an object as being physically const, then you're guaranteeing that you will not alter it through that reference or pointer (and not at all, if it's a value type, because then there can't be another reference or pointer which mutates it). The type system guarantees this, because it disallows any const variable from being mutated. When dealing with a const object, you can only call const functions on its interface. Whatever implementation there is for those interface methods would also have to be const to implement that interface. And the type system would then disallow any non-const functions being called within the implementations of those functions as well as disallowing the mutation of member variables. So, the type system guarantees that the object will not be mutated at all by any const function. And since const is part of the interface of the object, it is very much _not_ an implementation detail. Physical constness is _required_ in order to have immutable ever be converted to const, because immutable objects can _never_ be mutated through _any_ reference or pointer. If D's const were not transitive as well as guarantee that any const reference cannot alter the object it points to, then you couldn't have immutable be convertible to const, because const would not protect it against mutation. So, C++'s const is impossible in D if immutable is going to be convertible to const. It's _logical_ constness that is near-impossible to have the compiler verify, because it has no way of determining that the variables that you mark as mutable or from which you cast away const and mutate don't affect the logical state of the object. _You_ may know, but the compiler can't determine that. - Jonathan M Davis
May 02 2012
parent "Mehrdad" <wfunction hotmail.com> writes:
 Wouldn't "physical" const-ness be an implementation detail of the 
 object, and therefore, impossible to determine by the user of the 
 object?

physically const, then you're guaranteeing that you will not alter it through that reference or pointer.

I think you misunderstood my question. Yes, __IF__ you mark an object as physically const, then the world is beautiful... My question is, __WHEN__ can you ever do that, except in the most trivial situations? As soon as you try to add an extra layer of indirection (be it a proxy, implementing a method in an interface, overriding a base class method, etc.), there is NO WAY for the caller to know what obj.foo() does on obj. How can it possibly know whether obj will stay physically const?
May 02 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, May 02, 2012 00:10:33 Mehrdad wrote:
 Whoa, what?
 
 So you're saying
      "X results in UB"
 means
      "Compiler guarantees X"
 ?
 
 
 By that philosophy, C and C++ are orders of magnitude better than D, given
 how many so-called "guarantees" they make about your code...

I don't follow. The D compiler guarantees that as long as you don't cast away const, const will never be mutated. If you _do_ cast away const and then mutate the variable, you are doing something which is undefined. As it is undefined behavior, _anything_ could happen if you do it, and the compiler is free to assume that it will never happen. So, it effectively has a guarantee that a const variable will never be mutated (save by another, mutable reference to the same data). It then uses that guarantee for optimizations. To make it 100% iron-clan, casting away const would have to be illegal, but that would be unacceptable in a systems language - particularly when you want to be able to call C functions which may not be properly annotated. So instead, the compiler assumes that its guarantee holds in the case where you cast away const, and is still able to use it for optimizations. C++ _does_ define what happens when you cast away const and mutate a variable, so it guarantees that that behavior will be safe. In so doing however, it is then unable to assume that casting away const will not result in the variable being mutated and is therefore unable to use const for much in the way of optimizations. - Jonathan M Davis P.S. You can check out this stackoverflow question on the subject as well: http://stackoverflow.com/questions/4219600/logical-const-in-d
May 02 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
You can replace "signed integer overflow" with pretty much whatever 
'guarantee' in C that suits your fancy.
See below.


"Jonathan M Davis"  wrote in message 
news:mailman.207.1335944070.24740.digitalmars-d puremagic.com...
 I don't follow.

 The D compiler guarantees that as long as you don't cast away const, const 
 will never be mutated.

their results will be correct.
 If you _do_ cast away const and then mutate the variable, you are doing 
 something which is undefined.

 As it is undefined behavior, _anything_ could happen if you do it, and the 
 compiler is free to assume that it will never happen.

and the compiler is free to assume that it will never happen.
 So, it effectively has a guarantee that a const variable will never be 
 mutated (save by another, mutable reference to the same data). It then 
 uses that guarantee for optimizations.

overflown. It then uses that guarantee for optimizations.
 To make it 100% iron-clan, casting away const would have to be illegal, 
 but that would be unacceptable in a systems language - particularly when 
 you want to be able to call C functions which may not be properly 
 annotated.

that would be unacceptable in a systems language - particularly when you need to depend on system-dependent behavior.
 So instead, the compiler assumes that its guarantee holds in the case 
 where you cast away const, and is still able to use it for optimizations.

overflow a signed integer, and is still able to use it for optimizations.
 C++ _does_ define what happens when you cast away const and mutate a 
 variable, so it guarantees that that behavior will be safe.

guarantees that that behavior will be safe.
 In so doing however, it is then unable to assume that casting away const 
 will not result in the variable being mutated and is therefore unable to 
 use const for much in the way of optimizations.

never be overflown, and is therefore unable to use signed integer overflow for much in the way of optimizations. - Jonathan M Davis Mehrdad
May 02 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 05/02/2012 10:26 AM, Jonathan M Davis wrote:
 All of that might hold if the compiler could actually make optimizations based
 on the knowledge that an integer wouldn't overflow. But what optimizations
 could it make?

x+1 > x ==> true
May 02 2012
prev sibling next sibling parent "SomeDude" <lovelydear mailmetrash.com> writes:
On Wednesday, 2 May 2012 at 06:44:30 UTC, Mehrdad wrote:
 Whether the compiler makes **GUARANTEES** about code is 
 _irrelevant_ to whether or not the language uses 'const' to 
 improve performance.

 You could very well have a language which **enforces** 
 const-ness (i.e. doesn't prevent casting it away), but which 
 _doesn't_ use it to improve performance.
 Or which does.

Yes, pure functional languages do provide this kind of guarantee, by simply forbidding immutability at the cost of copying objects. I agree D's solution isn't "pure" as it must allow mutability, and I suspect it can't be, unless it starts copying objects all over the place. That means that when you use the immutable keyword, you *really* mean it and you *must* think that way. If you want to mutate your object, you have to copy it to another object. Thats how strings are designed. For me, your example doesn't prove that the tool is broken, it proves that you don't know how to use immutability. It's really another paradigm, which has its uses, in particular in concurrent programming.
May 02 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, May 02, 2012 01:12:15 Mehrdad wrote:
 Wouldn't "physical" const-ness be an implementation detail of the
 object, and therefore, impossible to determine by the user of the
 object?

No, it's not an implementation detail. When you mark an object as being physically const, then you're guaranteeing that you will not alter it through that reference or pointer.

I think you misunderstood my question. Yes, __IF__ you mark an object as physically const, then the world is beautiful... My question is, __WHEN__ can you ever do that, except in the most trivial situations? As soon as you try to add an extra layer of indirection (be it a proxy, implementing a method in an interface, overriding a base class method, etc.), there is NO WAY for the caller to know what obj.foo() does on obj. How can it possibly know whether obj will stay physically const?h

Because foo must be const, or if can't be called on a const object. And if it's const, then it can't call any non-const functions or mutate any of its member variables. If you have interface I { int foo() const; } class C : I { int foo() const {...} } C's foo _must_ be const, or it's not implementing I's foo, and it won't compile. And if C's foo is const, then it can't call a non-const function or mutate any of its member variables. If it were pure on top of that, then it couldn't mutate any global or class variables either. The same goes for any derived class which overrides foo. const is part of foo's signature, and no derived class can escape that. So, _every_ class which implements I, and _every_ class derived from C will have a const foo which will be unable to mutate the state of that object. If instead you did class D { int bar() const { return e.bar(); } E e; } then E's bar would have to be const, or it wouldn't be callable from D's bar, since the e member variable is const inside of D's bar, since D's bar is const, and you can't call a non-const function on a const variable. An extra layer of indirection doesn't escape const at all, because that layer of indirection must use const or it won't be usable by the outer layer. So, the type system is able to guarantee that when you call a const function, the object it's being called on - as well as any object that it contains directly or indirectly - will not be mutated. - Jonathan M Davis
May 02 2012
parent "Mehrdad" <wfunction hotmail.com> writes:
Yes, 'const' is part of the interface.

The trouble is that when you make it part of the interface, you're making 
the assumption that **no one** who derives from your class will need mutable 
state.

How can you ever guarantee that? 
May 02 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, May 02, 2012 01:06:47 Mehrdad wrote:
 You can replace "signed integer overflow" with pretty much whatever
 'guarantee' in C that suits your fancy.
 See below.
 
 
 "Jonathan M Davis"  wrote in message
 news:mailman.207.1335944070.24740.digitalmars-d puremagic.com...
 
 I don't follow.

Let's see if this is better.
 The D compiler guarantees that as long as you don't cast away const, const
 will never be mutated.

The C compiler guarantees that as long as signed integers don't overflow, their results will be correct.
 If you _do_ cast away const and then mutate the variable, you are doing
 something which is undefined.

If you _do_ overflow signed integers, their results will be undefined.
 As it is undefined behavior, _anything_ could happen if you do it, and the
 compiler is free to assume that it will never happen.

As it is undefined behavior, the result could be _anything_ if you do it, and the compiler is free to assume that it will never happen.
 So, it effectively has a guarantee that a const variable will never be
 mutated (save by another, mutable reference to the same data). It then
 uses that guarantee for optimizations.

So, it effectively has a guarantee that a signed integer will never be overflown. It then uses that guarantee for optimizations.
 To make it 100% iron-clan, casting away const would have to be illegal,
 but that would be unacceptable in a systems language - particularly when
 you want to be able to call C functions which may not be properly
 annotated.

To make it 100% iron-clan, signed overflow would have to be illegal, but that would be unacceptable in a systems language - particularly when you need to depend on system-dependent behavior.
 So instead, the compiler assumes that its guarantee holds in the case
 where you cast away const, and is still able to use it for optimizations.

So instead, the compiler assumes that its guarantee holds in the case where overflow a signed integer, and is still able to use it for optimizations.
 C++ _does_ define what happens when you cast away const and mutate a
 variable, so it guarantees that that behavior will be safe.

D _does_ define what happens when you overflow a signed integer, so it guarantees that that behavior will be safe.
 In so doing however, it is then unable to assume that casting away const
 will not result in the variable being mutated and is therefore unable to
 use const for much in the way of optimizations.

In so doing however, it is then unable to assume that a signed integer will never be overflown, and is therefore unable to use signed integer overflow for much in the way of optimizations.

All of that might hold if the compiler could actually make optimizations based on the knowledge that an integer wouldn't overflow. But what optimizations could it make? Regardless, I don't see how it's particularly relevant. Even if there were optimizations which C/C++ could make which D can't (which may or may not be the case), that wouldn't have any bearing on how D's const works. D's const and C/C++'s const have different goals and different pros and cons. D's const is physical const, whereas C/C++'s const is an attempt at logical const (though it isn't really logical const either, because it makes no guarantees that the logical state of the object remains the same - only that you don't directly mutate any const variables). - Jonathan M Davis
May 02 2012
parent "Mehrdad" <wfunction hotmail.com> writes:
 All of that might hold if the compiler could actually make optimizations 
 based on the knowledge that an integer wouldn't overflow. But what 
 optimizations could it make?

Look for "signed integer overflow" here. http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
May 02 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, May 02, 2012 at 09:57:58AM -0400, Andrei Alexandrescu wrote:
[...]
 Casting away const should be statically disallowed in  safe code.

Doesn't it already? T -- Everybody talks about it, but nobody does anything about it! -- Mark Twain
May 02 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
H. S. Teoh:

 Doesn't it already?

Right, this: class Foo {} void main() safe { const f1 = new Foo; auto f2 = cast(Foo)f1; } Gives: test.d(5): Error: cast from const(Foo) to test.Foo not allowed in safe code Bye, bearophile
May 02 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, May 02, 2012 at 05:16:26PM +0200, bearophile wrote:
 H. S. Teoh:
 
Doesn't it already?

Right, this: class Foo {} void main() safe { const f1 = new Foo; auto f2 = cast(Foo)f1; } Gives: test.d(5): Error: cast from const(Foo) to test.Foo not allowed in safe code

Thought so. One area where I'd like const to be improved, though, is a way to indicate that const-ness depends on the arguments, specifically, on the behaviour of a delegate argument. For non-delegate arguments we have inout (which is also its own minefield, but anyway), but it sucks that opApply can never be marked safe, const, or pure because there's no guarantee at all what the delegate will do. (Forcing the delegate to be any of the above is not really an option, because what if you *needed* to call opApply with an impure delegate?) This on its own is no big deal, but the thing is, const, pure, and safe are all "viral". All it takes is for a single nested function to call opApply somewhere, and suddenly the whole function call chain can no longer be marked const/pure/ safe. T -- Let X be the set not defined by this sentence...
May 02 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
H. S. Teoh:

 One area where I'd like const to be improved, though, is a way 
 to indicate that const-ness depends on the arguments, 
 specifically, on the behaviour of a delegate argument.

There was a long discussion about this, with proposals like pure(compile_time_predicate) to define a conditional purity. But Walter decided for a simpler solutions, where the purity and "non-throwness" of templates is inferred. Bye, bearophile
May 02 2012
prev sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, May 02, 2012 13:30:11 Mehrdad wrote:
 Okay thanks for the replies, that clears up a bunch of things for me.
 
 Regarding the last part:
 
 Yes, it 'works'. The only trouble is that you can never set the interface
 methods to be 'const', because any sort of indirection can screw you over
 later. This is why const is less useful than it could be.

You _can_ make the interface methods const if you're willing to deal with that restriction in everything that implements it. Sometimes that's reasonable. Sometimes it's not. The simple truth of the matter is that you can't just const in D as much as you would in C++, simply because it's more restrictive. It's more powerful thanks to its greater guarantees, but that comes at a cost. So, programmers are going to have to learn when using const in D makes sense and when it doesn't, even if they're experts at in C++. And a lot of it depends on what you're doing (e.g. I pretty much never lazy load anything, so const would never cause me problems due to disallowing lazy loading, but for someone who needs lazy loading for performance, const make be unacceptable in many cases). - Jonathan M Davis
May 02 2012