digitalmars.D - const challenge
- Jason House <jason.james.house gmail.com> Jan 30 2008
- Jason House <jason.james.house gmail.com> Jan 30 2008
- "Janice Caron" <caron800 googlemail.com> Jan 30 2008
- "Steven Schveighoffer" <schveiguy yahoo.com> Jan 31 2008
- Jason House <jason.james.house gmail.com> Jan 31 2008
- "Steven Schveighoffer" <schveiguy yahoo.com> Jan 31 2008
- Jason House <jason.james.house gmail.com> Jan 31 2008
- Georg Wrede <georg nospam.org> Jan 31 2008
- "Steven Schveighoffer" <schveiguy yahoo.com> Jan 31 2008
- "Kris" <foo bar.com> Jan 31 2008
- "Steven Schveighoffer" <schveiguy yahoo.com> Feb 01 2008
- Jason House <jason.james.house gmail.com> Feb 01 2008
- "Steven Schveighoffer" <schveiguy yahoo.com> Feb 01 2008
- "Kris" <foo bar.com> Feb 01 2008
- Sergey Gromov <snake.scaly gmail.com> Feb 01 2008
- Kris <foo bar.com> Feb 01 2008
- Sean Kelly <sean f4.ca> Feb 01 2008
- Sean Kelly <sean f4.ca> Feb 01 2008
- "Kris" <foo bar.com> Feb 01 2008
- Sean Kelly <sean f4.ca> Feb 01 2008
- Walter Bright <newshound1 digitalmars.com> Feb 01 2008
- Sean Kelly <sean f4.ca> Feb 02 2008
- Sergey Gromov <snake.scaly gmail.com> Feb 03 2008
- Oskar Linde <oskar.lindeREM OVEgmail.com> Feb 01 2008
- bearophile <bearophileHUGS lycos.com> Feb 01 2008
- Walter Bright <newshound1 digitalmars.com> Feb 02 2008
- "Steven Schveighoffer" <schveiguy yahoo.com> Feb 04 2008
- Walter Bright <newshound1 digitalmars.com> Feb 05 2008
- Sergey Gromov <snake.scaly gmail.com> Feb 01 2008
- Sergey Gromov <snake.scaly gmail.com> Feb 01 2008
- Walter Bright <newshound1 digitalmars.com> Feb 02 2008
- Derek Parnell <derek psych.ward> Feb 02 2008
- Derek Parnell <derek psych.ward> Feb 02 2008
- Derek Parnell <derek psych.ward> Feb 03 2008
- Derek Parnell <derek psych.ward> Feb 03 2008
- "Steven Schveighoffer" <schveiguy yahoo.com> Feb 03 2008
- "Steven Schveighoffer" <schveiguy yahoo.com> Feb 04 2008
- Derek Parnell <derek psych.ward> Feb 04 2008
- Christopher Wright <dhasenan gmail.com> Feb 02 2008
- Christopher Wright <dhasenan gmail.com> Feb 03 2008
- Walter Bright <newshound1 digitalmars.com> Feb 02 2008
- Walter Bright <newshound1 digitalmars.com> Feb 02 2008
- Robert Fraser <fraserofthenight gmail.com> Feb 03 2008
- Walter Bright <newshound1 digitalmars.com> Feb 03 2008
- Russell Lewis <webmaster villagersonline.com> Feb 03 2008
- Russell Lewis <webmaster villagersonline.com> Feb 04 2008
- Russell Lewis <webmaster villagersonline.com> Feb 05 2008
- Sean Kelly <sean f4.ca> Feb 02 2008
- Christopher Wright <dhasenan gmail.com> Feb 03 2008
- Christopher Wright <dhasenan gmail.com> Feb 03 2008
- "Janice Caron" <caron800 googlemail.com> Feb 03 2008
- "Janice Caron" <caron800 googlemail.com> Feb 03 2008
- "Janice Caron" <caron800 googlemail.com> Feb 02 2008
- "Joel C. Salomon" <joelcsalomon gmail.com> Jan 31 2008
- "Janice Caron" <caron800 googlemail.com> Feb 02 2008
- "Janice Caron" <caron800 googlemail.com> Feb 02 2008
- "Janice Caron" <caron800 googlemail.com> Feb 02 2008
- "Janice Caron" <caron800 googlemail.com> Feb 02 2008
- "Janice Caron" <caron800 googlemail.com> Feb 02 2008
- "Janice Caron" <caron800 googlemail.com> Feb 02 2008
- "Janice Caron" <caron800 googlemail.com> Feb 01 2008
- "Janice Caron" <caron800 googlemail.com> Jan 31 2008
- "Janice Caron" <caron800 googlemail.com> Jan 31 2008
- "Janice Caron" <caron800 googlemail.com> Feb 01 2008
- "Steven Schveighoffer" <schveiguy yahoo.com> Feb 01 2008
- "Janice Caron" <caron800 googlemail.com> Feb 01 2008
- "Janice Caron" <caron800 googlemail.com> Feb 01 2008
- "Janice Caron" <caron800 googlemail.com> Feb 02 2008
- "Janice Caron" <caron800 googlemail.com> Feb 03 2008
- "Janice Caron" <caron800 googlemail.com> Feb 05 2008
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" 2. Find a proper/elegant way to solve problems brought up to challenge #1. 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
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
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
"Jason House" wroteJason 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
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
"Jason House" wroteSteven 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
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
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
"Janice Caron" wroteOK, 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
Steve - some D2 questions for ya:
# char[] other (const(char)[] input, char[] output = null);
#
# auto result = other (niftyStringFunction(x, y));
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:
# char[] other (in char[] input, char[] output = null);
where 'in' indicates only that the input will not be modified by the callee.
By doing so, the following should be clean:
# char[] nested (in char[] input, char[] output = null);
#
# auto result = nested (other (x, y));
Given that the input cannot be modified, you should also be able to pass a
const or invariant string as an 'in' parameter; right?
# auto result = other ("foo");
If that's the case, then ok.
Assuming all is kosher so far, sliced 'in' arguments should also be accepted
in the same fashion:
# auto result = other ("foo"[0..2]);
right?
Additionally, the optional output should always be good for use with slices
also:
# char[32] buf;
#
# auto result = other ("foo", buf[0..8]);
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
"Kris" wroteSteve - some D2 questions for ya: # char[] other (const(char)[] input, char[] output = null); # # auto result = other (niftyStringFunction(x, y)); 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: # char[] other (in char[] input, char[] output = null); where 'in' indicates only that the input will not be modified by the callee. By doing so, the following should be clean: # char[] nested (in char[] input, char[] output = null); # # auto result = nested (other (x, y)); Given that the input cannot be modified, you should also be able to pass a const or invariant string as an 'in' parameter; right? # auto result = other ("foo");
'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: # auto result = other ("foo"[0..2]);
if you replace in with const, then yes.right? Additionally, the optional output should always be good for use with slices also: # char[32] buf; # # auto result = other ("foo", buf[0..8]); 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
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
"Jason House" wroteSteven 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
"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
Check2. 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 backwardsAs far as the example api-style goes, here's hoping that this will work correctly in D2: # char[] other (in char[] input, char[] output = null); where 'in' indicates only that the input will not be modified by the callee. By doing so, the following should be clean: # char[] nested (in char[] input, char[] output = null); # # auto result = nested (other (x, y)); Given that the input cannot be modified, you should also be able to pass a const or invariant string as an 'in' parameter; right? # auto result = other ("foo");
'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 placeIf that's the case, then ok. Assuming all is kosher so far, sliced 'in' arguments should also be accepted in the same fashion: # auto result = other ("foo"[0..2]);
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: # char[32] buf; # # auto result = other ("foo", buf[0..8]); 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
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: # char[] other (in char[] input, char[] output = null); where 'in' indicates only that the input will not be modified by the callee. By doing so, the following should be clean: # char[] nested (in char[] input, char[] output = null); # # auto result = nested (other (x, y)); Given that the input cannot be modified, you should also be able to pass a const or invariant string as an 'in' parameter; right? # auto result = other ("foo");
'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: # auto result = other ("foo"[0..2]);
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
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
Steven Schveighoffer wrote:"Kris" wroteAs far as the example api-style goes, here's hoping that this will work correctly in D2: # char[] other (in char[] input, char[] output = null); where 'in' indicates only that the input will not be modified by the callee. By doing so, the following should be clean: # char[] nested (in char[] input, char[] output = null); # # auto result = nested (other (x, y)); Given that the input cannot be modified, you should also be able to pass a const or invariant string as an 'in' parameter; right? # auto result = other ("foo");
'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: # char[32] buf; # # auto result = other ("foo", buf[0..8]); 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
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
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
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
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
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
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
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
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
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
"Walter Bright" wroteOskar 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
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
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
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
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
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
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
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
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
"Derek Parnell" wroteOn 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
"Steven Schveighoffer" wrote"Derek Parnell" wroteOn 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
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
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
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
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
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
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
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
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
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
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:
would also complain, again requiring an explicit cast to keep the compiler happy.
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
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
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
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
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
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
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
-----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
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
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
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
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
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
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
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
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
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
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
"Janice Caron" wroteOn 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
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
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
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
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
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









"Janice Caron" <caron800 googlemail.com> 