www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - const challenge

reply Jason House <jason.james.house gmail.com> writes:
It really seems like the whole const debate has died down and Walter is moving
on to bigger and better things.  This is great to see, but now I want to stress
the design!

I have two challenges, and they're at direct opposites of each other.

1. Create a usage example that can only be solved by stripping const out, or
adopting bad coding practices (global variables?).  I suspect this may bring
back old examples about why transitive const is bad.  Please avoid flames such
as "const is useless" or "my favorite language does not have const and it's
great"



I really hope this does not boil down to useless arguing.  I'm doing this in
the hopes that we can finally set all this const stuff to rest and find great
things to put into some kind of const FAQ.  If that's not possible, I hope this
will help highlight what remaining issues exist and how acceptable they are.
Jan 30 2008
next sibling parent reply Jason House <jason.james.house gmail.com> writes:
Jason House Wrote:

 1. Create a usage example that can only be solved by stripping const out, or
adopting bad coding practices (global variables?).  I suspect this may bring
back old examples about why transitive const is bad.  Please avoid flames such
as "const is useless" or "my favorite language does not have const and it's
great"
Here's an example I remember seeing posted. IMHO, it sounds like a rather annoying thing to handle. It can be solved by stripping out the const references to class A or possibly replacing "executionLog x" with some global variable reference. I could imagine this being problematic with multi-threaded or other large applications that have distinct logs for different components/threads. class A{ version(debug){ executionLog x; } ... // includes some writing to log for debugging } class B{ const class A; // can't work while debugging ... }
Jan 30 2008
next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On Jan 30, 2008 10:09 PM, Jason House <jason.james.house gmail.com> wrote:
   const class A; // can't work while debugging
Actually, I think it can. You just have to cheat a bit: debug struct executionLog { const void function1(/*...*/) { /* ... */ } const void function2(/*...*/) { /* ... */ } const void function3(/*...*/) { /* ... */ } ... private: /* all variables */ } If the member functions need to modify the member variables, they will need to cast away const. Yes, that is cheating, and undefined, but (a) at least all the cheating is localised to one place, so the rest of your code maintains its const correctness, and (b) the undefined behavior will only occur in your debug build, so if works on your platform, it really doesn't matter what it will do on anyone else's, since they will only see the release version. (Also, (c) - it's a struct, not a class, so all the member variables /will/ be on the stack, so the possibility of undefined behavior is minimised anyway).
Jan 30 2008
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Jason House" wrote
 Jason House Wrote:

 1. Create a usage example that can only be solved by stripping const out, 
 or adopting bad coding practices (global variables?).  I suspect this may 
 bring back old examples about why transitive const is bad.  Please avoid 
 flames such as "const is useless" or "my favorite language does not have 
 const and it's great"
Here's an example I remember seeing posted. IMHO, it sounds like a rather annoying thing to handle. It can be solved by stripping out the const references to class A or possibly replacing "executionLog x" with some global variable reference. I could imagine this being problematic with multi-threaded or other large applications that have distinct logs for different components/threads. class A{ version(debug){ executionLog x; } ... // includes some writing to log for debugging } class B{ const class A; // can't work while debugging ... }
The obvious solution is to store the execution log outside the A instance. However, I'm not sure how you use it, or if you need to have multiple instances of x. Normally logging is done through a static/global variable, i.e.: class A { version(debug){ static executionLog x; static this() { x = getExecutionLogForThisClass(); } } } -Steve
Jan 31 2008
parent reply Jason House <jason.james.house gmail.com> writes:
Steven Schveighoffer Wrote:

 However, I'm not sure how you use it, or if you need to have multiple 
 instances of x.
I made up the scenario based on what I remember from the const debates. I don't have the original post or remember who posted it. I was really hoping for those who regularly use const-based design patterns in other languages to jump in with examples that exist in their code. I hoped that me providing an example might inspire others to add more examples. I unfortunately don't have sufficient appreciation for the original poster's feeling to do it justice. I also recall some argument about handling widget draw routines. If they're a constant object but need to draw themselves on something. I haven't done any detailed GUI programming outside of java beans or GTK, so I tried to stay away from it and hope someone else would post it. I've at least done some logging and can appreciate problems with multi-threading. Mostly, I feel like the const debate has really died down (probably a good thing), but now we need to open the door once more to ensure potential users are happy with the const design. I have to imagine there's still people out there that think transitive const is a bad thing and still others that think transitive const is the best thing since sliced bread. I'd really love to see them jump into this thread and provide some real examples.
Jan 31 2008
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Jason House" wrote
 Steven Schveighoffer Wrote:

 However, I'm not sure how you use it, or if you need to have multiple
 instances of x.
I made up the scenario based on what I remember from the const debates. I don't have the original post or remember who posted it. I was really hoping for those who regularly use const-based design patterns in other languages to jump in with examples that exist in their code. I hoped that me providing an example might inspire others to add more examples. I unfortunately don't have sufficient appreciation for the original poster's feeling to do it justice. I also recall some argument about handling widget draw routines. If they're a constant object but need to draw themselves on something. I haven't done any detailed GUI programming outside of java beans or GTK, so I tried to stay away from it and hope someone else would post it. I've at least done some logging and can appreciate problems with multi-threading.
I remember this example, and when I saw it, I agreed with the poster, that transitive const makes this type of thing more convenient. However, I've proven that transitive const is a subet of const, and therefore, not necessary. If something is transitive const, it means that it is partially const, meaning it's not const. I believe that there is always a way to redesign your thinking to have const work. If you have a const widget and need to have it draw itself, pass in the non-const values needed to do the drawing. Or if you absolutely have to have a structure with a draw routine that doesn't take the argument of the thing to draw it on, then create a wrapper structure that isn't const but contains the const widget. Or change your widget so the data you want to remain const is declared const. It's hard to speculate without seeing the actual code, but I believe it's possible to solve.
 Mostly, I feel like the const debate has really died down (probably a good 
 thing), but now we need to open the door once more to ensure potential 
 users are happy with the const design.  I have to imagine there's still 
 people out there that think transitive const is a bad thing and still 
 others that think transitive const is the best thing since sliced bread. 
 I'd really love to see them jump into this thread and provide some real 
 examples.
I think the transitive const debate has died down. I still believe the head-const debate is very much alive, and I'm prepared to argue my side if anyone should challenge that validity. Last I checked, nobody had refuted my points that head-const for a class was possible and useful :) -Steve
Jan 31 2008
parent reply Jason House <jason.james.house gmail.com> writes:
Steven Schveighoffer Wrote:
 I believe that there is always a way to redesign your thinking to have const 
 work.  If you have a const widget and need to have it draw itself, pass in 
 the non-const values needed to do the drawing.  Or if you absolutely have to 
 have a structure with a draw routine that doesn't take the argument of the 
 thing to draw it on, then create a wrapper structure that isn't const but 
 contains the const widget.  Or change your widget so the data you want to 
 remain const is declared const.  It's hard to speculate without seeing the 
 actual code, but I believe it's possible to solve.
That's the whole point of my challenge. I'd like to see people post their real code and have others tack a whack at redesigning them. Maybe we'll see it's easy and there's a general method that should go into an FAQ. Or maybe there's a few corner cases that are tough to solve. Either way, we're smarter about the pros and cons of our (now stable?) const design.
 I think the transitive const debate has died down.  I still believe the 
 head-const debate is very much alive, and I'm prepared to argue my side if 
 anyone should challenge that validity.  Last I checked, nobody had refuted 
 my points that head-const for a class was possible and useful :)
What I have nagging me in the back of my mind is if there should be ways to break transitive const with an instance of head const... such as access to a logging utility, a raster for drawing images, etc... I would like to put that to rest. All my current coding is in D 1.x. I'd love to see active (C++?) const coders to post problematic cases. In the end, I'd like to see a final blessing of D's const and have stuff like Tango finally start using it (so I can then port my code that depends on Tango to D 2.x)
Jan 31 2008
parent reply "Janice Caron" <caron800 googlemail.com> writes:
OK, here's an example, borrowed a bit from Tango.

    char[] niftyStringFunction(char[] outBuffer, char[] input)
    {
        /*...*/
    }

The idea is, if (outBuffer == null) then the function allocates memory
and returns it; whereas, if (outBuffer != null) then the result is
writen into outBuffer, and the return value is a slice of outBuffer.
(The idea is to minimize allocations).

Converting this to D2 is problematic, because you want to return a
string, but string is invariant, so if you do that, you won't be able
to pass the same buffer back into other, similarly nifty string
functions.

The interesting thing about this sort of function is that there is an
implicit in-contract that outBuffer, if supplied, must be unique (the
caller must have finished with it), and an implicit out-countract that
the return value is unique (even if you return outBuffer, because of
the implicit in-contract), so you /could/ code your way around this by
returning a string, and then casting away the invariance if you want
to reuse the buffer - but that is a nasty kludge. I can come up with
const-correct workarounds which do the job, but none which do the job
/as efficiently/.

Once again, this problem arises because D has no way to express
uniqueness. If we had such a means, then the const-correct prototype
would be

    unique(char)[] niftyStringFunction(unique(char)[] outBuffer, string input)

The workaround is simple enough though: just don't use const at this
level. Treat functions of this ilk as a low-level API layer. Then
supply a higher-level layer with more conventional semantics, which
hides the low-level layer from the caller. All the const casting can
go on in the interface between the two layers.
Jan 31 2008
next sibling parent reply Georg Wrede <georg nospam.org> writes:
Janice Caron wrote:
 OK, here's an example, borrowed a bit from Tango.
 
     char[] niftyStringFunction(char[] outBuffer, char[] input)
     {
         /*...*/
     }
 
 The idea is, if (outBuffer == null) then the function allocates memory
 and returns it; whereas, if (outBuffer != null) then the result is
 writen into outBuffer, and the return value is a slice of outBuffer.
 (The idea is to minimize allocations).
 
 Converting this to D2 is problematic, because you want to return a
 string, but string is invariant, so if you do that, you won't be able
 to pass the same buffer back into other, similarly nifty string
 functions.
 
 The interesting thing about this sort of function is that there is an
 implicit in-contract that outBuffer, if supplied, must be unique (the
 caller must have finished with it), and an implicit out-countract that
 the return value is unique (even if you return outBuffer, because of
 the implicit in-contract), so you /could/ code your way around this by
 returning a string, and then casting away the invariance if you want
 to reuse the buffer - but that is a nasty kludge. I can come up with
 const-correct workarounds which do the job, but none which do the job
 /as efficiently/.
 
 Once again, this problem arises because D has no way to express
 uniqueness. If we had such a means, then the const-correct prototype
 would be
 
     unique(char)[] niftyStringFunction(unique(char)[] outBuffer, string input)
 
 The workaround is simple enough though: just don't use const at this
 level. Treat functions of this ilk as a low-level API layer. Then
 supply a higher-level layer with more conventional semantics, which
 hides the low-level layer from the caller. All the const casting can
 go on in the interface between the two layers.
Unique sounds interesting. And new to me, at least as a definable concept. Question: suppose you have something defined as unique, and then somebody makes a copy of it. What happens to the uniqueness? (You may want to explain this even more broadly if you know that most people have had problems getting some specific part of the idea.)
Jan 31 2008
parent "Janice Caron" <caron800 googlemail.com> writes:
On 1/31/08, Georg Wrede <georg nospam.org> wrote:
 Unique sounds interesting. And new to me, at least as a definable concept.

 Question: suppose you have something defined as unique, and then
 somebody makes a copy of it. What happens to the uniqueness?

 (You may want to explain this even more broadly if you know that most
 people have had problems getting some specific part of the idea.)
Coincidentally, Andrei posted about this very thing just today. http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D.bugs&article_id=13195 He wrote Unique!(T) where I wrote unique(T). Uniqueness isn't something the compiler can check, so the idea is to make it the user's responsibility. Simply put, unique(T) can implicitly cast to T, const(T) or invariant(T). But woe betide any irresponsible programmer who declares something unique when it isn't!
Jan 31 2008
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Janice Caron" wrote
 OK, here's an example, borrowed a bit from Tango.

    char[] niftyStringFunction(char[] outBuffer, char[] input)
    {
        /*...*/
    }

 The idea is, if (outBuffer == null) then the function allocates memory
 and returns it; whereas, if (outBuffer != null) then the result is
 writen into outBuffer, and the return value is a slice of outBuffer.
 (The idea is to minimize allocations).

 Converting this to D2 is problematic, because you want to return a
 string, but string is invariant, so if you do that, you won't be able
 to pass the same buffer back into other, similarly nifty string
 functions.
char[] niftyStringFunction(char[] outBuffer, const(char)[] input) The function contract says, "I may write to outBuffer, I won't write to input. You can write to the result if you wish". How is this not what you want? -Steve
Jan 31 2008
next sibling parent reply "Kris" <foo bar.com> writes:
Steve - some D2 questions for ya:





no nesting/chaining of results permitted? That would be a problem :)

I find the current const approach to exhibit various opposing tensions, so 
I'm not exactly a 'fan' at this time. Taking an example related to the 
discussion, and if I understand correctly, the variations on Unique!(T) are 
little more than attempts to hide a cast() operation under a sugary coating, 
right? A need for such sugar in the language syntax would surely tend to 
indicate a notable flaw?

As far as the example api-style goes, here's hoping that this will work 
correctly in D2:



where 'in' indicates only that the input will not be modified by the callee. 
By doing so, the following should be clean:





Given that the input cannot be modified, you should also be able to pass a 
const or invariant string as an 'in' parameter; right?



If that's the case, then ok.

Assuming all is kosher so far, sliced 'in' arguments should also be accepted 
in the same fashion:



right?

Additionally, the optional output should always be good for use with slices 
also:





yes?

- Kris


"Steven Schveighoffer" <schveiguy yahoo.com> wrote in message 
news:fnu30k$2fnm$1 digitalmars.com...
 "Janice Caron" wrote
 OK, here's an example, borrowed a bit from Tango.

    char[] niftyStringFunction(char[] outBuffer, char[] input)
    {
        /*...*/
    }

 The idea is, if (outBuffer == null) then the function allocates memory
 and returns it; whereas, if (outBuffer != null) then the result is
 writen into outBuffer, and the return value is a slice of outBuffer.
 (The idea is to minimize allocations).

 Converting this to D2 is problematic, because you want to return a
 string, but string is invariant, so if you do that, you won't be able
 to pass the same buffer back into other, similarly nifty string
 functions.
char[] niftyStringFunction(char[] outBuffer, const(char)[] input) The function contract says, "I may write to outBuffer, I won't write to input. You can write to the result if you wish". How is this not what you want? -Steve
Jan 31 2008
next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On Feb 1, 2008 4:55 AM, Kris <foo bar.com> wrote:
 Steve - some D2 questions for ya:

 Taking an example related to the
 discussion, and if I understand correctly, the variations on Unique!(T) are
 little more than attempts to hide a cast() operation under a sugary coating,
 right?
Yes.
 A need for such sugar in the language syntax would surely tend to
 indicate a notable flaw?
It indicates a difficulty, yes. If something is invariant(T), then it /may/ be in a read-only segment, and so modifying it is undefined. That's fair enough. The thing is though that one can create a buffer full of stuff on the heap or the stack and then cast it to invariant. (That's what assumeUnique() does - it's more or less template sugar for cast(invariant)). Now you can pass all those objects to functions that take invariants, no problem. (At least, no problem if you don't mind explicit casts. Some people object to them). Unfortunately, it does have a negative consequence, which is exemplified in concatenation. To get a mutable buffer when concatenating, you have to do something like this: string s; char[] t; char[] buffer = cast(char[])("hello " ~ s ~ cast(invariant)t); This is known to be safe. Unfortunately, the D specification says that casting away invariance is undefined, so strictly speaking, one shouldn't do it. Perhaps, in the future, Andrei may come up with some clever library template to hide the cast, but it will still be there, under the hood. So the advantage of the Unique!() type would be that concatenation of strings could be allowed to return Unique!(char)[], which would mean that, together with other changes like implicitly casting all the supplied arguments to const, one would then be able to write: string s; char[] t; char[] buffer ="hello " ~ s ~ t; so you wouldn't necessarily actually /see/ the Unique!() types - they'd just be there to make the code compile.
 As far as the example api-style goes, here's hoping that this will work
 correctly in D2:
Everything works correctly in D2. The question is, will it work as expected? Will it provide the right API to allow calling code to use it? Will it demand additional explicit casts? If the philosopy is "explicit casts are bad", then what we have right now is not sufficient.
 <snip>
 yes?
I don't see anything wrong with anything you said. It just doesn't provide the right API to interface with higher layers, that's all. Some functions will call this like string result = other ("foo" ); and get a compile error. Sure - there are workarounds. That's really the whole point of this thread though.
Feb 01 2008
parent reply Sean Kelly <sean f4.ca> writes:
Janice Caron wrote:
 On Feb 1, 2008 4:55 AM, Kris <foo bar.com> wrote:
 Steve - some D2 questions for ya:

 Taking an example related to the
 discussion, and if I understand correctly, the variations on Unique!(T) are
 little more than attempts to hide a cast() operation under a sugary coating,
 right?
Yes.
 A need for such sugar in the language syntax would surely tend to
 indicate a notable flaw?
It indicates a difficulty, yes. If something is invariant(T), then it /may/ be in a read-only segment, and so modifying it is undefined. That's fair enough. The thing is though that one can create a buffer full of stuff on the heap or the stack and then cast it to invariant. (That's what assumeUnique() does - it's more or less template sugar for cast(invariant)). Now you can pass all those objects to functions that take invariants, no problem. (At least, no problem if you don't mind explicit casts. Some people object to them). Unfortunately, it does have a negative consequence, which is exemplified in concatenation. To get a mutable buffer when concatenating, you have to do something like this: string s; char[] t; char[] buffer = cast(char[])("hello " ~ s ~ cast(invariant)t); This is known to be safe. Unfortunately, the D specification says that casting away invariance is undefined, so strictly speaking, one shouldn't do it. Perhaps, in the future, Andrei may come up with some clever library template to hide the cast, but it will still be there, under the hood.
That's ridiculous. Is there an implicit cast here as well: int x = 1 + 2; And does the compiler complain about this: int y = 3 + x; This second example is exactly the same as your example above: char[] buffer = "hello " ~ t; Requiring casts here seems like a blatant flaw in the current design. I'm aware of the counter-example Andrei posted, but that doesn't change the fact that the above should have always worked. Quite frankly, I'm shocked that the const features were considered complete with something like this in place. One of these days I really need to spend some time with 2.0. I'm starting to wonder if no one has any issues with it simply because no one's using it. Sean
Feb 01 2008
next sibling parent "Kris" <foo bar.com> writes:
Hear hear, Sean ...

Explicit casts are for exceptional conditions, when you're deliberately 
working against the compiler for one reason or another. Walter has noted 
this (in one form or another) on various ocassions, so it's not as though he 
thinks cast() should be a common pedestrian requirement.



"Sean Kelly" <sean f4.ca> wrote in message 
news:fnvgm4$2akd$1 digitalmars.com...
 Janice Caron wrote:
 On Feb 1, 2008 4:55 AM, Kris <foo bar.com> wrote:
 Steve - some D2 questions for ya:

 Taking an example related to the
 discussion, and if I understand correctly, the variations on Unique!(T) 
 are
 little more than attempts to hide a cast() operation under a sugary 
 coating,
 right?
Yes.
 A need for such sugar in the language syntax would surely tend to
 indicate a notable flaw?
It indicates a difficulty, yes. If something is invariant(T), then it /may/ be in a read-only segment, and so modifying it is undefined. That's fair enough. The thing is though that one can create a buffer full of stuff on the heap or the stack and then cast it to invariant. (That's what assumeUnique() does - it's more or less template sugar for cast(invariant)). Now you can pass all those objects to functions that take invariants, no problem. (At least, no problem if you don't mind explicit casts. Some people object to them). Unfortunately, it does have a negative consequence, which is exemplified in concatenation. To get a mutable buffer when concatenating, you have to do something like this: string s; char[] t; char[] buffer = cast(char[])("hello " ~ s ~ cast(invariant)t); This is known to be safe. Unfortunately, the D specification says that casting away invariance is undefined, so strictly speaking, one shouldn't do it. Perhaps, in the future, Andrei may come up with some clever library template to hide the cast, but it will still be there, under the hood.
That's ridiculous. Is there an implicit cast here as well: int x = 1 + 2; And does the compiler complain about this: int y = 3 + x; This second example is exactly the same as your example above: char[] buffer = "hello " ~ t; Requiring casts here seems like a blatant flaw in the current design. I'm aware of the counter-example Andrei posted, but that doesn't change the fact that the above should have always worked. Quite frankly, I'm shocked that the const features were considered complete with something like this in place. One of these days I really need to spend some time with 2.0. I'm starting to wonder if no one has any issues with it simply because no one's using it. Sean
Feb 01 2008
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/1/08, Sean Kelly <sean f4.ca> wrote:
 Requiring casts here seems like a blatant flaw in the current design.
I agree. But I gather Andrei, at least, is working on fixing it.
 Quite frankly, I'm
 shocked that the const features were considered complete with something
 like this in place.
Has anyone said they're considered complete? I don't think anyone considers const to be finalized yet. It's still a work in progress, and there are still one or two niggles remaining, which get brought up from time to time. I guess we all assume that the wrinkles will get ironed out in time.
 I'm starting to wonder if no one has any issues with it
 simply because no one's using it.
Ah, now there I have to say you're wrong on both counts. There are still issues. Not as many as before, but still some minor ones. They do get reported - sometimes here, sometimes in bugzilla. And there are plenty of people using it, too (me, for one). That's how I know about the concatenation thing. I think it's more that the "cutting edge brigade" /expect/ things to have a few problems, and so aren't that distressed when we encounter them. We just figure out workarounds, and then post about it. Eventually, the workaround stops being needed. (...we would hope). I have to say, I love D2. I love the const features. I can live with the annoyances (for now) because I think we're working towards something marvellous.
Feb 01 2008
parent reply Sean Kelly <sean f4.ca> writes:
Janice Caron wrote:
 On 2/1/08, Sean Kelly <sean f4.ca> wrote:
 Requiring casts here seems like a blatant flaw in the current design.
I agree. But I gather Andrei, at least, is working on fixing it.
Yes, thanks to Steven's bug report. What bothers me is that it apparently took a bug report and a follow-up argument to get this addressed. I'm all for making things illegal until there is a substantive argument for why they should not be, but this particular feature is so fundamental that I'm quite honestly shocked that it was left un-addressed.
 Quite frankly, I'm
 shocked that the const features were considered complete with something
 like this in place.
Has anyone said they're considered complete?
Walter said so to me privately recently. I asked him about it because I didn't want to devote the time to porting Tango if there was any chance that the design may change.
 I don't think anyone
 considers const to be finalized yet. It's still a work in progress,
 and there are still one or two niggles remaining, which get brought up
 from time to time. I guess we all assume that the wrinkles will get
 ironed out in time.
See above. However, in this particular case, changing the way these expressions are handled won't break any code. It will simply eliminate the necessity for cast operations. So I suppose one could argue that the syntax has been finalized but the implementation may still be tweaked a bit.
 I think it's more that the "cutting edge brigade" /expect/ things to
 have a few problems, and so aren't that distressed when we encounter
 them. We just figure out workarounds, and then post about it.
 Eventually, the workaround stops being needed. (...we would hope).
I suppose I've always been a bit different then. If I encounter something that seems broken I'll generally avoid it until it works properly. Traditionally, I would have posted a proposal or critique about it as well, but I've become a bit jaded about this over time. At this point it's more likely that I'll just spend the hour working on something productive rather than writing a long post that will be probably be ignored (this is how I feel, even if it isn't true).
 I have to say, I love D2. I love the const features. I can live with
 the annoyances (for now) because I think we're working towards
 something marvellous.
To be honest, I have trepidations about 2.0, largely for what many would feel are silly reasons. For example, I simply don't find the aesthetics of the new features appealing. And while many might say this aesthetics are unimportant, they are a significant reason why I initially switched to D from C++. For the rest, I am mostly waiting for 2.0 to get some of the features that I actually care about. Being a long-time C++ person one might think that I would consider const to be tremendously important, but experience with it has mostly made me feel that const is often more trouble than it's worth. In large projects, I more often find myself struggling against other programmer's inconsistent choices of how to apply const to their designs than I do benefiting from any apparent security that it provides. That said, I do think that the choice for transitive const in D will help tremendously, because it mandates consistency of design. Sean
Feb 01 2008
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Sean Kelly wrote:
 For the rest, I am mostly waiting for 2.0 to get some of the features
 that I actually care about.
There is a lot more coming <g>. For example, I'm pretty excited about the upcoming destructors for structs - they're an earthquake.
 Being a long-time C++ person one might
 think that I would consider const to be tremendously important, but
 experience with it has mostly made me feel that const is often more
 trouble than it's worth.  In large projects, I more often find myself
 struggling against other programmer's inconsistent choices of how to
 apply const to their designs than I do benefiting from any apparent
 security that it provides.  That said, I do think that the choice for
 transitive const in D will help tremendously, because it mandates
 consistency of design.
I suppose everyone knows how I feel about C++ const - it's the germ of a good idea, but it's a feature that relies more on programmer convention rather than language enforcement. This makes it more trouble than it's worth. D const, on the other hand, is driven by the idea that the semantics of it must be reliable. The twin concepts of invariantness and transitivity are the key. As we discussed, I think it results in a paradigm change, rather than just sprinkling const through the declarations. Invariant strings are one example of the transformative nature of it, and it took me a long time to 'get it'. So I understand completely the skepticism about it.
Feb 01 2008
next sibling parent Sean Kelly <sean f4.ca> writes:
Walter Bright wrote:
 
 Invariant strings are one example of the transformative nature of it,
 and it took me a long time to 'get it'. So I understand completely the
 skepticism about it.
I'll admit that I have my trepidations about invariant strings, because it seems that they could cause a great deal of GC churn that may be avoided with in-place modification. But perhaps this is simply an unreasonable fear. Sean
Feb 02 2008
prev sibling parent Sergey Gromov <snake.scaly gmail.com> writes:
Walter Bright Wrote:

 D const, on the other hand, is driven by the idea that the semantics of 
 it must be reliable. The twin concepts of invariantness and transitivity 
 are the key. As we discussed, I think it results in a paradigm change, 
 rather than just sprinkling const through the declarations.
 
 Invariant strings are one example of the transformative nature of it, 
 and it took me a long time to 'get it'. So I understand completely the 
 skepticism about it.
There wouldn't be any need in unique() if not invariant(). Right now programmers have to rely on conventions and their knowledge of data structures rather than on strict typing. SnakE
Feb 03 2008
prev sibling parent reply Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
Sean Kelly wrote:

 Quite frankly, I'm
 shocked that the const features were considered complete with something
 like this in place.  One of these days I really need to spend some time
 with 2.0.  I'm starting to wonder if no one has any issues with it
 simply because no one's using it.
I can't help but agree. The very first thing I tried when D 2.009 was released was: struct Vector { int x,y; } invariant Vector up = {0,1}; void main() { Vector y = up; } which didn't work because: Error: no property 'opCall' for type 'Vector' ??? No idea what that means. Is it trying to implicitly call a non-existant struct static opCall "constructor"? and changing the main body to: Vector y; y = up; Yields: Error: cannot implicitly convert expression (up) of type invariant(Vector) to Vector Declaring constants using the "const" keyword is of course(?) wrong, and: (I use the function call to illustrate the point, as just initializing a variable gives the weird opCall error) const Vector c = {1,1}; void foo(invariant Vector v) {} void main() { foo(c); } Yields: Error: cannot implicitly convert expression (c) of type const(Vector) to invariant(Vector) And changing that to a "manifest constant": enum Vector c = {1,1}; Yields: Error: cannot implicitly convert expression (c) of type Vector to invariant(Vector) So, we have three different ways of declaring constants that are not only incompatible with each other, but also incompatible with non-constants. (This is what prompted me to post the "orthogonal const proposal") In my book, there are only two kinds of plain values. Constant and variable. Why D needs 4 kinds is beyond me. Plain constant values should always be implicitly convertible to non-constants. (Plain values are values that contain no references to other data). Those examples only deal with plain values. Not a single reference is included. If this design is final and D2 can't even handle values properly I'm afraid there is not much attraction in D2 for me... There are other issues with D2 const. Some I feel are quite serious and others mainly aesthetic, but I can't be bothered to write about them now. I don't even know why I bothered writing all this down. It is not like I have any illusions about it making any difference what so ever. In the meantime, D1 works well and remains a great language that is a pleasure to use. ;) -- Oskar
Feb 01 2008
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Oskar Linde:

 In my book, there are only two kinds of plain values. Constant and 
 variable. Why D needs 4 kinds is beyond me.
It may be beyond you (and me) but it may be a good idea anyway :-) There things in some Haskell code that I need to understand still, but I don't criticize them.
 I don't even know why I bothered writing all this down. It is not 
 like I have any illusions about it making any difference what so ever.
I have read this post of yours and I agree with most things you say. If lot of people think about this like you then maybe GDC will not grow all the features of D 2.x ;-)
 In the meantime, D1 works well and remains a great language that is a 
 pleasure to use. ;)
But it seems a dead end. I am not expert enough to offer real suggestions, but I can express a general opinion regarding the current const regime of 2.x: it may be more "correct" and well designed, but sometimes in programming "worse is better", it means that a simpler system, even if it has some known flaws, may be preferred in the long term. Trying to do the "right" thing may lead to more problems than choosing a simpler way. That's probably why C is used today too, despite its load of faults and deficiencies. This means that maybe a simpler constant system maybe be "better" even if it's "worse". Bye, bearophile
Feb 01 2008
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Oskar Linde wrote:
 I can't help but agree. The very first thing I tried when D 2.009 was 
 released was:
 
 struct Vector { int x,y; }
 invariant Vector up = {0,1};
 void main() {
     Vector y = up;
 }
 
 which didn't work because:
     Error: no property 'opCall' for type 'Vector'
 ??? No idea what that means. Is it trying to implicitly call a 
 non-existant struct static opCall "constructor"?
The issue here is assigning a mutable instance of Vector to an invariant. If Vector contained a pointer, then there's a big correctness hole. So then the issue is "since Vector does not contain any pointers, shouldn't this work?" The problem then is that if one builds code that uses Vector in this manner, and later the maintainer adds a pointer member variable to Vector, then all those uses of Vector break. The reason for that particular error message is that because invariant(Vector) is not the same type as Vector, the compiler looks for an overload of the form: static invariant(Vector) opCall(Vector);
 and changing the main body to:
 
     Vector y;
     y = up;
 
 Yields:
     Error: cannot implicitly convert expression (up) of type 
 invariant(Vector) to Vector
This, of course, is the reverse problem. If Vector contains a pointer, then copying an invariant pointer to a mutable pointer is a giant hole in correctness.
 Declaring constants using the "const" keyword is of course(?) wrong, 
 and: (I use the function call to illustrate the point, as just 
 initializing a variable gives the weird opCall error)
 
 const Vector c = {1,1};
 void foo(invariant Vector v) {}
 void main() { foo(c); }
 
 Yields:
     Error: cannot implicitly convert expression (c) of type 
 const(Vector) to invariant(Vector)
const cannot be implicitly converted to either mutable or invariant, for the same reasons outlined above.
 And changing that to a "manifest constant":
 
 enum Vector c = {1,1};
 
 Yields:
     Error: cannot implicitly convert expression (c) of type Vector to 
 invariant(Vector)
 
 So, we have three different ways of declaring constants that are not 
 only incompatible with each other, but also incompatible with 
 non-constants. (This is what prompted me to post the "orthogonal const 
 proposal")
 
 In my book, there are only two kinds of plain values. Constant and 
 variable. Why D needs 4 kinds is beyond me. Plain constant values should 
 always be implicitly convertible to non-constants. (Plain values are 
 values that contain no references to other data).
This is really the root issue here - should structs with references behave differently that structs without?
 Those examples only deal with plain values. Not a single reference is 
 included. If this design is final and D2 can't even handle values 
 properly I'm afraid there is not much attraction in D2 for me...
 
 There are other issues with D2 const. Some I feel are quite serious and 
 others mainly aesthetic, but I can't be bothered to write about them 
 now. I don't even know why I bothered writing all this down. It is not 
 like I have any illusions about it making any difference what so ever.
 
 In the meantime, D1 works well and remains a great language that is a 
 pleasure to use. ;)
 
Feb 02 2008
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Walter Bright" wrote
 Oskar Linde wrote:
 I can't help but agree. The very first thing I tried when D 2.009 was 
 released was:

 struct Vector { int x,y; }
 invariant Vector up = {0,1};
 void main() {
     Vector y = up;
 }

 which didn't work because:
     Error: no property 'opCall' for type 'Vector'
 ??? No idea what that means. Is it trying to implicitly call a 
 non-existant struct static opCall "constructor"?
The issue here is assigning a mutable instance of Vector to an invariant. If Vector contained a pointer, then there's a big correctness hole. So then the issue is "since Vector does not contain any pointers, shouldn't this work?" The problem then is that if one builds code that uses Vector in this manner, and later the maintainer adds a pointer member variable to Vector, then all those uses of Vector break.
I just wanted to respond to this one point. I think it is unreasonable for the compiler to restrict code from compiling because someone might change an API or implementation in the future. It is always possible to break code in the future by changing an API or type. Why should a developer who is certain that the type will never contain a pointer be punished because the compiler can't predict what he will do with the type in the future? What if someone does add a pointer to vector, then other pieces of code that expect copying a vector to make a 'deep copy' will break. I understand the concern, but I just don't think your argument is valid. Also, having this restriction makes it impossible to mimic builtin types with struct wrappers, as builtin types already enjoy this benefit. The real question is, is it possible for the compiler to determine whether a type contains pointers, and if so, restrict copying const or invariant versions of the type to mutable types. I believe the answer is yes, although the compiler doesn't take that into account today. I even think that it is strictly a compile-time check and therefore should not affect any existing code. If this is the case, the next question is, why would it be undesirable to have this behavior? I have not yet seen a good answer to this. -Steve
Feb 04 2008
parent Walter Bright <newshound1 digitalmars.com> writes:
Steven Schveighoffer wrote:
 I think it is unreasonable for the compiler to restrict code from compiling 
 because someone might change an API or implementation in the future.  It is 
 always possible to break code in the future by changing an API or type.  Why 
 should a developer who is certain that the type will never contain a pointer 
 be punished because the compiler can't predict what he will do with the type 
 in the future?  What if someone does add a pointer to vector, then other 
 pieces of code that expect copying a vector to make a 'deep copy' will 
 break.  I understand the concern, but I just don't think your argument is 
 valid.  Also, having this restriction makes it impossible to mimic builtin 
 types with struct wrappers, as builtin types already enjoy this benefit.
 
 The real question is, is it possible for the compiler to determine whether a 
 type contains pointers, and if so, restrict copying const or invariant 
 versions of the type to mutable types.  I believe the answer is yes, 
 although the compiler doesn't take that into account today.  I even think 
 that it is strictly a compile-time check and therefore should not affect any 
 existing code.
 
 If this is the case, the next question is, why would it be undesirable to 
 have this behavior?  I have not yet seen a good answer to this.
Your argument is sound, and I agree with it. I think it's pretty clear this will have to be changed.
Feb 05 2008
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Kris" wrote
 Steve - some D2 questions for ya:





 no nesting/chaining of results permitted? That would be a problem :)
This would compile. Remember, mutable arrays are implicitly castable to const arrays :)
 I find the current const approach to exhibit various opposing tensions, so 
 I'm not exactly a 'fan' at this time. Taking an example related to the 
 discussion, and if I understand correctly, the variations on Unique!(T) 
 are little more than attempts to hide a cast() operation under a sugary 
 coating, right? A need for such sugar in the language syntax would surely 
 tend to indicate a notable flaw?
I believe the current approach is workable save 2 things: 1. There should be a way to have head-const classes, where the reference itself is mutable, but the class data is not 2. There should be a way to specify that a return value is mutable, but can be implicitly cast to an invariant (i.e. the unique(X) syntax Janice proposed).
 As far as the example api-style goes, here's hoping that this will work 
 correctly in D2:



 where 'in' indicates only that the input will not be modified by the 
 callee. By doing so, the following should be clean:





 Given that the input cannot be modified, you should also be able to pass a 
 const or invariant string as an 'in' parameter; right?


'in' isn't necessary in my mind. How can the callee modify the string if the callee is in the same thread? I think what you want here is const(char)[] input. This specifies that the input is not supposed to change.
 If that's the case, then ok.

 Assuming all is kosher so far, sliced 'in' arguments should also be 
 accepted in the same fashion:


if you replace in with const, then yes.
 right?

 Additionally, the optional output should always be good for use with 
 slices also:





 yes?
Well, as long as other doesn't allocate more data if buf can't hold it :) Then yes, it is fine. -Steve
Feb 01 2008
next sibling parent reply Jason House <jason.james.house gmail.com> writes:
Steven Schveighoffer Wrote:
 1. There should be a way to have head-const classes, where the reference 
 itself is mutable, but the class data is not
I believe that's tail const, not head const.
Feb 01 2008
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Jason House" wrote
 Steven Schveighoffer Wrote:
 1. There should be a way to have head-const classes, where the reference
 itself is mutable, but the class data is not
I believe that's tail const, not head const.
Yes of course, you are correct, my bad :) -Steve
Feb 01 2008
prev sibling next sibling parent reply "Kris" <foo bar.com> writes:
"Steven Schveighoffer" <schveiguy yahoo.com> wrote ...

 I believe the current approach is workable save 2 things:

 1. There should be a way to have head-const classes, where the reference 
 itself is mutable, but the class data is not
Check
 2. There should be a way to specify that a return value is mutable, but 
 can be implicitly cast to an invariant (i.e. the unique(X) syntax Janice 
 proposed).
If implicit, as you say, the sugar would be redundant. On the other hand, explicit casting all over the place would be a major step backwards
 As far as the example api-style goes, here's hoping that this will work 
 correctly in D2:



 where 'in' indicates only that the input will not be modified by the 
 callee. By doing so, the following should be clean:





 Given that the input cannot be modified, you should also be able to pass 
 a const or invariant string as an 'in' parameter; right?


'in' isn't necessary in my mind. How can the callee modify the string if the callee is in the same thread? I think what you want here is const(char)[] input. This specifies that the input is not supposed to change.
only because 'in' actually appears to do what it implies, whilst apparently accepting mutable, const, or invariant. Not to mention it is a darn sight more readable than the decorative alternatives. BTW: how would one pass an invariant string as a const(char[]) argument, per the above example? Isn't there a cast required? Also, who said anything about being in the same thread? All I'm concerned with here is the semantics of the callee, and what can be passed to it without casts all over the place
 If that's the case, then ok.

 Assuming all is kosher so far, sliced 'in' arguments should also be 
 accepted in the same fashion:


if you replace in with const, then yes.
Are you quite certain of that?
 right?

 Additionally, the optional output should always be good for use with 
 slices also:





 yes?
Well, as long as other doesn't allocate more data if buf can't hold it :) Then yes, it is fine.
the discrete type for slices in D2 does not interfere with this?
Feb 01 2008
parent reply Sergey Gromov <snake.scaly gmail.com> writes:
Kris Wrote:
 "Steven Schveighoffer" <schveiguy yahoo.com> wrote ...
 As far as the example api-style goes, here's hoping that this will work 
 correctly in D2:



 where 'in' indicates only that the input will not be modified by the 
 callee. By doing so, the following should be clean:





 Given that the input cannot be modified, you should also be able to pass 
 a const or invariant string as an 'in' parameter; right?


'in' isn't necessary in my mind. How can the callee modify the string if the callee is in the same thread?
Um... by modifying it ? If it's not either 'in' or 'const' ?
 I think what you want here is 
 const(char)[] input.  This specifies that the input is not supposed to 
 change.
only because 'in' actually appears to do what it implies, whilst apparently accepting mutable, const, or invariant. Not to mention it is a darn sight more readable than the decorative alternatives. BTW: how would one pass an invariant string as a const(char[]) argument, per the above example? Isn't there a cast required?
Anything casts to 'const' implicitly because 'const' doesn't violate any contracts for other types. The 'in' in D2 seems to be just an alias for 'const'.
 If that's the case, then ok.

 Assuming all is kosher so far, sliced 'in' arguments should also be 
 accepted in the same fashion:


if you replace in with const, then yes.
Are you quite certain of that?
'const' and 'in' work exactly the same, just checked. void foo(in char[] s) { writeln(s); } void bar(const char[] s) { writeln(s); } foo("test"); foo("test"[0..2]); foo("test".dup); bar("test"); bar("test"[0..2]); bar("test".dup); compiles and works correctly. SnakE
Feb 01 2008
parent Kris <foo bar.com> writes:
Sergey Gromov Wrote:

 
 'const' and 'in' work exactly the same, just checked.
 
 void foo(in char[] s) { writeln(s); }
 void bar(const char[] s) { writeln(s); }
 
 foo("test");
 foo("test"[0..2]);
 foo("test".dup);
 bar("test");
 bar("test"[0..2]);
 bar("test".dup);
 
 compiles and works correctly.
 
 SnakE
Thank you, SnakE, for clarifying that. I'd been harbouring a misguided impression that invariant would not implicitly cast to const
Feb 01 2008
prev sibling parent Sean Kelly <sean f4.ca> writes:
Steven Schveighoffer wrote:
 "Kris" wrote
 As far as the example api-style goes, here's hoping that this will work 
 correctly in D2:



 where 'in' indicates only that the input will not be modified by the 
 callee. By doing so, the following should be clean:





 Given that the input cannot be modified, you should also be able to pass a 
 const or invariant string as an 'in' parameter; right?


'in' isn't necessary in my mind. How can the callee modify the string if the callee is in the same thread? I think what you want here is const(char)[] input. This specifies that the input is not supposed to change.
Doesn't "in char[]" map to "const char[]" in 2.0?
 Additionally, the optional output should always be good for use with 
 slices also:





 yes?
Well, as long as other doesn't allocate more data if buf can't hold it :) Then yes, it is fine.
This is one area where I'd like to have head const, as I think it would eliminate the problems that the "T[new]" change was proposed to address. buf[0..8] can't be resized (in place), but its contents should be modifiable. Sean
Feb 01 2008
prev sibling parent reply Sergey Gromov <snake.scaly gmail.com> writes:
Kris Wrote:
 A need for such sugar in the language syntax would surely tend to 
 indicate a notable flaw?
Yes you're right. It's an attempt to avoid undefined behavior in specification in situations where the behavior is essentially defined. 1. Anything you 'new' is basically unique: new char[] "a".dup "a" ~ "b" are all of type unique(unique(char)[]), so as any immediate literal: typeof(4) == unique(int) 2. Any non-unique type within a type casts away uniqueness: typeof(["a", "b"]) == invariant(char)[][] int[] x = [1,2,3]; int[][] y = [x]; typeof(y.dup) == int[][] but typeof([[1,2,3]]) == unique(unique(unique(int)[])[]) 3. unique(...) casts to invariant(...), const(...), or just ... implicitly with well-defined behavior. 4. There is no such thing as unique variable. Did I miss something ? SnakE
Feb 01 2008
next sibling parent Sergey Gromov <snake.scaly gmail.com> writes:
Sergey Gromov Wrote:

 Kris Wrote:
 A need for such sugar in the language syntax would surely tend to 
 indicate a notable flaw?
Yes you're right. It's an attempt to avoid undefined behavior in specification in situations where the behavior is essentially defined. 1. Anything you 'new' is basically unique: new char[] "a".dup "a" ~ "b" are all of type unique(unique(char)[]), so as any immediate literal: typeof(4) == unique(int) 2. Any non-unique type within a type casts away uniqueness: typeof(["a", "b"]) == invariant(char)[][] int[] x = [1,2,3]; int[][] y = [x]; typeof(y.dup) == int[][] but typeof([[1,2,3]]) == unique(unique(unique(int)[])[]) 3. unique(...) casts to invariant(...), const(...), or just ... implicitly with well-defined behavior. 4. There is no such thing as unique variable. Did I miss something ?
Of course: 5. Only unique() casts implicitly to unique(). 6. A function can have a unique() result, transitive. SnakE
Feb 01 2008
prev sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/1/08, Sergey Gromov <snake.scaly gmail.com> wrote:
 1. Anything you 'new' is basically unique:
 2. Any non-unique type within a type casts away uniqueness:
 3. unique(...) casts to invariant(...), const(...), or just ... implicitly
 with well-defined behavior.
 4. There is no such thing as unique variable.

 Did I miss something ?
Only function returns, I think. Anything you "new" is unique, and unique things can also be obtained as function returns (if the function created it with "new" etc). As soon as you assign it to a variable, or use it in an expression it stops being unique. You need a way of expressing that functions can accept unique objects, and return them. e.g. unique(char)[] fillWithSpaces(unique(char)s) { foreach(ref c;s) c = ' '; return s; } We know that s[0] = 'x' has no side effects, because the function has been promised that there are no other references to s (not even in the calling code). We similarly promise to the callee that there are no other references to the return value. Then you get to call it like: string s = fillWithSpaces(new char[10]); so, new creates a unique(char)[]. That gets passed to the function. The function implicitly casts it to char[], then fills it with spaces. Then it returns it, and the function return type converts it back to unique(char)[]. Finally, it's assigned to a variable of type invariant(char)[]. There was plenty of casting going on there, but all of it was implicit
Feb 01 2008
parent reply Walter Bright <newshound1 digitalmars.com> writes:
There are some research papers about unique as a type qualifier, and it 
was toyed around with for a while with D. The trouble with it was:

1) complexity (yet another type qualifier)

2) it still never was quite able to get rid of all the interesting cases 
where a cast(invariant) (i.e. at some point trust the user) was needed

3) it was hard to understand

4) its utility seemed very limited
Feb 02 2008
next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/2/08, Walter Bright <newshound1 digitalmars.com> wrote:
 There are some research papers about unique as a type qualifier, and it
 was toyed around with for a while with D. The trouble with it was:

 1) complexity (yet another type qualifier)

 2) it still never was quite able to get rid of all the interesting cases
 where a cast(invariant) (i.e. at some point trust the user) was needed

 3) it was hard to understand

 4) its utility seemed very limited
Yep, you're spot on the nail. Nobody can argue with any of those points (except possibly (1), but we can go into that elsewhere if needed). Still - nobody here was suggesting unique "on a whim". Rather, it was to solve a class of problems, the most obvious of which is to allow the following to compile: string s; char[] t; char[] u = "hello" ~ s ~ t; It could well be that we don't need unique, but you are always going to get people complaining that they have to do: string s; char[] t; char[] u = "hello".dup ~ s.dup ~ t; or string s; char[] t; char[] u = cast(char[])("hello" ~ s ~ cast(invariant(char)[])t); The former doesn't look too bad but has unnecessary casts; the latter looks ugly and is error-prone. It's not just concatenation. There's a whole class of similar functions which are likely to cause similar headaches, and cause people to be critical of the whole const system. Without unique, you're going to have to convince people that superfluous dups are really OK. Maybe that's the way to go. Maybe making new and gc collection really, really fast might do that?
Feb 02 2008
next sibling parent reply Derek Parnell <derek psych.ward> writes:
On Sat, 2 Feb 2008 11:42:57 +0000, Janice Caron wrote:

 Still - nobody here was suggesting unique "on a whim". Rather, it was
 to solve a class of problems, the most obvious of which is to allow
 the following to compile:
 
     string s;
     char[] t;
     char[] u = "hello" ~ s ~ t;
But that already compiles and runs okay.
 It could well be that we don't need unique, but you are always going
 to get people complaining that they have to do:
 
     string s;
     char[] t;
     char[] u = "hello".dup ~ s.dup ~ t;
No, you don't have to do that.
 or
 
     string s;
     char[] t;
     char[] u = cast(char[])("hello" ~ s ~ cast(invariant(char)[])t);
Now you're just being silly. -- Derek Parnell Melbourne, Australia skype: derek.j.parnell
Feb 02 2008
parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/2/08, Derek Parnell <derek psych.ward> wrote:
 On Sat, 2 Feb 2008 11:42:57 +0000, Janice Caron wrote:

 Still - nobody here was suggesting unique "on a whim". Rather, it was
 to solve a class of problems, the most obvious of which is to allow
 the following to compile:

     string s;
     char[] t;
     char[] u = "hello" ~ s ~ t;
But that already compiles and runs okay.
No it doesn't. The error message is: Error: non-constant expression cast(const(char)[])("hello" ~ s) ~ cast(const(char)[])t; (Perhaps you were thinking of D1.x. This is a D2.x conversation)
Feb 02 2008
parent reply Derek Parnell <derek psych.ward> writes:
On Sat, 2 Feb 2008 12:54:54 +0000, Janice Caron wrote:

 On 2/2/08, Derek Parnell <derek psych.ward> wrote:
 On Sat, 2 Feb 2008 11:42:57 +0000, Janice Caron wrote:

 Still - nobody here was suggesting unique "on a whim". Rather, it was
 to solve a class of problems, the most obvious of which is to allow
 the following to compile:

     string s;
     char[] t;
     char[] u = "hello" ~ s ~ t;
But that already compiles and runs okay.
No it doesn't. The error message is: Error: non-constant expression cast(const(char)[])("hello" ~ s) ~ cast(const(char)[])t; (Perhaps you were thinking of D1.x. This is a D2.x conversation)
Ok, so maybe I'm /am/ going mad ... but this is what happens on my machine ... c:\temp>type test.d import std.stdio; void main() { string s; char[] t; char[] u = "hello" ~ s ~ t; writefln("%s", u); } c:\temp>dmd test c:\dmd\dmd\bin\..\..\dm\bin\link.exe test,,,user32+kernel32/noi; c:\temp>test hello c:\temp>dmd Digital Mars D Compiler v2.010 Copyright (c) 1999-2008 by Digital Mars written by Walter Bright Documentation: http://www.digitalmars.com/d/index.html -- Derek Parnell Melbourne, Australia skype: derek.j.parnell
Feb 02 2008
parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/2/08, Derek Parnell <derek psych.ward> wrote:
 void main()
 {
     string s;
     char[] t;
     char[] u = "hello" ~ s ~ t;

     writefln("%s", u);
 }
 c:\temp>dmd test
 c:\dmd\dmd\bin\..\..\dm\bin\link.exe test,,,user32+kernel32/noi;

 c:\temp>test
 hello
Bah! Dangnabbit! OK then - /this/ doesn't compile:
     string s;
     char[] t;
     string u = "hello" ~ s ~ t;
I'm fairly sure that the char[] version didn't compile in some earlier version of D, but I'm not going to redownload the old versions to find out. In any case the string version doesn't compile, and that's just as bad. The error message indicates that the compiler can't convert a char[] to an invariant(char)[], which means that the type of the RHS is now deemed to be char[]. (In previous releases, I'm sure it would have been const(char)[], but I see nothing in the change log to account for it. Maybe it's I who's going mad?)
Feb 02 2008
parent reply Derek Parnell <derek psych.ward> writes:
On Sun, 3 Feb 2008 07:44:03 +0000, Janice Caron wrote:

 On 2/2/08, Derek Parnell <derek psych.ward> wrote:
 void main()
 {
     string s;
     char[] t;
     char[] u = "hello" ~ s ~ t;

     writefln("%s", u);
 }
 c:\temp>dmd test
 c:\dmd\dmd\bin\..\..\dm\bin\link.exe test,,,user32+kernel32/noi;

 c:\temp>test
 hello
Bah! Dangnabbit! OK then - /this/ doesn't compile:
     string s;
     char[] t;
     string u = "hello" ~ s ~ t;
Of course it doesn't. As far as I can recall, D has always said that concatenation creates a dynamic array, and since 'const' has been in use, that array is also a mutable one.
 ... the string version doesn't compile, and that's just
 as bad. The error message indicates that the compiler can't convert a
 char[] to an invariant(char)[], which means that the type of the RHS
 is now deemed to be char[].
Well its not so much that it can't convert it, but more that it won't convert it implicitly; you have to be explicit. Try this instead ... string s; char[] t; string u = ("hello" ~ s ~ t).idup; We have to be aware that the rule in D is that RHS concatentation always produces a mutable dynamic array, so if we want it to be immutable we need to be explicit about it. Maybe smart compilers can even avoid redundant copying if they see the ".idup" operation in this context.
 (In previous releases, I'm sure it would
 have been const(char)[], but I see nothing in the change log to
 account for it. Maybe it's I who's going mad?)
I don't think it (the RHS formed by concatenation) was ever a const thingy. -- Derek Parnell Melbourne, Australia skype: derek.j.parnell
Feb 03 2008
parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/3/08, Derek Parnell <derek psych.ward> wrote:
 As far as I can recall, D has always said that
 concatenation creates a dynamic array, and since 'const' has been in use,
 that array is also a mutable one.
I don't think that's true. Back in November, Steven Schveighoffer wrote this post: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=61276 Stephen was finding he needed to explicitly .dup to get a mutable result. Since nothing implicitly casts to both char[] and string, some duping or iduping or explicit casting would always be necessary in some circumstance or other. You can't keep /everyone/ happy (as you could in D1). Unique would have fixed that problem. But that said, Walter has now effectively closed this discussion by saying that concatenation will in future give a polysemous result - a hack which does the same as what unique would have done, but /only/ in that circumstance. Once this is done, everyone's going to be happy.
Feb 03 2008
parent reply Derek Parnell <derek psych.ward> writes:
On Sun, 3 Feb 2008 08:43:54 +0000, Janice Caron wrote:

 On 2/3/08, Derek Parnell <derek psych.ward> wrote:
 As far as I can recall, D has always said that
 concatenation creates a dynamic array, and since 'const' has been in use,
 that array is also a mutable one.
I don't think that's true. Back in November, Steven Schveighoffer wrote this post: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=61276 Stephen was finding he needed to explicitly .dup to get a mutable result.
With respect and meaning no offense, however even though that post of Steven's says...
 auto a2 = a1 ~ "\n";
 Now, a2 is declared as a const(char)[] array,
He has not shown any evidence that that is actually the case. It could be that he is assuming that to be the case, because each attempt I have made to duplicate his assertion has just shown me that the compiler regards 'a2' as mutable. The current D2 compiler treats 'a2' as mutable. -- Derek Parnell Melbourne, Australia skype: derek.j.parnell
Feb 03 2008
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Derek Parnell" wrote
 On Sun, 3 Feb 2008 08:43:54 +0000, Janice Caron wrote:

 On 2/3/08, Derek Parnell wrote:
 As far as I can recall, D has always said that
 concatenation creates a dynamic array, and since 'const' has been in 
 use,
 that array is also a mutable one.
I don't think that's true. Back in November, Steven Schveighoffer wrote this post: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=61276 Stephen was finding he needed to explicitly .dup to get a mutable result.
With respect and meaning no offense, however even though that post of Steven's says...
 auto a2 = a1 ~ "\n";
 Now, a2 is declared as a const(char)[] array,
He has not shown any evidence that that is actually the case. It could be that he is assuming that to be the case, because each attempt I have made to duplicate his assertion has just shown me that the compiler regards 'a2' as mutable. The current D2 compiler treats 'a2' as mutable.
I probably should respond to this :) D2 at the time did have an issue with a similar statement. I don't know if that's the case now, but it was the basis for my bug report (1654). What triggered me to write the post is that at the time I was attempting to port tango to D 2 (I think it was 2.006 or 2.007), and there was a piece of a function that attempted to add a string literal to the end of a const or mutable char array (can't remember which), and since 2.x has strings as invariant, the compiler was complaining. I was forced to do a dup, or needlessly initialize a dynamic array. I may have misrepresented the exact problem, which may have lead to me posting valid code, sorry about that. I thought I had posted the correct thing. I believe that the return value of any concatenation operation should be mutable, as it always creates a new copy. The issue becomes when you are dealing with only invariants, i.e.: string s = "hello" ~ " world"; Now, if the concatenation operation returned a mutable string, then you need an explicit cast. However I think the problem is being worked on by Walter and Andrei. -Steve
Feb 03 2008
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Steven Schveighoffer" wrote
 "Derek Parnell" wrote
 On Sun, 3 Feb 2008 08:43:54 +0000, Janice Caron wrote:

 On 2/3/08, Derek Parnell wrote:
 As far as I can recall, D has always said that
 concatenation creates a dynamic array, and since 'const' has been in 
 use,
 that array is also a mutable one.
I don't think that's true. Back in November, Steven Schveighoffer wrote this post: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=61276 Stephen was finding he needed to explicitly .dup to get a mutable result.
With respect and meaning no offense, however even though that post of Steven's says...
 auto a2 = a1 ~ "\n";
 Now, a2 is declared as a const(char)[] array,
He has not shown any evidence that that is actually the case. It could be that he is assuming that to be the case, because each attempt I have made to duplicate his assertion has just shown me that the compiler regards 'a2' as mutable. The current D2 compiler treats 'a2' as mutable.
I probably should respond to this :) D2 at the time did have an issue with a similar statement. I don't know if that's the case now, but it was the basis for my bug report (1654). What triggered me to write the post is that at the time I was attempting to port tango to D 2 (I think it was 2.006 or 2.007), and there was a piece of a function that attempted to add a string literal to the end of a const or mutable char array (can't remember which), and since 2.x has strings as invariant, the compiler was complaining. I was forced to do a dup, or needlessly initialize a dynamic array. I may have misrepresented the exact problem, which may have lead to me posting valid code, sorry about that. I thought I had posted the correct thing. I believe that the return value of any concatenation operation should be mutable, as it always creates a new copy. The issue becomes when you are dealing with only invariants, i.e.: string s = "hello" ~ " world"; Now, if the concatenation operation returned a mutable string, then you need an explicit cast. However I think the problem is being worked on by Walter and Andrei. -Steve
I just tested this out in D 2.006, and you are correct. a1 ~ "\n" results in a char[]. I think when I was porting the code, it looked more like: a1 ~ '\n'; which results in a const(char)[]. So the example I gave originally is invalid, but the point is not. The result of concatenation should be determined by the usage of the result, and allowed if it doesn't break constness. -Steve
Feb 04 2008
parent Derek Parnell <derek psych.ward> writes:
On Mon, 4 Feb 2008 10:01:06 -0500, Steven Schveighoffer wrote:

 I just tested this out in D 2.006, and you are correct.  a1 ~ "\n" results 
 in a char[].  I think when I was porting the code, it looked more like:
 
 a1 ~ '\n';
 
 which results in a const(char)[].
 
 So the example I gave originally is invalid, but the point is not.  The 
 result of concatenation should be determined by the usage of the result, and 
 allowed if it doesn't break constness.
Yes, and another point is why does the compiler treat string ~ char and string ~ string differently!? -- Derek Parnell Melbourne, Australia skype: derek.j.parnell
Feb 04 2008
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Janice Caron wrote:
 Still - nobody here was suggesting unique "on a whim". Rather, it was
 to solve a class of problems, the most obvious of which is to allow
 the following to compile:
 
     string s;
     char[] t;
     char[] u = "hello" ~ s ~ t;
I understand that's a problem. The solution, however, doesn't need unique. It can be solved by having the result type of ~ be a polysemous type, i.e. its type is based on how it is used.
Feb 02 2008
next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 2/2/08, Walter Bright <newshound1 digitalmars.com> wrote:
 It can be solved by having the result type of ~ be a polysemous
 type, i.e. its type is based on how it is used.
Nice! (I haven't quite wrapped my head around those, yet!)
Feb 02 2008
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/2/08, Walter Bright <newshound1 digitalmars.com> wrote:
 It can be solved by having the result type of ~ be a polysemous
 type, i.e. its type is based on how it is used.
Will we be able to write our own functions which are declared to return polysemous types? Sorry, I'm getting all excited now! :-)
Feb 02 2008
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Janice Caron wrote:
 On 2/2/08, Walter Bright <newshound1 digitalmars.com> wrote:
 It can be solved by having the result type of ~ be a polysemous
 type, i.e. its type is based on how it is used.
Will we be able to write our own functions which are declared to return polysemous types?
No. They're basically a hack.
Feb 02 2008
next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
Walter Bright wrote:
 Janice Caron wrote:
 On 2/2/08, Walter Bright <newshound1 digitalmars.com> wrote:
 It can be solved by having the result type of ~ be a polysemous
 type, i.e. its type is based on how it is used.
Will we be able to write our own functions which are declared to return polysemous types?
No. They're basically a hack.
We can do it with macros, though, right?
Feb 03 2008
parent Walter Bright <newshound1 digitalmars.com> writes:
Robert Fraser wrote:
 Walter Bright wrote:
 Janice Caron wrote:
 On 2/2/08, Walter Bright <newshound1 digitalmars.com> wrote:
 It can be solved by having the result type of ~ be a polysemous
 type, i.e. its type is based on how it is used.
Will we be able to write our own functions which are declared to return polysemous types?
No. They're basically a hack.
We can do it with macros, though, right?
Yes.
Feb 03 2008
prev sibling parent reply Russell Lewis <webmaster villagersonline.com> writes:
If we had a opImplicitCast (and it was overloadable), then it would be 
pretty easy to implement a template with thunks:



template PolysemousContainer(A,B)
{
   A delegate() thunk1;
   B delegate() thunk2;

   A opImplicitCast() { return thunk1(); }
   B opImplicitCast() { return thunk2(); }
}

PolySemousContainer(A,B)
Polysemous(A,B)(A delegate() thunk1,
                 B delegate() thunk2)
{
   PolysemousContainer!(A,B) ret;
     ret.thunk1 = thunk1;
     ret.thunk2 = thunk2;
   return ret;
}



Walter Bright wrote:
 Janice Caron wrote:
 On 2/2/08, Walter Bright <newshound1 digitalmars.com> wrote:
 It can be solved by having the result type of ~ be a polysemous
 type, i.e. its type is based on how it is used.
Will we be able to write our own functions which are declared to return polysemous types?
No. They're basically a hack.
Feb 03 2008
parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 03/02/2008, Russell Lewis <webmaster villagersonline.com> wrote:
 If we had a opImplicitCast (and it was overloadable), then it would be
 pretty easy to implement a template with thunks:
It would - but you'd quickly find ambiguities, at which the compiler would also complain, again requiring an explicit cast to keep the compiler happy.
Feb 03 2008
parent reply Russell Lewis <webmaster villagersonline.com> writes:
Janice Caron wrote:
 On 03/02/2008, Russell Lewis <webmaster villagersonline.com> wrote:
 If we had a opImplicitCast (and it was overloadable), then it would be
 pretty easy to implement a template with thunks:
It would - but you'd quickly find ambiguities, at which the compiler would also complain, again requiring an explicit cast to keep the compiler happy.
Can you expand on this? Another (less elegant) solution to this would be to allow overloaded opCast(). You could, of course, just access the underlying fields in the struct, but that seems less than desirable. I could imagine an "OptionalInvariant" template which held two thunks, one of which would produce the invariant version, and one which would produce the variant version.
Feb 04 2008
parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 04/02/2008, Russell Lewis <webmaster villagersonline.com> wrote:
 Janice Caron wrote:
 On 03/02/2008, Russell Lewis <webmaster villagersonline.com> wrote:
 If we had a opImplicitCast (and it was overloadable), then it would be
 pretty easy to implement a template with thunks:
It would - but you'd quickly find ambiguities, at which the compiler would also complain, again requiring an explicit cast to keep the compiler happy.
Can you expand on this?
Sure. If one function, f, returned PolysemousContainer(A,B), and another function, g, was overloaded thus: g(A a) { /* do something */ } g(B b) { /* do something else */ } then the expression g( f( ) ) would be ambiguous.
Feb 05 2008
parent Russell Lewis <webmaster villagersonline.com> writes:
Janice Caron wrote:
 On 04/02/2008, Russell Lewis <webmaster villagersonline.com> wrote:
 Janice Caron wrote:
 On 03/02/2008, Russell Lewis <webmaster villagersonline.com> wrote:
 If we had a opImplicitCast (and it was overloadable), then it would be
 pretty easy to implement a template with thunks:
It would - but you'd quickly find ambiguities, at which the compiler would also complain, again requiring an explicit cast to keep the compiler happy.
Can you expand on this?
Sure. If one function, f, returned PolysemousContainer(A,B), and another function, g, was overloaded thus: g(A a) { /* do something */ } g(B b) { /* do something else */ } then the expression g( f( ) ) would be ambiguous.
Ok, good point. But, IMHO, that isn't a good argument against polysemous types. Remember that we already have that problem with various built-in types which have multiple possible implicit casts (such as numeric types). IMHO, the problem is not a problem with the polysemous type, but with the *user* of a polysemous type, and in that case, it is quite reasonable to require the user to resolve the ambiguity. For users which don't have any ambiguity, polysemous types express a clear and very useful thing: "There are two or more perfectly valid ways to express this value. Which do you want?"
Feb 05 2008
prev sibling next sibling parent Sean Kelly <sean f4.ca> writes:
Walter Bright wrote:
 Janice Caron wrote:
 Still - nobody here was suggesting unique "on a whim". Rather, it was
 to solve a class of problems, the most obvious of which is to allow
 the following to compile:

     string s;
     char[] t;
     char[] u = "hello" ~ s ~ t;
I understand that's a problem. The solution, however, doesn't need unique. It can be solved by having the result type of ~ be a polysemous type, i.e. its type is based on how it is used.
Awesome. I was wondering if that's where we were going with this. Sean
Feb 02 2008
prev sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Walter Bright wrote:
 Janice Caron wrote:
 Still - nobody here was suggesting unique "on a whim". Rather, it was
 to solve a class of problems, the most obvious of which is to allow
 the following to compile:

     string s;
     char[] t;
     char[] u = "hello" ~ s ~ t;
I understand that's a problem. The solution, however, doesn't need unique. It can be solved by having the result type of ~ be a polysemous type, i.e. its type is based on how it is used.
More simply, catenation of a mutable variable with a non-mutable one could yield a mutable one by default. A const cast is safer than a non-const cast, after all. One could extend that to all binary operators, as well. This might reduce the usage of const. Walter doesn't seem concerned about that, since he ignored just about everyone here when we asked for function arguments to be const by default.
Feb 03 2008
parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 03/02/2008, Christopher Wright <dhasenan gmail.com> wrote:
 More simply, catenation of a mutable variable with a non-mutable one
 could yield a mutable one by default. A const cast is safer than a
 non-const cast, after all.
Except that "non-mutable" doesn't necessarily mean const, and "non-const" doesn't necessarily mean mutable. I think you left invariant out of the mix there.
 This might reduce the usage of const. Walter doesn't seem concerned
 about that, since he ignored just about everyone here when we asked for
 function arguments to be const by default.
I wouldn't say no to revisiting that possibility! :-)
Feb 03 2008
parent Christopher Wright <dhasenan gmail.com> writes:
Janice Caron wrote:
 On 03/02/2008, Christopher Wright <dhasenan gmail.com> wrote:
 More simply, catenation of a mutable variable with a non-mutable one
 could yield a mutable one by default. A const cast is safer than a
 non-const cast, after all.
Except that "non-mutable" doesn't necessarily mean const, and "non-const" doesn't necessarily mean mutable. I think you left invariant out of the mix there.
My suggestion was: invariant(char)[] ~ char[] yields char[] const(char)[] ~ char[] yields char[] For clarity: invariant(char)[] ~ const(char)[] yields invariant(char)[]
Feb 03 2008
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 2/2/08, Janice Caron <caron800 googlemail.com> wrote:
 The former doesn't look too bad but has unnecessary casts
I meant, unnecessary dups.
Feb 02 2008
prev sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/2/08, Janice Caron <caron800 googlemail.com> wrote:
 Still - nobody here was suggesting unique "on a whim". Rather, it was
 to solve a class of problems, the most obvious of which is to allow
 the following to compile:

     string s;
     char[] t;
     char[] u = "hello" ~ s ~ t;
That said, we could always have a concatenation function in phobos: string s; char[] t; char[] u = cat("hello",s,t);
Feb 02 2008
parent reply Christopher Wright <dhasenan gmail.com> writes:
Janice Caron wrote:
 On 2/2/08, Janice Caron <caron800 googlemail.com> wrote:
 Still - nobody here was suggesting unique "on a whim". Rather, it was
 to solve a class of problems, the most obvious of which is to allow
 the following to compile:

     string s;
     char[] t;
     char[] u = "hello" ~ s ~ t;
That said, we could always have a concatenation function in phobos: string s; char[] t; char[] u = cat("hello",s,t);
Which is silly, when we have a catenation operator in the language.
Feb 02 2008
parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/2/08, Christopher Wright <dhasenan gmail.com> wrote:
 Janice Caron wrote:
 That said, we could always have a concatenation function in phobos:

     string s;
     char[] t;
     char[] u = cat("hello",s,t);
Which is silly, when we have a catenation operator in the language.
Not really. We could have two functions, cat and icat, whereby cat always returns a mutable, and icat always returns an invariant. The concatenation operator, by contrast, always returns the same type as its inputs.
Feb 02 2008
parent Christopher Wright <dhasenan gmail.com> writes:
Janice Caron wrote:
 Not really. We could have two functions, cat and icat, whereby cat
 always returns a mutable, and icat always returns an invariant. The
 concatenation operator, by contrast, always returns the same type as
 its inputs.
The issue with the concatenation operator is that its operands need not be the same type. You can use invariant(char)[] and char[], and invariant(char)[] takes precedence. I'm not sure how often this issue comes up. If you have a buffer, you can append to it with ~=. You might want to prepend something, though, and in that case, the current behavior will be problematic. If you want to prepend something to a string, but you want to build the stuff you prepend...this seems like a marginal use case, more so than the buffer use case. It's easy enough to do: char[] build; const(char)[] prepend = build; str = prepend ~ build; The workaround for a mutable buffer is: char[] tmpBuffer = new char[buffer.length + prepend.length]; tmpBuffer[0..prepend.length] = prepend[]; tmpBuffer[prepend.length..$] = buffer[]; buffer = tmpBuffer; That is extremely ugly.
Feb 03 2008
prev sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On Feb 1, 2008 3:15 AM, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 How is this not what you want?
In D1, it's /exactly/ what you want. But if you've ever tried to port Tango code to D2 (I have) then this is the point at which constancy gets in the way. Assuming one wants const correctness at higher levels, the compiler baulks because at some point you end up trying to pass const data to a non-const function. To get it to compile, you more or less have to take const out right the way up the chain to the higher level API, and ultimately end up not being able to pass strings (which are invariant) to functions to which you would natually want to pass them. It's the whole "rippling up" effect. From memory, I believe my workaround was to keep those nifty functions exactly as is, but hide some casting in upper layers of the API. Of course, for that not to result in undefined behavior, you have to understand /exactly/ what is being modified and when, and that's where the brain starts to hurt.
Feb 01 2008
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Janice Caron" wrote
 On Feb 1, 2008 3:15 AM, Steven Schveighoffer wrote:
 How is this not what you want?
In D1, it's /exactly/ what you want.
D1 does not have this type of function signature possible, I was talking about D2.
 But if you've ever tried to port Tango code to D2 (I have)
I have. In fact, Frank Benoit and I successfully produced a tango tree that worked with D 2.007.
 then this
 is the point at which constancy gets in the way. Assuming one wants
 const correctness at higher levels, the compiler baulks because at
 some point you end up trying to pass const data to a non-const
 function.
We did not have that happen to us. We did not have a single place where we had to cast away const. Of course, we did not produce a fully const tango, but that was mostly because there were still some bugs with const that made it impossible to be const-correct in Tango. Namely, that you could not overload a const function with a non-const function, and that you could override a const function with a non const function )(there were probably others, but those stand out in my mind). I'm not sure if these have been fixed in the latest DMD, but I think your arguments are anecdotal without an anecdote. I believe it is possible with the proposed const scheme to have const arguments and const functions and be fully const-correct. Invariant functions and invariant args, that's a different story. Because you cannot yet implicitly cast a mutable return value to an invariant, even though technically it is, code is going to look ugly. either you have wrapper functions for everything which do the cast, or you have to cast in your code. This is where your unique idea comes in handy, and that's really the only place I support it (as the return value of a function). -Steve
Feb 01 2008
prev sibling parent "Joel C. Salomon" <joelcsalomon gmail.com> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Janice Caron wrote:
 Once again, this problem arises because D has no way to express
 uniqueness. If we had such a means, then the const-correct prototype
 would be
 
     unique(char)[] niftyStringFunction(unique(char)[] outBuffer, string input)
Looks a lot like C’s proposed noalias to me; dmr’s problems with noalias might not apply exactly to D, but I’d be cautious. - --Joel -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iD8DBQFHosNqzLx4GzBL9dYRAiOgAJ93AkHzAJKfyuSA9ybFZygg8oBTtACg08x8 qjDDivlk8ex1i+YpkrOfSI0= =equv -----END PGP SIGNATURE-----
Jan 31 2008
prev sibling parent reply Sergey Gromov <snake.scaly gmail.com> writes:
Janice Caron Wrote:

 On 2/1/08, Sergey Gromov <snake.scaly gmail.com> wrote:
 1. Anything you 'new' is basically unique:
 2. Any non-unique type within a type casts away uniqueness:
 3. unique(...) casts to invariant(...), const(...), or just ... implicitly
 with well-defined behavior.
 4. There is no such thing as unique variable.

 Did I miss something ?
Only function returns, I think.
Of course, you're right.
 As soon as you assign it to a variable, or use it in an expression it
 stops being unique.
Inline array initialization is also an expression, and unique must survive to allow the whole array be unique. Why using in expression makes data not unique ?
 You need a way of expressing that functions can accept unique objects,
 and return them. e.g.
 
     unique(char)[] fillWithSpaces(unique(char)s)
     {
         foreach(ref c;s) c = ' ';
         return s;
     }
I'm afraid that's not possible if you want strict type safety. A unique parameter effectively results in a local variable of type unique. Or, even worse, of non-unique type. In either case, the compiler doesn't have any chance to check what's happening and enforce type safety. Which is a must: implicit conversions must be safe. So the only way to return a unique result from a function is to dup or new or inline-construct directly within its return statement. Other return values are illegal. SnakE
Feb 01 2008
parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/1/08, Sergey Gromov <snake.scaly gmail.com> wrote:
 You need a way of expressing that functions can accept unique objects,
 and return them. e.g.

     unique(char)[] fillWithSpaces(unique(char)[]s)
     {
         foreach(ref c;s) c = ' ';
         return s;
     }
I'm afraid that's not possible if you want strict type safety
What I'm telling the compiler here is that the function's formal parameter, s, is unique at the callee site. Within the function, it stops being unique. Thus, within the function body, s is just a char[]. At the callee site, however, the compiler gets to complain if a non-unique type is passed to the function. (My example passed it the return value from new, so it was OK).
 So the only way to return a unique result from a function is to dup
 or new or inline-construct directly within its return statement.  Other
 return values are illegal.
Good point. It should have been return cast(unique)s;
Feb 01 2008
next sibling parent reply Sergey Gromov <snake.scaly gmail.com> writes:
Janice Caron Wrote:
 What I'm telling the compiler here is that the function's formal
 parameter, s, is unique at the callee site. Within the function, it
 stops being unique. Thus, within the function body, s is just a
 char[]. At the callee site, however, the compiler gets to complain if
 a non-unique type is passed to the function.
On second thoughts, why would you want a function with a unique parameter ? Nothing casts to unique(), so that the caller is always required to dup() or whatever. Why force the user do it if you can always dup inside the function if you wish ? I think it only complicates the function usage. SnakE
 
 (My example passed it the return value from new, so it was OK).
 
 
 So the only way to return a unique result from a function is to dup
 or new or inline-construct directly within its return statement.  Other
 return values are illegal.
Good point. It should have been return cast(unique)s;
Feb 01 2008
parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 2/1/08, Sergey Gromov <snake.scaly gmail.com> wrote:
 On second thoughts, why would you want a function with a unique
 parameter ?  Nothing casts to unique(), so that the caller is always
 required to dup() or whatever.
So that you can do string s; string t = f( g( h( i( j( k( s )))))); The functions f,g,h,i,j and k all return unique. The functions f,g,h,i and j (but not k) all accept a unique parameter. In this example, no duping or casting is required, because the temporary value is unique right the way through the chain.
 Why force the user do it if you can
 always dup inside the function if you wish ?
That would be five dups in the above example. The fact is, if don't mind duping, we don't need unique /at all/. e.g. string s; char[] t; char[] u = "hello".dup ~ s.dup ~ t; But if we're concerned to minimise allocations and duplications, then you need functions that /accept/ uniques, as well as functions that return them, for reasons demonstrated in the first example.
 I think it only complicates
 the function usage.
As with an other function in-contracts, yes. But misuse would be compiler checkable, so string s; string t = f( s ); (where f is as above) would be a compile error (cannot implicitly cast invariant(char)[] to unique(char)[]). The caller of f would have to change it to one of: string t = f( s.dup ); or string t = f( cast(unique)s ); In the latter case, of course, it would be the caller's responsibility to guarantee uniqueness. If the programmer is wrong, undefined behaviour results.
Feb 01 2008
next sibling parent Sergey Gromov <snake.scaly gmail.com> writes:
Janice Caron Wrote:

 On 2/1/08, Sergey Gromov <snake.scaly gmail.com> wrote:
 On second thoughts, why would you want a function with a unique
 parameter ?  Nothing casts to unique(), so that the caller is always
 required to dup() or whatever.
So that you can do string s; string t = f( g( h( i( j( k( s ))))));
Something tells me that this use case is very rare, but the dangers are great: every and each of those functions will include an explicit cast to unique. Can't prove the rariness though. The discussion loses its point unfortunately. The chances that the unique() gets into the language are very slim. SnakE
Feb 03 2008
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Janice Caron" wrote
 On 2/1/08, Sergey Gromov wrote:
 On second thoughts, why would you want a function with a unique
 parameter ?  Nothing casts to unique(), so that the caller is always
 required to dup() or whatever.
So that you can do string s; string t = f( g( h( i( j( k( s )))))); The functions f,g,h,i,j and k all return unique. The functions f,g,h,i and j (but not k) all accept a unique parameter. In this example, no duping or casting is required, because the temporary value is unique right the way through the chain.
Can't you just declare the functions as accepting mutable strings? Doesn't unique(char)[] implicitly cast to char[]? As Sergey says, this FORCES you to do a dup if you don't have a unique value. -Steve
Feb 03 2008
prev sibling parent reply Sergey Gromov <snake.scaly gmail.com> writes:
Janice Caron Wrote:
 So the only way to return a unique result from a function is to dup
 or new or inline-construct directly within its return statement.  Other
 return values are illegal.
Good point. It should have been return cast(unique)s;
The possibility to cast should still be there for complex cases where you can't/don't want to put data allocation into return statement but can guarantee its uniqueness. SnakE
Feb 01 2008
next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 2/1/08, Sergey Gromov <snake.scaly gmail.com> wrote:
 The possibility to cast should still be there for complex cases where you
 can't/don't want to put data allocation into return statement but can
 guarantee its uniqueness.
I disagree. For type safety, it should be required wherever a non-unique type is assigned to a unique type. By necessity, this must include function returns. Thus unique(char)[] f() { char[] s = whatever(); return cast(unique)s; } In this case, the cast to unique is unavoidable, since, if you omit it, you would be attempting to implicitly cast s (a char[]) to the return type, upon return. This cast is required for type safety, and justified because the code does guarantee uniqueness (because s goes out of scope on return, so at that point there will be no references to the data apart from the return value). It is almost impossible to /prove/ uniqueness, so it is reasonable to rely on programmer assertion (casts). What the compiler /can/ do, and do well, is ensure type safety.
Feb 01 2008
prev sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 2/2/08, Janice Caron <caron800 googlemail.com> wrote:
 On 2/1/08, Sergey Gromov <snake.scaly gmail.com> wrote:
 The possibility to cast should still be there for complex cases where you
 can't/don't want to put data allocation into return statement but can
 guarantee its uniqueness.
I disagree.
Actually, I just realised I might be agreeing (not disagreeing) with you there! Sorry about that.
Feb 01 2008