digitalmars.D - Transitive const sucks
- Alex Burton (31/31) Sep 11 2007 While const is being reexamined....
- Regan Heath (24/65) Sep 11 2007 I remember your example case from your earlier thread. :)
- Regan Heath (6/10) Sep 11 2007 Actually, correction, this should probably be:
- Alex Burton (13/39) Sep 11 2007 Yes essentially we need to be able to distinguish between mutable refere...
- Janice Caron (18/18) Sep 11 2007 Content-Disposition: inline
- Alex Burton (7/8) Sep 11 2007 It then further generalised to anything "state machiney" :)
- Janice Caron (18/20) Sep 11 2007 Yes, it's even more obvious with a read than with a write.
- Sean Kelly (10/38) Sep 11 2007 My classic example for the need of "mutable" is a mutex used to
- Janice Caron (4/7) Sep 11 2007 Again, if we let D's "private" attribute also imply C++'s "mutable", the...
- Bruno Medeiros (9/18) Sep 11 2007 I'm don't think that's entirely true. 'invariant' the keyword is indeed
- Sean Kelly (8/21) Sep 11 2007 True enough. But the current design (ie. transitive and lacking
- Walter Bright (14/21) Sep 12 2007 This is not the primary goal (it is a side effect of the primary goals)....
- Steven Schveighoffer (39/54) Sep 12 2007 How does making const transitive allow for functional programming? From...
- Janice Caron (1/4) Sep 12 2007 I suspect he meant "copy on write", but forget to tell us. :-)
- Gregor Richards (5/13) Sep 12 2007 "const int void f()" is clearly a bad declaration. Anyway, "const int
- Steven Schveighoffer (4/17) Sep 12 2007 Um... yeah, I meant to say f() is const. Sorry my example code didn't
- Janice Caron (4/7) Sep 12 2007 See thread "properties and const"
- Janice Caron (13/14) Sep 12 2007 Here's an example of copy-on-write:
- Bruce Adams (5/23) Sep 12 2007 If you are living in the wonderful world of unix. Copy on write automagi...
- Walter Bright (10/34) Sep 12 2007 Although const is necessary for f.p., it is not sufficient for f.p., as
- Steven Schveighoffer (23/44) Sep 12 2007 OK, but why not allow BOTH logical const and transitive const? What if
- Sean Kelly (9/17) Sep 12 2007 This was perhaps too strong a statement on my part. Given the design of...
- Walter Bright (6/13) Sep 12 2007 Interestingly, the look of C++ apps have changed *substantially* over
- Walter Bright (21/26) Sep 11 2007 You're right in that transitive const does not support the notion of
- Janice Caron (80/101) Sep 12 2007 But you could guarantee that /non-private/ data will not change. And sin...
- Bruce Adams (6/24) Sep 12 2007 You obviously missed the discussion of the "pure" keyword, borrowed from...
- Janice Caron (6/7) Sep 12 2007 Cool!
- Bruce Adams (3/16) Sep 12 2007 Getting constness and purity mixed up is liable to open a whole new can ...
- Ingo Oeser (14/18) Sep 12 2007 They are like mappings (e.g y = f(x) ) in math.
- Walter Bright (2/12) Sep 12 2007 No. Pass a reference to a const through a non-pure function to a pure on...
- Walter Bright (33/78) Sep 12 2007 BTW, Janice, I appreciate your thoughts on this. You're obviously an
- Janice Caron (6/13) Sep 12 2007 That's "with", as in, "on the same side as", not "arguing" as in
- Janice Caron (87/106) Sep 12 2007 I've written a lot of multithreaded programming. A lot.
- Sean Kelly (37/114) Sep 12 2007 In general that's true, though you might want a separate mutex class for...
- Walter Bright (60/168) Sep 12 2007 Ok. I've written some, enough to know it is very, very hard to get
- Bill Baxter (35/48) Sep 12 2007 You use these arguments to justify that it's the compiler's job to help
- Robert Fraser (2/58) Sep 12 2007 D's current const fixes both problems rather nicely, I still can't see t...
- James Dennett (38/52) Sep 12 2007 The term may be new from the C++ community, but the notion
- Janice Caron (69/93) Sep 13 2007 I could start another thread (...newsgroup thread, I mean...) for that.
- Janice Caron (4/18) Sep 13 2007 Except of course that using the "mutable" keyword ensures that /only/
- Christopher Wright (12/12) Sep 13 2007 Walter Bright wrote: well, quite a lot, really.
- Sean Kelly (3/14) Sep 13 2007 Now there's an idea. I like it.
- Reiner Pope (13/47) Sep 12 2007 Is logical const really that bad? What would happen if you required in
- Janice Caron (18/18) Sep 12 2007 Content-Disposition: inline
- Walter Bright (4/7) Sep 12 2007 At least for this example, the same issue exists when attempting to do
- James Dennett (9/18) Sep 12 2007 No, it doesn't. C++ doesn't hide the references from you
- Walter Bright (5/11) Sep 13 2007 Try turning a char* into a const char* at the top level of your program
- James Dennett (9/22) Sep 13 2007 OK, now I understand: and what you say is true, const-correctness
- Bruce Adams (14/31) Sep 13 2007 This is a good point to emphasise. In object oriented programming
- Sean Kelly (5/32) Sep 13 2007 For what it's worth, I think object ownership could be communicated via
- Janice Caron (12/12) Sep 12 2007 Content-Disposition: inline
- Bruce Adams (3/21) Sep 12 2007 BTW if there any chance you can change your mail options not to post in ...
- Walter Bright (6/20) Sep 12 2007 You can:
- Janice Caron (7/7) Sep 12 2007 I was trying to simplifly. Sigh. The gist of this particular example
- Walter Bright (2/8) Sep 12 2007 Or you can cast away const-ness. But you are on your own if you do that.
- James Dennett (6/15) Sep 12 2007 Well, in C++ you'd be fine, but in D I thought the behavior
- Walter Bright (3/10) Sep 13 2007 It's also undefined behavior if you cast a pointer to an int. You need
- James Dennett (12/24) Sep 13 2007 Right. But you've snipped too much context. The point was that
- Walter Bright (5/8) Sep 12 2007 It doesn't re-enable any benefits, as all the usual synchronization,
- Bruno Medeiros (20/54) Sep 12 2007 In the interest of this discussion, here's a very nice article regarding...
- Janice Caron (5/8) Sep 11 2007 These days, of course, the data source might not even be a file - it mig...
- Janice Caron (21/21) Sep 11 2007 Content-Disposition: inline
- Steven Schveighoffer (7/12) Sep 11 2007 I sort of agree with you, but what about instances where you want derive...
- Janice Caron (60/74) Sep 11 2007 I believe I can make the case for my argument stronger. In general,
- Steven Schveighoffer (33/76) Sep 11 2007 Yeah, I understand your original point. I'm not disputing that, which i...
- Janice Caron (4/16) Sep 11 2007 No it won't, because it won't compile, because x is private.
- Janice Caron (1/2) Sep 11 2007 Sorry - accept my apologies - I misread your code.
- Christopher Wright (3/14) Sep 11 2007 Provide a property to access the cache. It'll likely be inlined, so no
- Bruno Medeiros (6/31) Sep 11 2007 And why is this nonsense?? Because x is public? I don't see the problem
- Janice Caron (4/6) Sep 11 2007 What's the use-case for mutable non-private members?
- Bruno Medeiros (6/15) Sep 12 2007 I can't name a use case right now, but I do think significant use-cases
- Bruce Adams (10/31) Sep 12 2007 Since you mention contracts, one idea I had for a way out of this
- =?ISO-8859-1?Q?Carsten_S=F8rensen?= (20/28) Sep 12 2007 Hello, world! I'm fairly new to the world of D, but a minor detail like
- Bruce Adams (3/37) Sep 12 2007 Someone correct me if I'm wrong but I thought D doesn't / wasn't going t...
- =?ISO-8859-1?Q?Carsten_S=F8rensen?= (5/6) Sep 12 2007 Sorry if I wasn't totally clear. I'm not talking about overloading, but
- Bruce Adams (3/12) Sep 12 2007 You were totally clear but using a C++ concept of const methods which I ...
- Nathan Reed (8/11) Sep 12 2007 I believe the issue of overloading opIndex and similiar is going to be
- Alex Burton (12/34) Sep 12 2007 It is far more than a comment. This class may be passed all through an a...
- Christopher Wright (18/28) Sep 13 2007 Right! And the thread safety and optimization stuff is encapsulated in a...
- Sean Kelly (4/14) Sep 13 2007 A third option for thread safety would be if the function mutated only
- Alex Burton (4/15) Sep 13 2007 James point is still valid.
- Mike Capp (4/8) Sep 13 2007 You'll hit undefined behavior in C++ as well if the data being modified ...
- James Dennett (10/23) Sep 13 2007 Something of a mantra around here! However, that's certainly
While const is being reexamined.... I create an interface : interface Server { Data getData() const; }; I then write half my application using a stub implementation of Server, which is refered to using const references, because this application doesn't modify the server (the interface has no non const members). Then the time comes to implement the Server properly using a socket to communicate with a server. interface Socket { void send(const Data d); Data receive() const; }; class ServerImplementation : Server { Socket sock; Data getData() const { sock.send(request); return sock.receive(); } }; My application doesn't compile because I am refering to using const Server references every where, and it turns out the server class needs to modify it's socket to implement the getData method. This is wrong. The socket is not part of the ServerImplementation, it's just that D can't tell the difference between things that are part of a class and things that are not. I now have to make the getData method non const and all the references to Server non const, and possibly not use any const references in my application again, as similar examples can be constructed for all sorts of things, and basically the const system does not work. I think it's great to search for a better const system that c++, but if you have a great system for allowing compiler to optimise, but the programmer can't actually use the system to describe reality then it's not better at all. The solution is to be able to say whether the socket is part of the server or not, then the const system can be made to work, as it did in c++. I am not saying that c++ const is great but at least I can use it to describe reality (although it's cumbersome, and there are a lot of reasons why I am itching to convert all my stuff to D). Sincerely, Alex
Sep 11 2007
Alex Burton wrote:While const is being reexamined.... I create an interface : interface Server { Data getData() const; }; I then write half my application using a stub implementation of Server, which is refered to using const references, because this application doesn't modify the server (the interface has no non const members). Then the time comes to implement the Server properly using a socket to communicate with a server. interface Socket { void send(const Data d); Data receive() const; }; class ServerImplementation : Server { Socket sock; Data getData() const { sock.send(request); return sock.receive(); } }; My application doesn't compile because I am refering to using const Server references every where, and it turns out the server class needs to modify it's socket to implement the getData method. This is wrong. The socket is not part of the ServerImplementation, it's just that D can't tell the difference between things that are part of a class and things that are not. I now have to make the getData method non const and all the references to Server non const, and possibly not use any const references in my application again, as similar examples can be constructed for all sorts of things, and basically the const system does not work. I think it's great to search for a better const system that c++, but if you have a great system for allowing compiler to optimise, but the programmer can't actually use the system to describe reality then it's not better at all. The solution is to be able to say whether the socket is part of the server or not, then the const system can be made to work, as it did in c++. I am not saying that c++ const is great but at least I can use it to describe reality (although it's cumbersome, and there are a lot of reasons why I am itching to convert all my stuff to D). Sincerely, AlexI remember your example case from your earlier thread. :) So, correct me if I'm wrong. You want to be able to specify a reference which itself cannot change (perhaps a requirement, perhaps not?), but refers to an object which can. And you want this reference to be usable inside a const method. For the first I would suggest a new syntax like: class Foo { int a; } const(Foo&) pFoo; //pFoo cannot change, pFoo.a can This is analogous to my other idea[1]. For the second, either: 1. the compiler should assume that as the author used const on this reference, and intentionally applied it only to the reference itself, that changes made _via_ this reference are not covered by the const method contract. 2. we need a new keyword to indicate which references/pointers to mutable data can be used in a const method. i.e. "mutable" in either case you would then be able to make changes to the referenced data from within a const method. [1]See my recent posts to the "Const sucks" thread for the idea that: class Foo { int a; } const(Foo*) pFoo; //pFoo can change, pFoo.a cannot would declare a tail const class reference. Regan
Sep 11 2007
Regan Heath wrote:For the first I would suggest a new syntax like: class Foo { int a; } const(Foo&) pFoo; //pFoo cannot change, pFoo.a canActually, correction, this should probably be: const(&Foo) pFoo; //pFoo cannot change, pFoo.a can The position of & in this case is important becase we don't want it confused with reference types but rather we're saying: "the reference/pointer/address of this is const"
Sep 11 2007
Regan Heath Wrote:I remember your example case from your earlier thread. :) So, correct me if I'm wrong. You want to be able to specify a reference which itself cannot change (perhaps a requirement, perhaps not?), but refers to an object which can. And you want this reference to be usable inside a const method. For the first I would suggest a new syntax like: class Foo { int a; } const(Foo&) pFoo; //pFoo cannot change, pFoo.a can This is analogous to my other idea[1]. For the second, either: 1. the compiler should assume that as the author used const on this reference, and intentionally applied it only to the reference itself, that changes made _via_ this reference are not covered by the const method contract. 2. we need a new keyword to indicate which references/pointers to mutable data can be used in a const method. i.e. "mutable" in either case you would then be able to make changes to the referenced data from within a const method.Yes essentially we need to be able to distinguish between mutable references and non mutable references. The latter being logically part of the class. Note I would not suggest a mutable keyword that could be applied to class members that are structs or ints, like you can use in c++, as this is clearly bad for optimisation and is logically wrong. But what I do want is essentially an ability to mark class references as mutable. On the one hand I totally empathise with being able to write 'Socket sock;' for members that are classes possibly polymorphic. Instead of in c++ aggregate_ptr<Socket> sock, or scoped_ptr<Socket> sock or shared_ptr<Socket> sock; Most of the needs of having these different pointer types was to do with memory management, which has been eliminated in D. But there was also information on aggregation (part of relationships) in there. The mutableness is just a property of the member not being part of the class, so what we really need is something like: class Whole { Part p; not_a_part_keyword NotAPart np; };
Sep 11 2007
Content-Disposition: inline Your problem generalises to anything "streamy" Going way back to the old C way of doing things FILE * f; fprintf(f, fmt, stuff); You'd expect that to work, even if you copied from from inside a const struct, right? But then the type of f would have to change to const FILE * f; which is C-speak for "f is a const pointer to mutable FILE". Now throwing in transitivity would stop it all working. Moving forward in time to the modern era of objects, in general, you would want: stream.write(x); to work even if the variable "stream" was const. (That is, if the /reference/ was const, not the data which is pointed to by the reference). The stack variable can be const, but the heap data needs to be mutable. I don't have a solution, except to agree that "head const" does seem to be required after all.
Sep 11 2007
Janice Caron Wrote:Your problem generalises to anything "streamy"It then further generalised to anything "state machiney" :) State machines are essential for computers to work. The processor itself is a state machine. The stack has state. If I was to implement a processor emulator in D I would have the same problem. The idea that you can just 'get' a value from memory without modifing anything is an illusion created by the computer. Alex
Sep 11 2007
Content-Disposition: inline On 9/11/07, Alex Burton <alexibu mac.com> wrote:The idea that you can just 'get' a value from memory without modifing anything is an illusion created by the computer.Yes, it's even more obvious with a read than with a write. Another interesting use-case involves caching. I'll spare you the boring details, but the essence is, I once had a class member function that did a lookup, much like an associative array, and the prototype would have been something like int lookup(int key) const; This was C++, so the const was at the end. The thing is, all the data was in a file, and so the function had to open a file, read it, close the file, and return the data. Well, naturally, it cached some data internally to avoid too many file accesses. And - also naturally - I did not load the entire file into memory at constructor time because huge parts of it might not be needed at all. The file access was deferred until it was needed, and cached appropriately. So the function was declared const, because /conceptually/ it was. But the class had a mutable cache, declared with the C++ keyword "mutable" Transitivity would wreck that.
Sep 11 2007
Janice Caron wrote:On 9/11/07, *Alex Burton* <alexibu mac.com <mailto:alexibu mac.com>> wrote: The idea that you can just 'get' a value from memory without modifing anything is an illusion created by the computer. Yes, it's even more obvious with a read than with a write. Another interesting use-case involves caching. I'll spare you the boring details, but the essence is, I once had a class member function that did a lookup, much like an associative array, and the prototype would have been something like int lookup(int key) const; This was C++, so the const was at the end. The thing is, all the data was in a file, and so the function had to open a file, read it, close the file, and return the data. Well, naturally, it cached some data internally to avoid too many file accesses. And - also naturally - I did not load the entire file into memory at constructor time because huge parts of it might not be needed at all. The file access was deferred until it was needed, and cached appropriately. So the function was declared const, because /conceptually/ it was. But the class had a mutable cache, declared with the C++ keyword "mutable" Transitivity would wreck that.My classic example for the need of "mutable" is a mutex used to synchronize access to class data. The mutex must be modified even for read operations. But for better or worse, D const has a different aim. The goal is largely to produce a system which allows for compiler optimization rather than to enforce some sort of logical restrictions on behavior. I suspect this means that D apps won't look very much like C++ apps in terms of how const is used, and the overall utility for the average programmer may well be somewhat small. Sean
Sep 11 2007
Content-Disposition: inline On 9/11/07, Sean Kelly <sean f4.ca> wrote:My classic example for the need of "mutable" is a mutex used to synchronize access to class data. The mutex must be modified even for read operations.Again, if we let D's "private" attribute also imply C++'s "mutable", then that problem goes away.
Sep 11 2007
Sean Kelly wrote:read operations. But for better or worse, D const has a different aim. The goal is largely to produce a system which allows for compiler optimization rather than to enforce some sort of logical restrictions on behavior. I suspect this means that D apps won't look very much like C++ apps in terms of how const is used, and the overall utility for the average programmer may well be somewhat small. SeanI'm don't think that's entirely true. 'invariant' the keyword is indeed made to allow several compiler and program optimizations, but 'const' the keyword is really for enforcing program contracts and restrictions, thus improving safety. I don't even think 'const' the keyword has any use whatsoever for optimization. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Sep 11 2007
Bruno Medeiros wrote:Sean Kelly wrote:True enough. But the current design (ie. transitive and lacking 'mutable') is such that 'const' may have limited utility for UDTs where logical state is not equivalent to physical state. I still think this is fine, as I'd prefer something simple and understandable, but I wonder whether this will be sufficient for enterprise programmers looking to switch from C++ to D. Only time will tell, I suppose. Seanread operations. But for better or worse, D const has a different aim. The goal is largely to produce a system which allows for compiler optimization rather than to enforce some sort of logical restrictions on behavior. I suspect this means that D apps won't look very much like C++ apps in terms of how const is used, and the overall utility for the average programmer may well be somewhat small.I'm don't think that's entirely true. 'invariant' the keyword is indeed made to allow several compiler and program optimizations, but 'const' the keyword is really for enforcing program contracts and restrictions, thus improving safety. I don't even think 'const' the keyword has any use whatsoever for optimization.
Sep 11 2007
Sean Kelly wrote:But for better or worse, D const has a different aim. The goal is largely to produce a system which allows for compiler optimization rather than to enforce some sort of logical restrictions on behavior.This is not the primary goal (it is a side effect of the primary goals). The goals are: 1) Make functional style programming possible, which will become extremely important as people will start using all those lovely cores. 2) Provide compiler enforced semantic guarantees, which improves the specification of interfaces between disparate parts of code. 3) Be able to treat invariant references as value types. This, for example, makes manipulating strings as easy as manipulating ints. 4) Makes COW programming checkable and enforceable. (COW programming is an important part of many styles of programming.) Without transitive const, COW is overly reliant on programmer discipline.I suspect this means that D apps won't look very much like C++ apps in terms of how const is used, and the overall utility for the average programmer may well be somewhat small.Yes, it will be used differently. Whether it is overall better or not only experience will tell.
Sep 12 2007
"Walter Bright" <newshound1 digitalmars.com> wrote in message news:fc837t$29ni$1 digitalmars.com...Sean Kelly wrote:How does making const transitive allow for functional programming? From what I understand (and that's not much) about functional programming, it's programming without side effects. Consider: int x; class X { const int void f() { x += 5; return x; } } int main() { const X x1 = new X; const X x2 = new X; int y = x1.f() * x2.f(); } Although X.f() is const, it has modified global data, so X.f() has side effects. Yet both x1 and x2 are transitive-const, are they not? Therefore, the compiler STILL cannot make any optimizations based on the fact that x1 and x2 are const. I think in order to allow functional programming, you need to introduce a new type of const, for example fconst. If a function is declared fconst, it is not allowed to change any data that is not local to the function, or call a non-fconst function, or to read non-fconst data. If a piece of data is declared fconst, it cannot be changed. I don't think you can make this restriction with const in general as I think it will severely limit current programming styles (not everyone wants to use functional programming, or else we'd all be using scheme).But for better or worse, D const has a different aim. The goal is largely to produce a system which allows for compiler optimization rather than to enforce some sort of logical restrictions on behavior.This is not the primary goal (it is a side effect of the primary goals). The goals are: 1) Make functional style programming possible, which will become extremely important as people will start using all those lovely cores.2) Provide compiler enforced semantic guarantees, which improves the specification of interfaces between disparate parts of code.As I showed above, this is not possible with simply transitive const.3) Be able to treat invariant references as value types. This, for example, makes manipulating strings as easy as manipulating ints.I don't understand this, perhaps someone can explain it further. An example of how this helps would be good.4) Makes COW programming checkable and enforceable. (COW programming is an important part of many styles of programming.) Without transitive const, COW is overly reliant on programmer discipline.All I can find on the net about COW programming is the COW programming language. I'm sure this isn't what you meant :) can you please explain this further? -Steve
Sep 12 2007
All I can find on the net about COW programming is the COW programming language. I'm sure this isn't what you meant :) can you please explain this further?I suspect he meant "copy on write", but forget to tell us. :-)
Sep 12 2007
Steven Schveighoffer wrote:class X { const int void f() { x += 5; return x; } }"const int void f()" is clearly a bad declaration. Anyway, "const int f()" is a function returning a const int, not a const function returning an int, so you wouldn't be able to call it through a const reference. - Gregor Richards
Sep 12 2007
"Gregor Richards" wroteSteven Schveighoffer wrote:Um... yeah, I meant to say f() is const. Sorry my example code didn't compile for you :P -Steveclass X { const int void f() { x += 5; return x; } }"const int void f()" is clearly a bad declaration. Anyway, "const int f()" is a function returning a const int, not a const function returning an int, so you wouldn't be able to call it through a const reference. - Gregor Richards
Sep 12 2007
"const int void f()" is clearly a bad declaration. Anyway, "const int f()" is a function returning a const int, not a const function returning an int, so you wouldn't be able to call it through a const reference.See thread "properties and const" The D syntax for "member function which does not modify member variables" is sort of a bit unclear. In C++ I would have written "int void f() const", but that's not how it goes in D.
Sep 12 2007
I suspect he meant "copy on write", but forget to tell us. :-)Here's an example of copy-on-write: MyString s = "cat"; MyString t = s; /* Only a reference is copied. Now s and t both reference the same data */ writefln(t); /* prints "cat" */ /*drum roll*/ t[0] = 'b'; /* Only now is a copy made. Now t is unique, with s=="cat" and t="bat" */ writefln(s); /* prints "cat" */ writefln(t); /* prints "bat" */ Of course, to implement this - to actually create a class that did this, you'd need MyString to have some internal state (and therefore, either non-transitive const, or logical const).
Sep 12 2007
Janice Caron Wrote:If you are living in the wonderful world of unix. Copy on write automagically happens for you if you change a value, following a process fork, courtesy of the OS. That's not to say it not a useful technique outside of forking unix processes but the point is that it can happen without any kind of const thrown into the mix. I suspect the restrict keyword interacts with this in interesting ways. Fortunately I've never had to try it myself. Bruce.I suspect he meant "copy on write", but forget to tell us. :-)Here's an example of copy-on-write: MyString s = "cat"; MyString t = s; /* Only a reference is copied. Now s and t both reference the same data */ writefln(t); /* prints "cat" */ /*drum roll*/ t[0] = 'b'; /* Only now is a copy made. Now t is unique, with s=="cat" and t="bat" */ writefln(s); /* prints "cat" */ writefln(t); /* prints "bat" */ Of course, to implement this - to actually create a class that did this, you'd need MyString to have some internal state (and therefore, either non-transitive const, or logical const).
Sep 12 2007
Steven Schveighoffer wrote:"Walter Bright" <newshound1 digitalmars.com> wrote in messageAlthough const is necessary for f.p., it is not sufficient for f.p., as your example illustrates.1) Make functional style programming possible, which will become extremely important as people will start using all those lovely cores.How does making const transitive allow for functional programming? From what I understand (and that's not much) about functional programming, it's programming without side effects. Consider:I think in order to allow functional programming, you need to introduce a new type of const, for example fconst. If a function is declared fconst, it is not allowed to change any data that is not local to the function, or call a non-fconst function, or to read non-fconst data. If a piece of data is declared fconst, it cannot be changed.The thought is to declare a function as 'pure'.Strings in, say, Perl or Basic "just work". Just like integers "just work" in C. Nobody ever gives a thought to them inadvertently changing value. But strings in C, everybody worries about them changing value, and such is a major source of bugs.3) Be able to treat invariant references as value types. This, for example, makes manipulating strings as easy as manipulating ints.I don't understand this, perhaps someone can explain it further. An example of how this helps would be good.copy-on-write4) Makes COW programming checkable and enforceable. (COW programming is an important part of many styles of programming.) Without transitive const, COW is overly reliant on programmer discipline.All I can find on the net about COW programming is the COW programming language. I'm sure this isn't what you meant :) can you please explain this further?
Sep 12 2007
"Walter Bright" wroteSteven Schveighoffer wrote:Although const is necessary for f.p., it is not sufficient for f.p., as your example illustrates.The thought is to declare a function as 'pure'.OK, but why not allow BOTH logical const and transitive const? What if const exists as it does, except the mutable keyword overrides it? As long as pure functions can only call other pure functions and use const basic types, then isn't f.p. still possible? You could even use const types that have mutable parts as long as you are only calling pure functions on those types (as those wouldn't be able to access the mutable piece).I understand why it is good to have strings immutable, as I've used Java quite a bit. But how does transitive const guarantee that a string's value won't change? Don't you just need immutable strings? What I was looking for is: example = ??? without transitive const: life sucks in this example because you can't do ... with transitive const: life is better because now I can do ...Strings in, say, Perl or Basic "just work". Just like integers "just work" in C. Nobody ever gives a thought to them inadvertently changing value. But strings in C, everybody worries about them changing value, and such is a major source of bugs.3) Be able to treat invariant references as value types. This, for example, makes manipulating strings as easy as manipulating ints.I don't understand this, perhaps someone can explain it further. An example of how this helps would be good.ok, so you are saying that with transitive const, this is enforceable, by the compiler I'm assuming. Give me an example of how having transitive const makes COW enforceable by the compiler and doesn't rely on the programmer. I understand much better with examples rather than abstract arguments if you notice :) -Stevecopy-on-write4) Makes COW programming checkable and enforceable. (COW programming is an important part of many styles of programming.) Without transitive const, COW is overly reliant on programmer discipline.All I can find on the net about COW programming is the COW programming language. I'm sure this isn't what you meant :) can you please explain this further?
Sep 12 2007
Walter Bright wrote:Sean Kelly wrote:This was perhaps too strong a statement on my part. Given the design of const, I'm beginning to suspect that while the syntax of D is fairly similar to C++, I think the structure of a D app five years from now may well look very little like that of a C++ app. The recent D features all lend themselves to a more functional style of programming. This is probably a good thing in terms of scaling towards the future, but it could be surprising for someone who thinks D is C merged with Java :-) SeanI suspect this means that D apps won't look very much like C++ apps in terms of how const is used, and the overall utility for the average programmer may well be somewhat small.Yes, it will be used differently. Whether it is overall better or not only experience will tell.
Sep 12 2007
Sean Kelly wrote:This was perhaps too strong a statement on my part. Given the design of const, I'm beginning to suspect that while the syntax of D is fairly similar to C++, I think the structure of a D app five years from now may well look very little like that of a C++ app. The recent D features all lend themselves to a more functional style of programming. This is probably a good thing in terms of scaling towards the future, but it could be surprising for someone who thinks D is C merged with Java :-)Interestingly, the look of C++ apps have changed *substantially* over time as new philosophies of how to use it have evolved. One could note that I'm a bit stuck in the past, as my C++ code (the D front end) is definitely an old style of C++. It has very little in common with the modern style (see Boost for that).
Sep 12 2007
Janice Caron wrote:So the function was declared const, because /conceptually/ it was. But the class had a mutable cache, declared with the C++ keyword "mutable" Transitivity would wreck that.You're right in that transitive const does not support the notion of "logical const" (which is the usual term for what you are referring to). The problem with logical const, however, is it offers no semantic guarantees. I pass a logical const reference around, and the underlying data may or may not change. I have no guarantees one way or the other, and even worse, I can't even tell this is happening. "I" here meaning the compiler, and our hapless code auditor. This just pulls the rug out from under: 1) functional programming 2) multithreaded programming 3) having a tightly specified interface It goes back to painting a stripe across your hips and calling it a seatbelt. Given this, it isn't any surprise that C++ is disastrously difficult to write functional & multithreaded programs in, and C++ mutability is one of the reasons why. In C++, you can write const this and const that and it doesn't mean jack squat. Many experienced C++ programmers will tell you that const is little more than a documentation aid. In other words, you're quite right that transitive const totally wrecks using const to specify logical constness. And that's a good thing <g>.
Sep 11 2007
Content-Disposition: inline On 9/12/07, Walter Bright <newshound1 digitalmars.com> wrote:Janice Caron wrote:But you could guarantee that /non-private/ data will not change. And since non-private data is never seen outside the file in which it is declared, I don't understand why this is a problem. and even worse, I can't even tell this is happening. "I" here meaningSo the function was declared const, because /conceptually/ it was. But the class had a mutable cache, declared with the C++ keyword"mutable"Transitivity would wreck that.You're right in that transitive const does not support the notion of "logical const" (which is the usual term for what you are referring to). The problem with logical const, however, is it offers no semantic guarantees. I pass a logical const reference around, and the underlying data may or may not change. I have no guarantees one way or the other,the compiler, and our hapless code auditor.Providing you restrict "logical constness" to private variables, is that really true? Inside the module, the compiler knows everything. Outside the module, the private variables are irrelevant anyway as they can never be accessed in any way. This just pulls the rug out from under:1) functional programmingFunctional programming isn't possible anyway unless the function can also guarantee that it won't modify /global/ variables, and I see no way of specifying that in the prototype. 2) multithreaded programming Likewise, a function cannot be guaranteed to be completely threadsafe unless it guarantees not to modify global variables, and I see no way of specifying that in the prototype. Even if you could, it's more complicated than that. A function might modify global variables and yet still be threadsafe - providing it uses mutexes to ensure it has exclusive access to the shared variables. A good example of this is writef(). The writef() function writes to standard output, and so /must/ modify some global state - but it's thread-safe because file access is all mutex locked. Moreover, multithreaded programming might /require/ you to lock a mutex, do something, then unlock said mutex. The mutex itself needs to be modifiable - that is, "logically const". More thought needs to be put into that one. 3) having a tightly specified interface Interfaces are my concern. If an Interface specifies that a function be const (in the sense of non modifying member variables), then it would make a lot of sense that classes which implement that interface be allowed to assume the interface means "logical constness", for the reasons of all the use-cases given so far. It goes back to painting a stripe across your hips and calling it aseatbelt. Given this, it isn't any surprise that C++ is disastrously difficult to write functional & multithreaded programs in, and C++ mutability is one of the reasons why. In C++, you can write const this and const that and it doesn't mean jack squat. Many experienced C++ programmers will tell you that const is little more than a documentation aid.I /am/ an experienced C++ programmer, and I agree. However, in C++, it is possible to declare non-private members mutable - and to make matters worse, the function bodies of functions which modify that variable could be in any number of different source files scattered all over the place. In D it's different. First off, I do suggest restricting "logical constness" to private variables only. Secondly, all the function defintions which can access those source files are in one file, so the compiler knows everything. This means that, in D, when you declare a function as const, there would still be an absolute guarantee that nothing non-private could ever be changed by that function. Outside the module, that's all you need to know. Inside the module, the compiler knows all anyway. In other words, you're quite right that transitive const totally wrecksusing const to specify logical constness. And that's a good thing <g>.But logical constness is a /necessary/ thing, and if you outlaw any way of doing it legitimately, then people will do it by "cheating". By (for example), storing state in global variables. The simplest example I can come up with is that random number one. Recall: class RandomNumberGenerator; { private long seed; const int rand() /* guarantees not to modify any non-private member variables */ { seed = f(seed); return (seed >> 32); } } So then I'd have to change it to something like: private long seed; private long getSeed() { lockMutex(); long s = seed; unlockMutex(); return s; } class RandomNumberGenerator; { const int rand() { seed = f(getSeed()); return (seed >> 32); } } And I'd end up with something that was much nastier than the very thing you are trying to avoid.
Sep 12 2007
Janice Caron Wrote: [snip]This just pulls the rug out from under:You obviously missed the discussion of the "pure" keyword, borrowed from Fortran-90 from the conference. This is a clear way of declaring that a function must not have any side-effects. Something I've long hoped to see in C++ too. http://s3.amazonaws.com/dconf2007/WalterAndrei.pdf Regards, Bruce.1) functional programmingFunctional programming isn't possible anyway unless the function can also guarantee that it won't modify /global/ variables, and I see no way of specifying that in the prototype. 2) multithreaded programming Likewise, a function cannot be guaranteed to be completely threadsafe unless it guarantees not to modify global variables, and I see no way of specifying that in the prototype.
Sep 12 2007
You obviously missed the discussion of the "pure" keyword, borrowed from Fortran-90 from the conference. This is a clear way of declaring that a function must not have any side-effects.Cool! But then, I don't understand Walter's objections to "logical constness". Seems pretty simple to me. If a function is logically const but not truly const, then don't declare it pure. Conversely, if it's not declared pure, then it can have logical constness. Isn't that problem solved?
Sep 12 2007
Janice Caron Wrote:Getting constness and purity mixed up is liable to open a whole new can of worms. I humbly suggest you don't go there. I not sure whetherpure functions can modify class variables. The presentation states that they can't modify anything reachable through their arguments. If we interpret "this" as a argument then pure methods must also be const in the C++ sense.You obviously missed the discussion of the "pure" keyword, borrowed from Fortran-90 from the conference. This is a clear way of declaring that a function must not have any side-effects.Cool! But then, I don't understand Walter's objections to "logical constness". Seems pretty simple to me. If a function is logically const but not truly const, then don't declare it pure. Conversely, if it's not declared pure, then it can have logical constness. Isn't that problem solved?
Sep 12 2007
Bruce Adams wrote:I not sure whether pure functions can modify class variables.I hope not!The presentation states that they can't modify anything reachable through their arguments.They are like mappings (e.g y = f(x) ) in math. If you write sth. like this: b = foo; for(i = 0; i < rand_range(1,1000); i++) { a = pure_func(b); } writefln("%s", a); b will still contain foo and a will always have the same value in every program run. pure_func will only be called once here. This is the possible compiler optimisation, if pure_func() is a pure function. Best Regards Ingo Oeser
Sep 12 2007
Janice Caron wrote:But then, I don't understand Walter's objections to "logical constness". Seems pretty simple to me. If a function is logically const but not truly const, then don't declare it pure. Conversely, if it's not declared pure, then it can have logical constness. Isn't that problem solved?No. Pass a reference to a const through a non-pure function to a pure one.
Sep 12 2007
BTW, Janice, I appreciate your thoughts on this. You're obviously an experienced programmer. Janice Caron wrote:But you could guarantee that /non-private/ data will not change. And since non-private data is never seen outside the file in which it is declared, I don't understand why this is a problem.Because multithreaded programming doesn't start working if the interface hides the changing variables. If it did, parallel programming would be easy. Logical constness is *still* changing state, and since it is, all the boogeymen of synchronization, race conditions, deadlocks, etc., are all there. Logical constness hides the state change from the awareness of the programmer, but it's still changing state.and even worse, I can't even tell this is happening. "I" here meaning the compiler, and our hapless code auditor. Providing you restrict "logical constness" to private variables, is that really true? Inside the module, the compiler knows everything. Outside the module, the private variables are irrelevant anyway as they can never be accessed in any way.Being in a module doesn't prevent another thread from using the module's interface and thereby changing the state.Functional programming isn't possible anyway unless the function can also guarantee that it won't modify /global/ variables, and I see no way of specifying that in the prototype.The upcoming 'pure' storage class for a function will resolve that.Even if you could, it's more complicated than that. A function might modify global variables and yet still be threadsafe - providing it uses mutexes to ensure it has exclusive access to the shared variables. A good example of this is writef(). The writef() function writes to standard output, and so /must/ modify some global state - but it's thread-safe because file access is all mutex locked.Yes, but that requires programmer discipline, which is unreliable and scales poorly. Very few programmers are able to successfully write such code. With FP programming, most programmers will be able to.Moreover, multithreaded programming might /require/ you to lock a mutex, do something, then unlock said mutex. The mutex itself needs to be modifiable - that is, "logically const". More thought needs to be put into that one.It has come up before. The answer is to not declare logically const things as being const, because they aren't const. Logical constness belongs as a comment because it is not compiler checkable.3) having a tightly specified interface Interfaces are my concern. If an Interface specifies that a function be const (in the sense of non modifying member variables), then it would make a lot of sense that classes which implement that interface be allowed to assume the interface means "logical constness", for the reasons of all the use-cases given so far.I'll reiterate that const-that-isn't-checkably-constant has no value beyond being a comment, so it might as well just be a comment.It goes back to painting a stripe across your hips and calling it a seatbelt. Given this, it isn't any surprise that C++ is disastrously difficult to write functional & multithreaded programs in, and C++ mutability is one of the reasons why. In C++, you can write const this and const that and it doesn't mean jack squat. Many experienced C++ programmers will tell you that const is little more than a documentation aid. I /am/ an experienced C++ programmer,I can tell <g>. The logical constness pattern comes from C++.Outside the module, that's all you need to know. Inside the module, the compiler knows all anyway.The D compiler does not necessarily know all there is to know about a type: 1) If you're writing generic code, with things like const(T), you don't know what T is. Therefore, as a programmer, you will not be able to write generically pure code. 2) D allows for so-called abstract types, where the compiler does not know the internals of the type. This is used in the Phobos to hide implementation details. Therefore, the compiler cannot tell if a const T has mutability or not.And I'd end up with something that was much nastier than the very thing you are trying to avoid.The error I see in the example is declaring rand() to be const. Why do that if it IS NOT constant? You can already hide members the user shouldn't be modifying with 'private', why layer 'const' over that, too?
Sep 12 2007
On 9/12/07, Janice Caron <caron800 googlemail.com> wrote:See, I was basically arguing with you,That's "with", as in, "on the same side as", not "arguing" as in "disagreeing". Guess I could have worded that better! Ahem. I'll try again... See, I was basically agreeing with you and supporting your claim...that transitivity is a good thingThe rest of the post should now make sense!-- /provided/ we can have mutable members. Now, if we can't have that, then suddenly transitivity no longer seems so welcoming. If const-transitivity becomes a strait-jacket, then folk are going to end up just not declaring anything const at all, because they can't get their code to compile otherwise.
Sep 12 2007
On 9/12/07, Walter Bright <newshound1 digitalmars.com> wrote:Because multithreaded programming doesn't start working if the interface hides the changing variables. If it did, parallel programming would be easy. Logical constness is *still* changing state, and since it is, all the boogeymen of synchronization, race conditions, deadlocks, etc., are all there.I've written a lot of multithreaded programming. A lot. ...which is perhaps a bad thing, because sometimes I forget that D does things differently. I don't need to use a separate mutex class if you've got synchronized. (By the way - could you also allow the spelling "synchronised" for the benefit of speakers of British English?) :-) synchronised is a blunt tool though. In my C++ code, I arrange it so that multiple threads can simultaneously obtain read-access, or exactly one thread can obtain write-access. Correct me if I'm wrong, but the built-in synchronized feature isn't that smart? So anyway, as I mentioned further up this thread, I had this loopup class member function that got stuff from a file and cached it. I assure you it was thread-safe. It even allowed multiple readers (if the key/value pair was already cached), but if a lookup was not cached, one thread and one thread only got to open the file, read a chunk of data, and stick it in the file. This was a class that knew what it was doing. You're probably going to tell me that I can still do all that in D, providing I don't declare it const. The problem is, if I don't declare it const, /and/ all consts in D are transitive, then I can't store a reference to that object in any const structure. Basically I'd end up declaring just about nothing const, and I'd see no benefit.Logical constness hides the state change from the awareness of the programmer, but it's still changing state.By definition, yes.Being in a module doesn't prevent another thread from using the module's interface and thereby changing the state.Is that why you object? I would have thought it the programmer's problem to ensure thread safety, not the compiler's. If I have a variable that's going to be accessed by multiple threads, then I'm going to make sure it's properly synchronised, and I if screw up, then it's my bug.Yes, but that requires programmer discipline, which is unreliable and scales poorly.I think it could scale very well with the right language support. But that's another subject.Very few programmers are able to successfully write such code. With FP programming, most programmers will be able to.That's obviously true. I'm not sure why it's relevant though. You said you had a "pure" keyword in mind for that. But if I don't use the pure keyword, then thread safety should be down to me, right?It has come up before. The answer is to not declare logically const things as being const, because they aren't const. Logical constness belongs as a comment because it is not compiler checkable.But you might want to declare logically const things as being const for efficiency reasons. And to argue that "they aren't const" is a matter of definition. "const" in C++ is /defined/ to mean logical const, so const they are! You use the word to mean "physically const". We don't all agree on the definition. Suppose there exists a class String with member function uppercase(), declared const (or invariant, as you would have it in D). So far, so good. But then, along comes a better (or at least, different) string class with the same interface, and it happens to be faster, and I want to use the new FastString class in place of the old String class. All I have to do is search and replace, right? Or just make an alias. But there's a problem. Turns out, the reason it's faster is because it caches some results internally so it doesn't have to keep recomputing them. From the point of view of "implementation should be independent of interface" this internal detail is something I shouldn't need to know or care about. But now suddenly, I do need to know about it. And care. Because now that uppercase() function I mentioned earlier is no longer physically const. That means, if I use it as a drop-in replacement, my code now won't compile.I'll reiterate that const-that-isn't-checkably-constant has no value beyond being a comment, so it might as well just be a comment.Oh contraire. It /is/ meaningful. It is a cast iron guarantee that no non mutable (which I argue should imply non-private) variables will be modified. Without the const declaration, you do not have that guarantee. It's a guarantee *which the compiler can use* to help the programmer catch errors! For example, this C++ class C { mutable int x; int y; void f() const; { x = 1; y = 2; /* The compiler catches this error */ } } See - this is useful to me. If I had not declared f const (which is basically what you're suggesting), then the compiler would have been no help to me whatsoever. It would not have flagged y=2 as an error. I /want/ the compiler to assist me by flagging errors like this. I want it to be a compile-time error for f to modify y. But as soon as remove the words "const" and "mutable" from that example, I lose that compiler-assistance.The D compiler does not necessarily know all there is to know about a type:OK. Good point. I hadn't thought that one through. The difficulty is, if you don't allow mutable member variables, then we're back to what started this thread - transitivity sucks. See, I was basically arguing with you, that transitivity is a good thing -- /provided/ we can have mutable members. Now, if we can't have that, then suddenly transitivity no longer seems so welcoming. If const-transitivity becomes a strait-jacket, then folk are going to end up just not declaring anything const at all, because they can't get their code to compile otherwise.
Sep 12 2007
Janice Caron wrote:On 9/12/07, Walter Bright <newshound1 digitalmars.com> wrote:In general that's true, though you might want a separate mutex class for specialized needs: inter-process synchronization, etc.Because multithreaded programming doesn't start working if the interface hides the changing variables. If it did, parallel programming would be easy. Logical constness is *still* changing state, and since it is, all the boogeymen of synchronization, race conditions, deadlocks, etc., are all there.I've written a lot of multithreaded programming. A lot. ...which is perhaps a bad thing, because sometimes I forget that D does things differently. I don't need to use a separate mutex class if you've got synchronized.(By the way - could you also allow the spelling "synchronised" for the benefit of speakers of British English?) :-) synchronised is a blunt tool though. In my C++ code, I arrange it so that multiple threads can simultaneously obtain read-access, or exactly one thread can obtain write-access. Correct me if I'm wrong, but the built-in synchronized feature isn't that smart?Yes and no :-) The default implementation isn't that smart, but it can be extended. Tango has a ReadWriteMutex that works like so: auto readWriteLock = new ReadWriteMutex; synchronized( readWriteLock.reader ) { // any number of threads can be here simultaneously } synchronized( readWriteLock.writer ) { // only one thread may enter this block simultaneously // and no threads may be in the reader block above either }So anyway, as I mentioned further up this thread, I had this loopup class member function that got stuff from a file and cached it. I assure you it was thread-safe. It even allowed multiple readers (if the key/value pair was already cached), but if a lookup was not cached, one thread and one thread only got to open the file, read a chunk of data, and stick it in the file. This was a class that knew what it was doing. You're probably going to tell me that I can still do all that in D, providing I don't declare it const. The problem is, if I don't declare it const, /and/ all consts in D are transitive, then I can't store a reference to that object in any const structure. Basically I'd end up declaring just about nothing const, and I'd see no benefit.I've been wondering about this. So far, I've only been able to come up with two options: 1) Cast away const when dealing with 'mutable' data. 2) Store mutable data externally, perhaps in an AA. I haven't yet decided whether there are any fundamental problems with option 1 other than it being generally bad practice. Option 2 seems impractical in most cases however, because any common store of such mutable data must itself be protected by a mutex.I think Walter is suggesting that the language should still lend itself to code that isn't error-prone. What remains unclear to me is whether the lack of 'mutable' will actually be an issue or if workarounds and alternate programming techniques (encouraged by the FP model) will be sufficient as a replacement.Very few programmers are able to successfully write such code. With FP programming, most programmers will be able to.That's obviously true. I'm not sure why it's relevant though. You said you had a "pure" keyword in mind for that. But if I don't use the pure keyword, then thread safety should be down to me, right?That's a good example. I do wonder, however, whether such an object would be a viable drop-in replacement in all cases. In C++ my answer would be a confident "yes," but in D...It has come up before. The answer is to not declare logically const things as being const, because they aren't const. Logical constness belongs as a comment because it is not compiler checkable.But you might want to declare logically const things as being const for efficiency reasons. And to argue that "they aren't const" is a matter of definition. "const" in C++ is /defined/ to mean logical const, so const they are! You use the word to mean "physically const". We don't all agree on the definition. Suppose there exists a class String with member function uppercase(), declared const (or invariant, as you would have it in D). So far, so good. But then, along comes a better (or at least, different) string class with the same interface, and it happens to be faster, and I want to use the new FastString class in place of the old String class. All I have to do is search and replace, right? Or just make an alias. But there's a problem. Turns out, the reason it's faster is because it caches some results internally so it doesn't have to keep recomputing them. From the point of view of "implementation should be independent of interface" this internal detail is something I shouldn't need to know or care about. But now suddenly, I do need to know about it. And care. Because now that uppercase() function I mentioned earlier is no longer physically const. That means, if I use it as a drop-in replacement, my code now won't compile.Agreed. Though I do think it's worth experimenting a bit more with the D approach to see if it is a reasonable replacement. I do think that the traditional OO design that logical const supports in C++ may end up not being the general approach to program design in D. But by the same token, perhaps that isn't sufficient reason to not support the approach. SeanI'll reiterate that const-that-isn't-checkably-constant has no value beyond being a comment, so it might as well just be a comment.Oh contraire. It /is/ meaningful. It is a cast iron guarantee that no non mutable (which I argue should imply non-private) variables will be modified. Without the const declaration, you do not have that guarantee. It's a guarantee *which the compiler can use* to help the programmer catch errors!
Sep 12 2007
Janice Caron wrote:I've written a lot of multithreaded programming. A lot.Ok. I've written some, enough to know it is very, very hard to get right. I also attended a conference of the top C++ people on what to do about multithreaded programming support in the C++ language, and came away from it with the realization that only a handful of those people even understood the problem (no, I don't count myself as one of them). These are the experts.(By the way - could you also allow the spelling "synchronised" for the benefit of speakers of British English?) :-)Maybe. Colour, never. <g>synchronised is a blunt tool though. In my C++ code, I arrange it so that multiple threads can simultaneously obtain read-access, or exactly one thread can obtain write-access. Correct me if I'm wrong, but the built-in synchronized feature isn't that smart?It's nothing more than a sugary mutex. It certainly isn't the overarching solution to multithreaded programming I had earlier thought it was.So anyway, as I mentioned further up this thread, I had this loopup class member function that got stuff from a file and cached it. I assure you it was thread-safe. It even allowed multiple readers (if the key/value pair was already cached), but if a lookup was not cached, one thread and one thread only got to open the file, read a chunk of data, and stick it in the file. This was a class that knew what it was doing.If you can assure me it is thread safe, and it is, then you are a very rare programmer. Even if it is thread safe, it can still have deadlocks when used in conjunction with other thread safe packages. What I'm getting at is multithreaded programming is very, very hard. It needs to be simpler, a lot simpler.You're probably going to tell me that I can still do all that in D, providing I don't declare it const. The problem is, if I don't declare it const, /and/ all consts in D are transitive, then I can't store a reference to that object in any const structure. Basically I'd end up declaring just about nothing const, and I'd see no benefit.You're right, I'm going to say that if it can change state, then it should not be declared const. I believe there is a greater benefit to things being reliably, checkably, constant, than there is for declaring things const that aren't constant. I think what is pretty clear at this point is that const in D will be used differently than in C++.That's one reason.Being in a module doesn't prevent another thread from using the module's interface and thereby changing the state.Is that why you object?I would have thought it the programmer's problem to ensure thread safety, not the compiler's. If I have a variable that's going to be accessed by multiple threads, then I'm going to make sure it's properly synchronised, and I if screw up, then it's my bug.There are a couple problems with this: 1) Programmers aren't that good. Sure, a few are, but most of us aren't, and we write code we imagine is thread safe but really isn't. See Scott Meyer's article on double checked locking, for example. 2) Even if a programmer is that good, programmers still have bad days. It's why pilots have checklists for everything, and always use them, no matter how well they've memorized them. 3) Programmers being good doesn't help the code auditor, who has to always assume the worst. 4) Automation to detect flaws is better than relying on human failings.I think this is the subject <g>.Yes, but that requires programmer discipline, which is unreliable and scales poorly.I think it could scale very well with the right language support. But that's another subject.The const will still be needed.Very few programmers are able to successfully write such code. With FP programming, most programmers will be able to.That's obviously true. I'm not sure why it's relevant though. You said you had a "pure" keyword in mind for that. But if I don't use the pure keyword, then thread safety should be down to me, right?There aren't any optimizations that logical const enables, for the simple reason that a state change will invalidate the optimization, and you already agreed that logically const objects change state.It has come up before. The answer is to not declare logically const things as being const, because they aren't const. Logical constness belongs as a comment because it is not compiler checkable.But you might want to declare logically const things as being const for efficiency reasons.And to argue that "they aren't const" is a matter of definition. "const" in C++ is /defined/ to mean logical const, so const they are! You use the word to mean "physically const". We don't all agree on the definition.C++ invented the term "logical const" because people clearly were surprised by const references not being constant. But that's not really relevant here, what is relevant is what is the right definition of const for D. I think D should have an intuitive, useful, and checkable notion of const. C++'s is not intuitive, marginally useful, and not checkable.Suppose there exists a class String with member function uppercase(), declared const (or invariant, as you would have it in D). So far, so good. But then, along comes a better (or at least, different) string class with the same interface, and it happens to be faster, and I want to use the new FastString class in place of the old String class. All I have to do is search and replace, right? Or just make an alias. But there's a problem. Turns out, the reason it's faster is because it caches some results internally so it doesn't have to keep recomputing them. From the point of view of "implementation should be independent of interface" this internal detail is something I shouldn't need to know or care about. But now suddenly, I do need to know about it. And care. Because now that uppercase() function I mentioned earlier is no longer physically const. That means, if I use it as a drop-in replacement, my code now won't compile.It's a different interface if one is const and the other mutable. It really IS different (repeating myself!). A user of it would care, because now he'd know that it may no longer be thread-safe.No, it isn't, because: 1) The supplier of the type may change its internals to mutable. You recompile, now it crashes in your multithreaded environment. 2) D has opaque types where the contents are not knowable. 3) It becomes impossible to write thread safe generic code.I'll reiterate that const-that-isn't-checkably-constant has no value beyond being a comment, so it might as well just be a comment.Oh contraire. It /is/ meaningful. It is a cast iron guarantee that no non mutable (which I argue should imply non-private) variables will be modified.Without the const declaration, you do not have that guarantee. It's a guarantee *which the compiler can use* to help the programmer catch errors! For example, this C++ class C { mutable int x; int y; void f() const; { x = 1; y = 2; /* The compiler catches this error */ } } See - this is useful to me. If I had not declared f const (which is basically what you're suggesting), then the compiler would have been no help to me whatsoever. It would not have flagged y=2 as an error. I /want/ the compiler to assist me by flagging errors like this. I want it to be a compile-time error for f to modify y. But as soon as remove the words "const" and "mutable" from that example, I lose that compiler-assistance.You can write your class as: int x; const int y;Transitivity doesn't have any use for logical const'ness, I'll agree with that.The D compiler does not necessarily know all there is to know about a type:OK. Good point. I hadn't thought that one through. The difficulty is, if you don't allow mutable member variables, then we're back to what started this thread - transitivity sucks.See, I was basically arguing with you, that transitivity is a good thing -- /provided/ we can have mutable members. Now, if we can't have that, then suddenly transitivity no longer seems so welcoming. If const-transitivity becomes a strait-jacket, then folk are going to end up just not declaring anything const at all, because they can't get their code to compile otherwise.The design patterns const is often used for in C++ won't work in D, that I agree with. But there will be new design patterns available, I argue very valuable ones, that are impossible with C++ const.
Sep 12 2007
Walter Bright wrote:1) Programmers aren't that good. Sure, a few are, but most of us aren't, and we write code we imagine is thread safe but really isn't. See Scott Meyer's article on double checked locking, for example. 2) Even if a programmer is that good, programmers still have bad days. It's why pilots have checklists for everything, and always use them, no matter how well they've memorized them. 3) Programmers being good doesn't help the code auditor, who has to always assume the worst. 4) Automation to detect flaws is better than relying on human failings.You use these arguments to justify that it's the compiler's job to help the user write thread-safe code. However, the same exact arguments apply to helping the user write "modification-safe" code -- I.e. code that doesn't modify anything not intended. This is what C++'s const/mutable combo gives you, and it seems useful to me.C++'s is not intuitive, marginally useful, and not checkable.Well, there's no proof at this point that D's const will be any more intuitive than C++'s (so far it's seeming less so). As for "marginally useful" -- C++ const helps people document the intended logical operation of their code. It's quite useful in that. Just not for optimization or multithreading purposes. As for "not checkable" -- what are all those "const int* doesn't match int*" errors I get when I try to compile my C++ code? It serves its purpose. You are fond of comparing C++ const to painting a black stripe on your chest and calling it a seat belt. It's humorous, but the comparison is inapt. C++ const is in fact a *LOT* like a seat belt in that it'll save your a** maybe 90% of the time, but not that other 10%. A painted stripe will save you %0 of the time, and that's simply not true of C++ const. Used properly, const in C++ probably points out something like 90% of potentially dangerous mistakes. Note: *Not talking about multithreading mistakes here!*, but rather more mundane mistakes. But programmers make plenty of the mundane kinds too. I think a more apt seatbelt analogy would be "seatbelts are worthless because all it takes is a simple pair of scissors to render them ineffective." That doesn't make seatbelts any less useful for those of use who use them properly. What you're after with D's const is not a seatbelt but like some sort of full-body airbag that will let you drive into oncomming traffic at 200MPH and still walk away unscathed. (like this maybe: http://www.zorb.com/ :-)) The goal of making life easy for multithreaded programming is great. But I think the less stringent variant of const found in C++ also has its uses. Maybe it's impossible to design a usable system that provides both, but I think you do the entire C++ community a disservice by offhandedly condemning C++ const as merely 'marginally useful'. --bb
Sep 12 2007
Bill Baxter Wrote:Walter Bright wrote:D's current const fixes both problems rather nicely, I still can't see the big argument against it semantically (syntax is a matter of taste, I like it, but it appears no one else does).1) Programmers aren't that good. Sure, a few are, but most of us aren't, and we write code we imagine is thread safe but really isn't. See Scott Meyer's article on double checked locking, for example. 2) Even if a programmer is that good, programmers still have bad days. It's why pilots have checklists for everything, and always use them, no matter how well they've memorized them. 3) Programmers being good doesn't help the code auditor, who has to always assume the worst. 4) Automation to detect flaws is better than relying on human failings.You use these arguments to justify that it's the compiler's job to help the user write thread-safe code. However, the same exact arguments apply to helping the user write "modification-safe" code -- I.e. code that doesn't modify anything not intended. This is what C++'s const/mutable combo gives you, and it seems useful to me. > C++'s is not intuitive, marginally useful, and not checkable. Well, there's no proof at this point that D's const will be any more intuitive than C++'s (so far it's seeming less so). As for "marginally useful" -- C++ const helps people document the intended logical operation of their code. It's quite useful in that. Just not for optimization or multithreading purposes. As for "not checkable" -- what are all those "const int* doesn't match int*" errors I get when I try to compile my C++ code? It serves its purpose. You are fond of comparing C++ const to painting a black stripe on your chest and calling it a seat belt. It's humorous, but the comparison is inapt. C++ const is in fact a *LOT* like a seat belt in that it'll save your a** maybe 90% of the time, but not that other 10%. A painted stripe will save you %0 of the time, and that's simply not true of C++ const. Used properly, const in C++ probably points out something like 90% of potentially dangerous mistakes. Note: *Not talking about multithreading mistakes here!*, but rather more mundane mistakes. But programmers make plenty of the mundane kinds too. I think a more apt seatbelt analogy would be "seatbelts are worthless because all it takes is a simple pair of scissors to render them ineffective." That doesn't make seatbelts any less useful for those of use who use them properly. What you're after with D's const is not a seatbelt but like some sort of full-body airbag that will let you drive into oncomming traffic at 200MPH and still walk away unscathed. (like this maybe: http://www.zorb.com/ :-)) The goal of making life easy for multithreaded programming is great. But I think the less stringent variant of const found in C++ also has its uses. Maybe it's impossible to design a usable system that provides both, but I think you do the entire C++ community a disservice by offhandedly condemning C++ const as merely 'marginally useful'. --bb
Sep 12 2007
Walter Bright wrote:Janice Caron wrote:[snip]The term may be new from the C++ community, but the notion is much, much older, and it's certainly nothing much to do with "const references not being constant". In an OO world, objects perform actions in response to messages that they receive. Logical constness means that their responses to those messages are unchanged. It's not about implementation details such as internal state. Logical const is about an interface, and it's not a C++ idea, though C++ helped to clarify it.And to argue that "they aren't const" is a matter of definition. "const" in C++ is /defined/ to mean logical const, so const they are! You use the word to mean "physically const". We don't all agree on the definition.C++ invented the term "logical const" because people clearly were surprised by const references not being constant.But that's not really relevant here, what is relevant is what is the right definition of const for D."Those who cannot learn from..." We can't ignore learning from the history of immutability and of read-only views in other languages if we want to find what's best for D. (That doesn't, of course, mean that new ideas shouldn't also be explored.)I think D should have an intuitive, useful, and checkable notion of const.These may be forces that are in conflict; coming up with something in the sweet spot is the challenge. (It may be that in some areas some of these ideas are complementary, which is nice when it happens.) C++'s const seems intuitive to many of us, but not to others. It's checkable in some sense, but not in another. It's not much use for optimization (though it is some). And it's very useful, though Walter disagrees with that assessment. Logical const reflects something meaningful at an interface level. Deep physical const isn't so useful there; you might have an const id, identifying a changing external resource: indirection sinks any attempt to make enforceable const reflect actual unchanging behavior, unless you take it much further in the direction of "pure". Physical const is an implementation detail. Logical const is an interface issue. Tools can deal more easily with implementation issues than with design issues, but we shouldn't let the tools get in the way of good design.C++'s is not intuitive, marginally useful, and not checkable.Just thought we should flag that last sentence as subjective propaganda in case anyone still thought Walter was bizarrely neutral on the C++/D discussions >;-) -- James
Sep 12 2007
On 9/13/07, Walter Bright <newshound1 digitalmars.com> wrote:See Scott Meyer's article on double checked locking, for example.Just read it. And fear not - I wouldn't make that mistake.I could start another thread (...newsgroup thread, I mean...) for that.I think it could scale very well with the right language support. But that's another subject.I think this is the subject <g>.programmer catch errors! For example, this C++ class C { mutable int x; int y; void f() const; { x = 1; y = 2; /* The compiler catches this error */ } } See - this is useful to me. If I had not declared f const (which is basically what you're suggesting), then the compiler would have been no help to me whatsoever. It would not have flagged y=2 as an error.You can write your class as: int x; const int y;no I can't, because then I can't add a member function g class C { mutable int x; int y; void f() const; { x = 1; y = 2; /* Error - f is const */ } void g() { y = 3; /* OK -*/ } } Much of what you said hinges on keywords being intuitive, and on that score, I must agree with you. To me, C++'s const /means/ "logical const", but only because I've got used to it. If we're going to use better, more intuitive keywords, then I would strongly argue in favor of using "readonly" to mean "a read-only view of stuff that might be changed through some other reference", and "const" to mean "stuff that can never be changed, ever". The distinction between logical const and physical const *disappears completely* if const is not transitive. That is, with non-transitive const, one can always write a class differently such that "mutable" is never required. Essentially, you replace class C { mutable T x; } with class C { T * x; } Then you can let x be constant, but still modify the actual variable itself through *x. The difficulty for me is that you want to get rid of intransitive const, and /also/ disallow mutable. I /like/ your syntax for applying constness to everything inside the brackets. The notion that C++'s int const * const * const * * * p; can be written instead as: const(int **)*** p; definitely has its appeal. If you so chose, you could allow the same functional notation to remove constness, so that int * * * const * const * const p; could be written in D as const(mutable(int **)***) p; ...but if I've followed this argument correcly, you're not likely to do that, because you believe that allowing mutable members to be accessed through a const pointer - however indirectly - screws things up for mulithreading. And of course, you'd be right. ...which leads me to the conclusion that - in partial agreement with you - allowing mutable members to be accessed through a const pointer, even indirectly, should not be allowed ... unless, and this is the rub, *UNLESS IT CAN BE MADE THREADSAFE* ...and I believe it can. As you quite rightly point out, "mutable" isn't enough. Even synchronised isn't enough. But I could suggest a new language feature that /would/ be enough. I'll start a new thread for it, once I've composed my thoughts into something approaching documentation. So ... you've convinced me. Now I want to convince you. The use cases that have been listed so far justify /trying/. Your desire to make multithreading easier also justifies trying. Hopefully there is a way we can get the best of both worlds.
Sep 13 2007
On 9/13/07, Janice Caron <caron800 googlemail.com> wrote:with non-transitive const, one can always write a class differently such that "mutable" is never required. Essentially, you replace class C { mutable T x; } with class C { T * x; } Then you can let x be constant, but still modify the actual variable itself through *x.Except of course that using the "mutable" keyword ensures that /only/ class member functions can modify x, /and/ gives you value semantics instead of pointer semantics to boot.
Sep 13 2007
Walter Bright wrote: well, quite a lot, really. It seems that you're concentrating heavily on const for objects being able to ensure thread safety and do a number of other optimizations. Basically, as far as the owner of a const reference is concerned, the object and all its methods are 'pure'. So, since, for all functions that can be optimized similarly and whose thread safety can be ensured similarly, we will be adding the pure keyword, why not use the pure keyword here? Then a const reference to an object could be a weaker contract that allows everything that people have been wishing the new new const would allow, if we can find a solution for it that enough people find useable. (And by 'we' I mean 'you'.)
Sep 13 2007
Christopher Wright wrote:Walter Bright wrote: well, quite a lot, really. It seems that you're concentrating heavily on const for objects being able to ensure thread safety and do a number of other optimizations. Basically, as far as the owner of a const reference is concerned, the object and all its methods are 'pure'. So, since, for all functions that can be optimized similarly and whose thread safety can be ensured similarly, we will be adding the pure keyword, why not use the pure keyword here?Now there's an idea. I like it. Sean
Sep 13 2007
Walter Bright wrote:Janice Caron wrote:Is logical const really that bad? What would happen if you required in the spec that any changes made to a logical const variable must be undoable without changing the semantics of the program? Suppose we have a logical const object with internal state, A. We then call a logical const method, getResult(), which caches the result internally, making the object's internal state B. As long as the caching is done correctly, then A.getResult() == B.getResult(), so the compiler is free to substitute B for A, or A for B whenever it wants. As far as I can see, this re-enables the benefits of transitive const for functional and multi-threaded programming, as the compiler is free to ignore any changes to the variable, just as it is for transitive const. -- ReinerSo the function was declared const, because /conceptually/ it was. But the class had a mutable cache, declared with the C++ keyword "mutable" Transitivity would wreck that.You're right in that transitive const does not support the notion of "logical const" (which is the usual term for what you are referring to). The problem with logical const, however, is it offers no semantic guarantees. I pass a logical const reference around, and the underlying data may or may not change. I have no guarantees one way or the other, and even worse, I can't even tell this is happening. "I" here meaning the compiler, and our hapless code auditor. This just pulls the rug out from under: 1) functional programming 2) multithreaded programming 3) having a tightly specified interface It goes back to painting a stripe across your hips and calling it a seatbelt. Given this, it isn't any surprise that C++ is disastrously difficult to write functional & multithreaded programs in, and C++ mutability is one of the reasons why. In C++, you can write const this and const that and it doesn't mean jack squat. Many experienced C++ programmers will tell you that const is little more than a documentation aid. In other words, you're quite right that transitive const totally wrecks using const to specify logical constness. And that's a good thing <g>.
Sep 12 2007
Content-Disposition: inline I'm fair sure that logical const is a real world requirement. Take the classic example Shape, for example... const Rectangle r = new Shape(); r.draw(); Whoops! Const-transitivity prevents r.draw() from compiling. Why? Because Shape has a member variable Raster, and Rectangle.draw() calls Raster.paintRectangle() or some such, which modifies the state of the raster. So what do you do? You could try changing the prototype to class Rectangle { override invariant draw(Raster raster); } But now it still won't compile, because the abstract function Shape.drawwasn't declared like that, so then you have to go back another step and change /that/ declaration to: class Shape { abstract invariant draw(Raster raster); } It's a solution which doesn't scale. And there goes encapsulation...
Sep 12 2007
Janice Caron wrote:It's a solution which doesn't scale. And there goes encapsulation...At least for this example, the same issue exists when attempting to do "const-correctness" in C++. You cannot paste const on at the top level, it has to be put in at every level underneath it.
Sep 12 2007
Walter Bright wrote:Janice Caron wrote:No, it doesn't. C++ doesn't hide the references from you with its syntax, but the const Rectangle having a reference to a Raster would *not* imply constness of that Raster in C++. (Indeed, this is a fairly common situation, whether Raster is held by reference or some kind of pointer.)It's a solution which doesn't scale. And there goes encapsulation...At least for this example, the same issue exists when attempting to do "const-correctness" in C++.You cannot paste const on at the top level, it has to be put in at every level underneath it.I'm not sure what you mean by that. There's not much context in which to interpret it. -- James
Sep 12 2007
James Dennett wrote:Walter Bright wrote:Try turning a char* into a const char* at the top level of your program where you don't use const anywhere else. You'll have to add it to every function that takes that variable as an argument, then recursively add it to every function that one calls.You cannot paste const on at the top level, it has to be put in at every level underneath it.I'm not sure what you mean by that. There's not much context in which to interpret it.
Sep 13 2007
Walter Bright wrote:James Dennett wrote:OK, now I understand: and what you say is true, const-correctness can't trivially be layered on top of const-ignorant ccode unless you tolerate a boundary layer with const_cast where needed. That's a good thing and a bad thing -- good in that it puts pressure on the lower layers to "do the right thing", bad in that it makes it harder for higher layers to do so if the layers beneath do not. However: for most C++, 99% of the time, it's not a problem. -- JamesWalter Bright wrote:Try turning a char* into a const char* at the top level of your program where you don't use const anywhere else. You'll have to add it to every function that takes that variable as an argument, then recursively add it to every function that one calls.You cannot paste const on at the top level, it has to be put in at every level underneath it.I'm not sure what you mean by that. There's not much context in which to interpret it.
Sep 13 2007
James Dennett Wrote:Walter Bright wrote:[snip]Janice Caron wrote:No, it doesn't. C++ doesn't hide the references from you with its syntax, but the const Rectangle having a reference to a Raster would *not* imply constness of that Raster in C++. (Indeed, this is a fairly common situation, whether Raster is held by reference or some kind of pointer.)It's a solution which doesn't scale. And there goes encapsulation...At least for this example, the same issue exists when attempting to do "const-correctness" in C++.-- JamesThis is a good point to emphasise. In object oriented programming there is a clear difference between encapsulating something in an aggregate and merely being associated with it. In the rectangle example a raster is clearly not "part of" a rectangle but it is associated. An important concept here is ownership. In C++ I general find myself labelling pointers as owned and not-owned. If you own a pointer you are responsible for deleting the object it points to. Some experimental static analysis tools are able to use pointers labelled this way to reason about memory allocation. Research suggests (I will have to google around a bit before I can quote references) that adding something to a language to indicate ownership is warranted. D with its garbage collection doesn't have this problem on the surface but there is still a distinction between something that is owned and something that is referred to. This is a distinction that is (as far as I can tell) currently impossible to represent except via comments. In D everything is a reference. Perhaps the solution is to make non-owned members (in the sense of association) pointers and reserve references (minus the pointer) for aggregation. A value keyword might help some but I think it would address the wrong problem. For members of an aggregate const should be transitive. If I cannot change this object then I should not be able to change any of its parts. If there is a logger, cache or raster - Associated - with the class I may still be able to change it - depending on whether that particular associated is marked as const. Regards, Bruce.
Sep 13 2007
Bruce Adams wrote:James Dennett Wrote:Walter Bright wrote:This is a good point to emphasise. In object oriented programming there is a clear difference between encapsulating something in an aggregate and merely being associated with it. In the rectangle example a raster is clearly not "part of" a rectangle but it is associated. An important concept here is ownership. In C++ I general find myself labelling pointers as owned and not-owned. If you own a pointer you are responsible for deleting the object it points to. Some experimental static analysis tools are able to use pointers labelled this way to reason about memory allocation. Research suggests (I will have to google around a bit before I can quote references) that adding something to a language to indicate ownership is warranted.Janice Caron wrote:No, it doesn't. C++ doesn't hide the references from you with its syntax, but the const Rectangle having a reference to a Raster would *not* imply constness of that Raster in C++. (Indeed, this is a fairly common situation, whether Raster is held by reference or some kind of pointer.)It's a solution which doesn't scale. And there goes encapsulation...At least for this example, the same issue exists when attempting to do "const-correctness" in C++.D with its garbage collection doesn't have this problem on the surface but there is still a distinction between something that is owned and something that is referred to. This is a distinction that is (as far as I can tell) currently impossible to represent except via comments. In D everything is a reference. Perhaps the solution is to make non-owned members (in the sense of association) pointers and reserve references (minus the pointer) for aggregation. A value keyword might help some but I think it would address the wrong problem. For members of an aggregate const should be transitive. If I cannot change this object then I should not be able to change any of its parts. If there is a logger, cache or raster - Associated - with the class I may still be able to change it - depending on whether that particular associated is marked as const.For what it's worth, I think object ownership could be communicated via the 'scope' keyword. Walter mentioned in the past that he'd considered allowing it in class scope anyway. Sean
Sep 13 2007
Content-Disposition: inline Even something as simple as this needs logical const class MyMathClass { invariant int multiply(int x, int y) /* logically const */ { debug logfile.writeLine("multiply.called"); return x * y; } debug private Stream logfile; } You can't tell me that's not a real world need.
Sep 12 2007
Janice Caron Wrote:Even something as simple as this needs logical const class MyMathClass { invariant int multiply(int x, int y) /* logically const */ { debug logfile.writeLine("multiply.called"); return x * y; } debug private Stream logfile; } You can't tell me that's not a real world need. Even something as simple as this needs logical const<br><br>class MyMathClass<br>{<br> invariant int multiply(int x, int y) /* logically const */<br> {<br> debug logfile.writeLine("multiply.called"); <br> return x * y;<br> }<br><br> debug private Stream logfile;<br>}<br><br>You can't tell me that's not a real world need.<br><br>BTW if there any chance you can change your mail options not to post in both html and text. Every single message of yours is duplicated twice on http://www.digitalmars.com/webnews. Thanks.
Sep 12 2007
Janice Caron wrote:Even something as simple as this needs logical const class MyMathClass { invariant int multiply(int x, int y) /* logically const */ { debug logfile.writeLine("multiply.called"); return x * y; } debug private Stream logfile; } You can't tell me that's not a real world need.You can: 1. make logfile a static member. 2. not use invariant on the multiply. After all, if it isn't actually invariant, the invariant declaration wouldn't have any more meaning than the comment you put on it.
Sep 12 2007
I was trying to simplifly. Sigh. The gist of this particular example is that have what /starts off/ as perfectly good, non-member-modifying code, and all is well. But then you later put in some diagnostics to make it write stuff to a file (coz you're debugging) and suddenly it no longer compiles and you have keep taking const out of your code all over the place until it does. What about all those other cases? There's a long list of applications there.
Sep 12 2007
Janice Caron wrote:I was trying to simplifly. Sigh. The gist of this particular example is that have what /starts off/ as perfectly good, non-member-modifying code, and all is well. But then you later put in some diagnostics to make it write stuff to a file (coz you're debugging) and suddenly it no longer compiles and you have keep taking const out of your code all over the place until it does.Or you can cast away const-ness. But you are on your own if you do that.
Sep 12 2007
Walter Bright wrote:Janice Caron wrote:Well, in C++ you'd be fine, but in D I thought the behavior if you modified something after casting away const was undefined, so you're out of luck. (Not that you'd need to cast away const for this in C++.) -- JamesI was trying to simplifly. Sigh. The gist of this particular example is that have what /starts off/ as perfectly good, non-member-modifying code, and all is well. But then you later put in some diagnostics to make it write stuff to a file (coz you're debugging) and suddenly it no longer compiles and you have keep taking const out of your code all over the place until it does.Or you can cast away const-ness. But you are on your own if you do that.
Sep 12 2007
James Dennett wrote:Walter Bright wrote:It's also undefined behavior if you cast a pointer to an int. You need to know what you're doing.Or you can cast away const-ness. But you are on your own if you do that.Well, in C++ you'd be fine, but in D I thought the behavior if you modified something after casting away const was undefined, so you're out of luck. (Not that you'd need to cast away const for this in C++.)
Sep 13 2007
Walter Bright wrote:James Dennett wrote:Right. But you've snipped too much context. The point was that C++ allows something, and D doesn't. In D you can't even cast away the const and then modify the underlying object, because it's undefined (so if you do so, you *don't* know what you're doing). The need is to modify state in an object associated with another object. That doesn't change the object in question (either physically or logically) but is disallowed by D, as there's no recognition that association does not imply aggregation/ containment. -- JamesWalter Bright wrote:It's also undefined behavior if you cast a pointer to an int. You need to know what you're doing.Or you can cast away const-ness. But you are on your own if you do that.Well, in C++ you'd be fine, but in D I thought the behavior if you modified something after casting away const was undefined, so you're out of luck. (Not that you'd need to cast away const for this in C++.)
Sep 13 2007
Reiner Pope wrote:As far as I can see, this re-enables the benefits of transitive const for functional and multi-threaded programming, as the compiler is free to ignore any changes to the variable, just as it is for transitive const.It doesn't re-enable any benefits, as all the usual synchronization, race conditions, etc., are there in full force. You're back to relying on programmer discipline to make it work right, and experience shows that that doesn't work for multithreading.
Sep 12 2007
Walter Bright wrote:Janice Caron wrote:In the interest of this discussion, here's a very nice article regarding the topic of logical constness: http://www.ddj.com/cpp/184403892 (the fact that it mentions Walter is coincidence, the article is simply the first result I got when I googled for "logical constness") Anyways, we now see that 'mutable' and logical constness is dangerous when one wants to do multi-threaded optimizations. However, since D is going to have several flavors of const (such as 'const', 'invariant' and also the 'pure' functions), why not allowing logical constness on some of these flavors, but disallow it in others. Namely, we could allow logical constness in 'const' and 'invariant' data, that is, changing 'mutable' members of 'const' and 'invariant' data. Thus D would allow for the the use cases of logical constness. But on 'pure' functions, no changes whatsover would be allowed, even of 'mutable' members, and so we could still be able to use the 'pure' construct to safely create multi-threaded an parallelization optimizations. Would this be ok? -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#DSo the function was declared const, because /conceptually/ it was. But the class had a mutable cache, declared with the C++ keyword "mutable" Transitivity would wreck that.You're right in that transitive const does not support the notion of "logical const" (which is the usual term for what you are referring to). The problem with logical const, however, is it offers no semantic guarantees. I pass a logical const reference around, and the underlying data may or may not change. I have no guarantees one way or the other, and even worse, I can't even tell this is happening. "I" here meaning the compiler, and our hapless code auditor. This just pulls the rug out from under: 1) functional programming 2) multithreaded programming 3) having a tightly specified interface It goes back to painting a stripe across your hips and calling it a seatbelt. Given this, it isn't any surprise that C++ is disastrously difficult to write functional & multithreaded programs in, and C++ mutability is one of the reasons why. In C++, you can write const this and const that and it doesn't mean jack squat. Many experienced C++ programmers will tell you that const is little more than a documentation aid. In other words, you're quite right that transitive const totally wrecks using const to specify logical constness. And that's a good thing <g>.
Sep 12 2007
Content-Disposition: inline On 9/11/07, Janice Caron <caron800 googlemail.com> wrote:The thing is, all the data was in a file, and so the function had to open a file, read it, close the file, and return the data. Well, naturally, it cached some data internallyThese days, of course, the data source might not even be a file - it might be an resource you have to fetch from a remote site on the internet using HTTP. Caching is, occasionally, really necessary.
Sep 11 2007
Content-Disposition: inline What all of these use-cases have in common is the fact that the state is private Suppose that all class and struct members which were declared private, were always mutable, even if the class instance is const. That would mean you could do this: class RandomNumberGenerator; { private long seed; const int rand() /* guarantees not to modify any non-private member variables */ { seed = f(seed); return (seed >> 32); } } const RandomNumberGenerator rng; writefln(rng.rand()); In C++, we would have declared seed "private mutable" to achieve the same thing, but in D we tend to assume that everything in the same module is "friendly", so why not just let "private" mean "private mutable"?
Sep 11 2007
"Janice Caron" wroteWhat all of these use-cases have in common is the fact that the state is private Suppose that all class and struct members which were declared private, were always mutable, even if the class instance is const.I sort of agree with you, but what about instances where you want derived classes to be able to access the cache? I think having a keyword for mutable is necessary, but what may make sense is to enforce that only non-public members are allowed to be mutable. What is the argument against mutable again? -Steve
Sep 11 2007
On 9/11/07, Steven Schveighoffer <schveiguy yahoo.com> wrote:"Janice Caron" wroteI believe I can make the case for my argument stronger. In general, when I expose a class's interface, the understood contract is: class C { public: /* stuff I want you to know about */ private: /* None of your gorram business. It's PRIVATE. You shouldn't care know or care what's in here */ } To a caller of the code, it should not matter what member functions of C do to its private members, so long as the public interface does what you expect. Here's another use case. Suppose I start off with a simple class like: class C { void f() { /* do something */ } } const C c; c.f(); But later, as I'm debugging the code, I realise it's not doing quite what I want it to do, and I need to put some diagnostic code in to help me with debugging. So I change it to: class C { void f() { debug ++count; /* do something */ } debug private int count; } const C c; c.f(); Whoops! Now it won't compile in debug mode (but it will still compile in release mode). Again, it's because /private/ state is conflicting with the constness.What all of these use-cases have in common is the fact that the state is private Suppose that all class and struct members which were declared private, were always mutable, even if the class instance is const.I sort of agree with you,but what about instances where you want derived classes to be able to access the cache?I can't think of a use-case for that. Would it not suffice for the base-class to expose protected functions to provide read-only access to the cache?I think having a keyword for mutable is necessaryI believe this thread has shown that there is a need for the /functionality/ that the keyword provides, but I'm not completely convinced that we need the keyword itself. If we allow private data to be implicitly mutable then we don't need the keyword.but what may make sense is to enforce that only non-public members are allowed to be mutable.My idea would make that enforcement unnecessaryWhat is the argument against mutable again?In C++, you can do this: class C { public: mutable int x; C() { x = 0; } void f() const { x = 5; } } main() { const C c; printf("%d\n", c.x); /* prints 0; */ c.f(); printf("%d\n", c.x); /* prints 5; */ } This is clearly nonsense. That would be disallowed in my scheme. (...at least, if main() was in a different file from the definition of C).
Sep 11 2007
"Janice Caron" wroteOn 9/11/07, Steven Schveighoffer <schveiguy yahoo.com> wrote:Yeah, I understand your original point. I'm not disputing that, which is why I "sort of" agree with you. My point that protected should be able to be mutable as well as private was the point I was trying to make."Janice Caron" wroteI believe I can make the case for my argument stronger. In general, when I expose a class's interface, the understood contract is: class C { public: /* stuff I want you to know about */ private: /* None of your gorram business. It's PRIVATE. You shouldn't care know or care what's in here */ }What all of these use-cases have in common is the fact that the state is private Suppose that all class and struct members which were declared private, were always mutable, even if the class instance is const.I sort of agree with you,The one that comes to mind is if you wanted to slightly modify the way a cache was used. The two options are: make the cache protected, and allow derived classes to use it (in which case mutable is required), or re-implement the cache in the slightly modified way in the derived class, in which case you are carrying around 2 caches for no reason.but what about instances where you want derived classes to be able to access the cache?I can't think of a use-case for that.Would it not suffice for the base-class to expose protected functions to provide read-only access to the cache?Well, yeah, you could do it that way, but you would have to know before-hand how your cache was going to be accessed/used by the derived classes, therefore restricting the creativity of the author of the derived class.// tango style class NonsenseInD { private int _x = 0; int x() const { return _x;} void f() const { x = 5;} } // could be in another file int main(char[][] args) { const NonsenseInD c = new NonsenseInD; Stdout(c.x).newline; // prints 0; c.f(); Stdout(c.x).newline; // prints 5; } Even with this example, I still don't see why mutable is such a horrible thing... To me it makes sense to have pieces of a class that can change, even when the class is const. You have given several good examples (files, net connections, etc), but I think your idea of enforcing public data being const is probably not feasible. -SteveWhat is the argument against mutable again?In C++, you can do this: class C { public: mutable int x; C() { x = 0; } void f() const { x = 5; } } main() { const C c; printf("%d\n", c.x); /* prints 0; */ c.f(); printf("%d\n", c.x); /* prints 5; */ } This is clearly nonsense.
Sep 11 2007
On 9/11/07, Steven Schveighoffer <schveiguy yahoo.com> wrote:// tango style class NonsenseInD { private int _x = 0; int x() const { return _x;} void f() const { x = 5;} } // could be in another file int main(char[][] args) { const NonsenseInD c = new NonsenseInD; Stdout(c.x).newline; // prints 0;No it won't, because it won't compile, because x is private. (It will compile if main is in the same file as class NonsenseInD, but I can live with that).
Sep 11 2007
No it won't, because it won't compile, because x is private.Sorry - accept my apologies - I misread your code.
Sep 11 2007
Steven Schveighoffer wrote:"Janice Caron" wroteProvide a property to access the cache. It'll likely be inlined, so no performance hit.On 9/11/07, Steven Schveighoffer <schveiguy yahoo.com> wrote:The one that comes to mind is if you wanted to slightly modify the way a cache was used. The two options are: make the cache protected, and allow derived classes to use it (in which case mutable is required), or re-implement the cache in the slightly modified way in the derived class, in which case you are carrying around 2 caches for no reason.but what about instances where you want derived classes to be able to access the cache?I can't think of a use-case for that.
Sep 11 2007
Janice Caron wrote:On 9/11/07, Steven Schveighoffer <schveiguy yahoo.com> wrote:And why is this nonsense?? Because x is public? I don't see the problem here. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#DWhat is the argument against mutable again?In C++, you can do this: class C { public: mutable int x; C() { x = 0; } void f() const { x = 5; } } main() { const C c; printf("%d\n", c.x); /* prints 0; */ c.f(); printf("%d\n", c.x); /* prints 5; */ } This is clearly nonsense. That would be disallowed in my scheme. (...at least, if main() was in a different file from the definition of C).
Sep 11 2007
And why is this nonsense?? Because x is public? I don't see the problem here.What's the use-case for mutable non-private members? I would say that it's nonsense if there's no need for them. "Nonsense" may be the wrong word. "Should not be part of D" is more what I really mean.
Sep 11 2007
Janice Caron wrote:I can't name a use case right now, but I do think significant use-cases for mutable non-private members exist. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#DAnd why is this nonsense?? Because x is public? I don't see the problem here.What's the use-case for mutable non-private members? I would say that it's nonsense if there's no need for them. "Nonsense" may be the wrong word. "Should not be part of D" is more what I really mean.
Sep 12 2007
Bruno Medeiros Wrote:Sean Kelly wrote:Since you mention contracts, one idea I had for a way out of this morass was to use them for enforcing type constraints like const. e.g. const int* foo; is sort of equivalent to: int* foo; static assert((*foo).const == true); The idea needs a bit of work obviously. Bruce.read operations. But for better or worse, D const has a different aim. The goal is largely to produce a system which allows for compiler optimization rather than to enforce some sort of logical restrictions on behavior. I suspect this means that D apps won't look very much like C++ apps in terms of how const is used, and the overall utility for the average programmer may well be somewhat small. SeanI'm don't think that's entirely true. 'invariant' the keyword is indeed made to allow several compiler and program optimizations, but 'const' the keyword is really for enforcing program contracts and restrictions, thus improving safety. I don't even think 'const' the keyword has any use whatsoever for optimization. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Sep 12 2007
Alex Burton wrote:While const is being reexamined.... I create an interface : interface Server { Data getData() const; };Hello, world! I'm fairly new to the world of D, but a minor detail like that has never stopped me from butting in so far ;) I'm sorry if any of my points have been made before, but I don't really feel like trawling through all 50.000 posts (although I have made an effort to dig into the more recent ones) First of all, I think specifying an interface method to be const is bad practice. The interface is assuming things about a possible implementation that is really not its business. As you describe, this will hinder your implementation. I understand you're not able to specify your particular getData() implementation as const due to const's transitivity, but as far as I'm concerned them's the breaks. I'm a bit of a const purist, sorry. What I would like to see regarding const and interfaces, is that the _implementation_ should be able to guarantee const-correctness even if the interface doesn't, much like covariant return types. The implementation should be free to guarantee more than its interface. That doesn't help you though... Best regards, Carsten Sørensen
Sep 12 2007
Carsten Sørensen Wrote:Alex Burton wrote:Someone correct me if I'm wrong but I thought D doesn't / wasn't going to support overloading methods on whether they are const or not. So this part of the discussion is almost moot. Bruce.While const is being reexamined.... I create an interface : interface Server { Data getData() const; };Hello, world! I'm fairly new to the world of D, but a minor detail like that has never stopped me from butting in so far ;) I'm sorry if any of my points have been made before, but I don't really feel like trawling through all 50.000 posts (although I have made an effort to dig into the more recent ones) First of all, I think specifying an interface method to be const is bad practice. The interface is assuming things about a possible implementation that is really not its business. As you describe, this will hinder your implementation. I understand you're not able to specify your particular getData() implementation as const due to const's transitivity, but as far as I'm concerned them's the breaks. I'm a bit of a const purist, sorry. What I would like to see regarding const and interfaces, is that the _implementation_ should be able to guarantee const-correctness even if the interface doesn't, much like covariant return types. The implementation should be free to guarantee more than its interface. That doesn't help you though... Best regards, Carsten Sørensen
Sep 12 2007
Bruce Adams wrote:Someone correct me if I'm wrong but I thought D doesn't / wasn't going to support overloading methods on whether they are const or not. So this part of the discussion is almost moot.Sorry if I wasn't totally clear. I'm not talking about overloading, but overriding/implementing and introducing const. Best regards, Carsten Sørensen
Sep 12 2007
Carsten Sørensen Wrote:Bruce Adams wrote:You were totally clear but using a C++ concept of const methods which I believe (and may be wrong) has no analogue in D. I believe the intention was to keep it out of the language as it is an ambomination. That said, I'm not clear how you can get the proper semantics for e.g. overloads of [] in D without it. Bruce.Someone correct me if I'm wrong but I thought D doesn't / wasn't going to support overloading methods on whether they are const or not. So this part of the discussion is almost moot.Sorry if I wasn't totally clear. I'm not talking about overloading, but overriding/implementing and introducing const. Best regards, Carsten Sørensen
Sep 12 2007
Bruce Adams wrote:You were totally clear but using a C++ concept of const methods which I believe (and may be wrong) has no analogue in D. I believe the intention was to keep it out of the language as it is an ambomination. That said, I'm not clear how you can get the proper semantics for e.g. overloads of [] in D without it. Bruce.I believe the issue of overloading opIndex and similiar is going to be taken care of using the 'return' storage-class (discussed in WalterAndrei.pdf). Basically it makes the return type of the function const or not, according to whether a particular parameter (e.g. 'this') is const or not. Thanks, Nathan Reed
Sep 12 2007
Walter Bright Wrote:Janice Caron wrote:Unacceptable - statics are bad as anyone writing good OO code with tests can tell you.Even something as simple as this needs logical const class MyMathClass { invariant int multiply(int x, int y) /* logically const */ { debug logfile.writeLine("multiply.called"); return x * y; } debug private Stream logfile; } You can't tell me that's not a real world need.You can: 1. make logfile a static member.2. not use invariant on the multiply. After all, if it isn't actually invariant, the invariant declaration wouldn't have any more meaning than the comment you put on it.It is far more than a comment. This class may be passed all through an application and as long as only const references to MyMathClass are we can be sure that all this code is not calling any other non const methods, only the ones marked as invariant which don't change the state of MyMathClass. Without transitive const, the logfile might be changed, but who cares, it's not part of the MyMathClass and no contract was made about modfying objects other than the current instance of MyMathClass. Basically I think you are trying really hard to kill many birds with one stone: thread safety checkable by compiler, const system for optimisation and lastly const system that is actually usable by programmer. This has never been done before and I commend you for working on it. I obviously don't know as much about compilers as you but, there seem to be limitations that you dont seem to be able to communicate to us about why you need transitive const. In the above example what prevents the compiler from changing any calls to multiply to: myMathClass.multiply(3,3); debug myMathClass.logfile.writeLine("multiply.called"); So now constness of the actual multiply call are preserved, and we can log the data too. Is it because the compiler is unable to see in side other functions unless they are templates - like c++ ?
Sep 12 2007
Alex Burton wrote:Basically I think you are trying really hard to kill many birds with one stone: thread safety checkable by compiler, const system for optimisation and lastly const system that is actually usable by programmer. This has never been done before and I commend you for working on it.Right! And the thread safety and optimization stuff is encapsulated in a second keyword reserved for functions: pure. Why is that keyword reserved for functions? Merely tradition? Though as Janice Caron has demonstrated in another thread, thread safety isn't really the sort of thing you want to include incidentally with other factors. Pure functions are by definition threadsafe and const; logically const functions are not necessarily threadsafe; threadsafe functions need not be const.I obviously don't know as much about compilers as you but, there seem to be limitations that you dont seem to be able to communicate to us about why you need transitive const.If you want to ensure thread safety, you either need some sort of mutex or you need to be able to say "Nothing will change when I do this, at all, so I don't need to worry about thread safety". (Well, regardless of whether you can say it, you need it to be true.) I don't think anyone here disagrees with that assessment. The issue is conflating all of const with something that allows such optimizations.In the above example what prevents the compiler from changing any calls to multiply to: myMathClass.multiply(3,3); debug myMathClass.logfile.writeLine("multiply.called"); So now constness of the actual multiply call are preserved, and we can log the data too. Is it because the compiler is unable to see in side other functions unless they are templates - like c++ ?Right, that's part of the reason. The rest of the reason is, if myMathClass is const, it's transitive const, so logfile is also const and you can't modify its state :)
Sep 13 2007
Christopher Wright wrote:Alex Burton wrote:A third option for thread safety would be if the function mutated only atomic non-local data. The function wouldn't be 'pure' however. SeanI obviously don't know as much about compilers as you but, there seem to be limitations that you dont seem to be able to communicate to us about why you need transitive const.If you want to ensure thread safety, you either need some sort of mutex or you need to be able to say "Nothing will change when I do this, at all, so I don't need to worry about thread safety". (Well, regardless of whether you can say it, you need it to be true.)
Sep 13 2007
Walter Bright Wrote:James Dennett wrote:James point is still valid. If you cast away const in D whose powerful const system allows the compiler to do all sorts of optimisations, there is no way to know whether some of those optimisations are invalid with your cast. If you know what you are doing you can cast a pointer to an int with full knowledge that the logic of your program is ok.Walter Bright wrote:It's also undefined behavior if you cast a pointer to an int. You need to know what you're doing.Or you can cast away const-ness. But you are on your own if you do that.Well, in C++ you'd be fine, but in D I thought the behavior if you modified something after casting away const was undefined, so you're out of luck. (Not that you'd need to cast away const for this in C++.)
Sep 13 2007
James Dennett Wrote:Well, in C++ you'd be fine, but in D I thought the behavior if you modified something after casting away const was undefined, so you're out of luck. (Not that you'd need to cast away const for this in C++.)You'll hit undefined behavior in C++ as well if the data being modified (via a const_cast) was originally declared as const. Compilers can (and some PPC compilers did) put such data in read-only memory, and even if they don't they tend to assume "really, truly const" and optimize accordingly. Hours of fun for all the family. cheers Mike
Sep 13 2007
Mike Capp wrote:James Dennett Wrote:Something of a mantra around here! However, that's certainly not the case we are talking about. We're talking about situations where an object (which is not changing) has a reference to another (mutable) object and should be able to change that object.Well, in C++ you'd be fine, but in D I thought the behavior if you modified something after casting away const was undefined, so you're out of luck. (Not that you'd need to cast away const for this in C++.)You'll hit undefined behavior in C++ as well if the data being modified (via a const_cast) was originally declared as const.Compilers can (and some PPC compilers did) put such data in read-only memory, and even if they don't they tend to assume "really, truly const" and optimize accordingly.Indeed. Non-mutable parts of objects defined as const in C++ cannot be modified by any well-defined code, and optimizers can and do use that fact to good effect. It's just not what this (sub-)thread was about. -- James
Sep 13 2007