digitalmars.D - Logical const
- Peter Alexander (16/16) Nov 20 2010 D does not support logical const due to the weak guarantees that it
- Michel Fortin (31/45) Nov 20 2010 Using a static associative array as a cache will work:
- Jordi (7/49) Nov 20 2010 I am also interested in what is the D way of doing this. The associative...
- BCS (4/23) Nov 27 2010 [...]
- Peter Alexander (5/64) Nov 28 2010 I'm a little worried by the lack of replies. I'm beginning to get the
- Jonathan M Davis (26/31) Nov 28 2010 Walter is certainly against logical constness, so it's not like the situ...
- Peter Alexander (41/45) Nov 28 2010 Thanks for the reply Jonathan.
- Jonathan M Davis (49/113) Nov 28 2010 Oh, I agree that the lack of logical const is a problem, but allowing fo...
- Andrei Alexandrescu (8/34) Nov 28 2010 The library solution to logical constness would gravitate around a union...
- Peter Alexander (5/12) Nov 28 2010 I must be missing something. Surely, when inside a const method, you
- Andrei Alexandrescu (4/19) Nov 28 2010 Sorry, that was wrong. I was thinking of a lazily initialized field
- Peter Alexander (21/29) Nov 28 2010 Mutable member variables are only a hole in the const system if the
- Andrei Alexandrescu (13/47) Nov 28 2010 Mutable is a disaster with read-write locks. Without mutable, you could
- bearophile (7/11) Nov 28 2010 In the air there is the idea of allowing implicit cast of the result of ...
- bearophile (3/4) Nov 28 2010 Ignore that, it's better to keep allow the usage of @memoize on strongly...
- Peter Alexander (11/22) Nov 28 2010 I agree with this, but it could work with an extra storage class.
- Walter Bright (8/23) Nov 28 2010 The problem with newlevel is (to repeat myself) it is unverifiable.
- bearophile (5/8) Nov 28 2010 I agree with what you say about C++ here. But in theory it's possible to...
- Peter Alexander (13/17) Nov 28 2010 I am technically worse off, just not from the compilers point of view.
- bearophile (6/16) Nov 28 2010 Likewise, in Python there is no const attribute, yet those program often...
- Rainer Deyke (19/31) Nov 28 2010 This is simply not true. Observe:
- bearophile (5/9) Nov 28 2010 I didn't know this. But of course you can do all you want in C++ :-)
- Walter Bright (3/6) Nov 28 2010 You'd have to do *all* of your pointer/ref members that way, with no com...
- Walter Bright (15/39) Nov 28 2010 Nope, for 5 reasons:
- so (3/15) Nov 29 2010 That is wrong, you can't be sure that will come back unmodified in C++.
- Simen kjaeraas (41/45) Nov 29 2010 As long as it is transitive, I believe it is verifiable:
- Walter Bright (4/7) Nov 29 2010 It is not verifiable because nothing prevents you from assigning:
- Simen kjaeraas (5/12) Nov 29 2010 Pardon my stubbornness (and perhaps ignorance), but how is this any less
- Walter Bright (2/17) Nov 29 2010 Logical const means the same value is returned every time, not a differe...
- Simen kjaeraas (13/15) Nov 29 2010 So you would have only pure functions work with logical const?
- Walter Bright (4/15) Nov 29 2010 It still is not verifiable. That's why logical constness is not a langua...
- Simen kjaeraas (4/6) Nov 30 2010 And finally I understood what you meant. Sorry about this taking a while...
- Walter Bright (2/8) Nov 30 2010 I know it's a tough issue, and worth the effort at trying to explain it.
- Peter Alexander (7/9) Nov 30 2010 No, it doesn't. Not even D's const can guarantee that:
- Michel Fortin (46/48) Nov 28 2010 I don't find it wrong, but it can certainly be an annoyance. Most other
- Walter Bright (11/18) Nov 28 2010 Carefully examining C++ const reveals that it offers no protection at al...
- Steven Schveighoffer (20/38) Nov 29 2010 This is complete BS. logical const in C++ is not the same as what logic...
- Steven Schveighoffer (6/8) Nov 29 2010 Reading more of the posts in this thread, it appears many are using C++ ...
- Walter Bright (9/36) Nov 29 2010 I don't understand your comment. In C++ you can write:
- Steven Schveighoffer (13/44) Nov 29 2010 You missed a const decorator there.
- Walter Bright (3/6) Nov 29 2010 Right, but C++ doesn't have purity either. I was trying to make the poin...
- Steven Schveighoffer (12/19) Nov 29 2010 Yeah, but saying because a const function in C++ might return a differen...
- Walter Bright (2/7) Nov 29 2010 Because people coming from C++ ask "why not do it like C++'s?"
- Steven Schveighoffer (7/14) Nov 29 2010 I don't get it. A way to make a field mutable in a transitively-const
- Walter Bright (7/24) Nov 29 2010 Having mutable members destroys any guarantees that const provides. That...
- Fawzi Mohamed (9/31) Nov 29 2010 logical const is useful for lazy functions and memoization, and if
- Walter Bright (4/11) Nov 29 2010 D allows escape from the type system, but the programmer who does that l...
- Max Samukha (6/18) Nov 30 2010 The problem is that logical const has many perfectly valid use cases.
- Andrei Alexandrescu (3/23) Nov 30 2010 I'm not seeing half of non-trivial C++ programs using mutable.
- Max Samukha (6/31) Nov 30 2010 That was a hyperbole. But logical const is obviously not some obscure
- Fawzi Mohamed (20/49) Nov 30 2010 The thing is that a lazy structure is very useful in functional
- Jesse Phillips (9/29) Nov 30 2010 This code is valid, the requirements placed on cast will not allow it to...
- Fawzi Mohamed (35/72) Dec 02 2010 a lazy list (for example one that list all natural numbers) cannot be
- Walter Bright (6/12) Nov 30 2010 I am not and never have said "don't use it. It's a fraud." I said that t...
- Steven Schveighoffer (22/45) Nov 30 2010 What guarantees? Const provides no guarantees.
- Walter Bright (5/6) Nov 30 2010 1. That nobody will modify any of the data structure accessed through th...
- Steven Schveighoffer (16/22) Nov 30 2010 If your data is immutable it will not be modified. That is a contract o...
- Walter Bright (2/5) Nov 30 2010 No, it doesn't surprise me. Const on one object does not apply to anothe...
- =?UTF-8?B?IkrDqXLDtG1lIE0uIEJlcmdlciI=?= (10/17) Nov 30 2010 const C c =3D C.theCommonOne;
- Steven Schveighoffer (11/17) Nov 30 2010 So this:
- Walter Bright (3/24) Nov 30 2010 No. There are a lot of things a member "x" could be. The const only appl...
- Steven Schveighoffer (19/40) Nov 30 2010 If you read my previous code, x is instance data. I'll reiterate the
- Walter Bright (8/14) Nov 30 2010 Ok, I see what you mean now. Your code is relying on there being a mutab...
- Steven Schveighoffer (22/36) Dec 01 2010 The literal guarantee is that things aren't modified through that
- Walter Bright (9/26) Dec 01 2010 foo() could only modify c if it has, via some other means, acquired a mu...
- Steven Schveighoffer (26/51) Dec 02 2010 You're assuming the domain of c's state stops at its scope. In fact,
- Bruno Medeiros (4/26) Dec 02 2010 So now you do agree that (D's) const does provide guarantees, right?
- Steven Schveighoffer (12/38) Dec 02 2010 It guarantees something very focused, and possible to work around withou...
- Walter Bright (3/5) Dec 02 2010 Yes, as is well documented, const is a read only view. It is not immutab...
- so (8/10) Nov 30 2010 No need to go that far.
- Walter Bright (6/14) Nov 30 2010 Yup. It's why I think it is worthwhile that I spend time in this thread
- Andrew Wiley (4/19) Nov 30 2010 I've been following this thread on and off, but is there a definition
- Walter Bright (6/9) Nov 30 2010 Const provides a read-only "view" of a reference and anything reachable ...
- spir (11/22) Dec 01 2010 hrough=20
- Walter Bright (3/4) Dec 01 2010 You'd have to write most every function twice, once to take immutable ar...
- Don (4/10) Dec 02 2010 Doesn't 'inout' do almost the same thing?
- Jonathan M Davis (4/15) Dec 02 2010 Except that doesn't inout actually produce multiple versions of the func...
- Don (4/19) Dec 02 2010 No. My understanding is that the constness of the return value is
- Steven Schveighoffer (5/21) Dec 02 2010 inout is different in that parameters cannot implicitly cast to inout. ...
- Jason House (2/19) Dec 02 2010 Inside a function, inout(T) should be considered a subtype of const(T). ...
- Walter Bright (3/14) Dec 02 2010 inout applies at the top level, but you cannot define a struct that has ...
- Jonathan M Davis (14/31) Dec 01 2010 The biggest problem would be that no function could then work on both a ...
- spir (21/36) Dec 01 2010 utable=20
- Bruno Medeiros (31/51) Dec 02 2010 That is not true, trivially. (assuming we are talking about D)
- Peter Alexander (2/3) Nov 29 2010 No one is asking this.
- Walter Bright (12/16) Nov 29 2010 I interpreted this from Jonathon as asking for it:
- Peter Alexander (9/27) Nov 30 2010 That was in response to your claim that C++'s const provides no
- Walter Bright (8/15) Nov 30 2010 Once mutable is introduced, the const attribute becomes no longer useful...
- so (17/19) Nov 29 2010 Sorry but I see nothing but the misconception of const-correctness with ...
- Peter Alexander (6/15) Nov 29 2010 I described the mechanisms of this in the original post. You have a
- Bruno Medeiros (13/20) Dec 02 2010 Indeed, you should not try to use D's const for the use-case of logical
- Simen kjaeraas (11/18) Nov 28 2010 Highly unsafe, but works in the cases you'd expect. Use with extreme
- Walter Bright (8/9) Nov 28 2010 I don't know of any language that provides logical constness as a featur...
- Jonathan M Davis (16/27) Nov 28 2010 The more I look at your comments on the matter, the more I see that logi...
- Walter Bright (11/39) Nov 28 2010 More than that, nothing at all about the mutable qualifier says that the...
- Andrei Alexandrescu (5/14) Nov 28 2010 Logical immutability can be implemented with reasonable guarantees (s we...
- bearophile (29/46) Nov 28 2010 I am not expert about this, so take this cum grano salis. This topic is ...
- Walter Bright (8/15) Nov 28 2010 While const/etc. has many advantages in concurrent programming, your sta...
- bearophile (4/7) Nov 28 2010 I see. I haven't written large programs that use transitive const/immuta...
- so (10/84) Nov 29 2010 I can understand Peter using Matrix just for example but still i have to...
- Peter Alexander (3/9) Nov 30 2010 This may be true for this particular case, but has no bearing on the
- Steven Schveighoffer (41/57) Nov 29 2010 This has been discussed at length on this newsgroup, and I argued for it...
- Andrei Alexandrescu (21/49) Nov 29 2010 In fact it's possible to provide logical const with guarantees. You need...
- Steven Schveighoffer (13/65) Nov 29 2010 This only solves the caching issue (which I agree is a large issue).
- Michel Fortin (8/39) Nov 29 2010 You can pass the drawing context (the window in your case) as an
- Steven Schveighoffer (8/39) Nov 29 2010 That's the "keep the relationship outside the class" solution. I find
- Andrei Alexandrescu (4/18) Nov 29 2010 Absent annotations, would a parameterized type suffice?
- Steven Schveighoffer (20/38) Nov 29 2010 No, the issue is not one of rebinding, it's that you want to call a
- Max Samukha (43/55) Nov 29 2010 I think we could try to devise a library solution. For example, the
- Max Samukha (5/64) Nov 29 2010 "C" is a remnant of the test I used locally. Should be renamed to Widget...
- Steven Schveighoffer (10/69) Nov 29 2010 I agree, a library solution would be ideal.
- Max Samukha (2/81) Nov 29 2010 Fair point.
- Simen kjaeraas (6/12) Nov 29 2010 The thing is, immutable is implicitly castable to const, and immutable
- Jesse Phillips (7/23) Nov 29 2010 In fact, couldn't opAssign be
- Simen kjaeraas (35/43) Nov 29 2010 Indeed it could. This brings me to this code:
- Simen kjaeraas (6/10) Nov 29 2010 Wait. Correction: In the case when the object harboring the mutable
- Jesse Phillips (13/27) Nov 29 2010 Well, the error I get seems to indicate an issue with alias this:
- Jesse Phillips (6/18) Nov 29 2010 Oops didn't give the full context of my assertions (though it is availab...
- Simen kjaeraas (15/16) Nov 29 2010 Some more testing brought this bug to my attention:
- Andrej Mitrovic (14/30) Nov 29 2010 It seems this is a problem with ref. If you use "in" the compiler gets
- Andrej Mitrovic (17/56) Nov 29 2010 Oh this one is even more spectacular:
- Simen kjaeraas (5/21) Nov 29 2010 Don't. It's your first assert, failing as
- Andrej Mitrovic (2/62) Nov 29 2010
- Steven Schveighoffer (8/20) Nov 30 2010 One would have to ensure that data with mutable members never makes it
- Steven Schveighoffer (6/25) Nov 30 2010 I should clarify that this only applies if logical const is a language
- so (21/33) Dec 01 2010 Looking at the replies seems i am the only one that didn't quite get the...
- so (9/9) Dec 01 2010 Since i called it a bad design, i am entitled to introduce a better desi...
- so (12/20) Dec 01 2010 Pfft sorry for that abomination!
- Steven Schveighoffer (5/25) Dec 01 2010 This requires you to store the widget-renderer relationship outside the ...
- Fawzi Mohamed (7/36) Dec 02 2010 indeed that is one of the main things that I want from logical const:
- Jesse Phillips (3/9) Dec 01 2010 I believe it is because cast doesn't do any checking. Cast is meant for ...
- Steven Schveighoffer (13/37) Dec 01 2010 It is where the widget is located, i.e. a physical window where the widg...
- so (3/5) Dec 01 2010 Sorry about that one, it was a misunderstanding on my part, was thinking...
- Jordi (20/80) Dec 01 2010 This entire thread has been very instructive. Thanks everybody.
- Jesse Phillips (15/29) Dec 01 2010 But the compiler is does not enforce it. At least people assume it is tr...
- Steven Schveighoffer (16/29) Dec 01 2010 This isn't practical. .di files are not object files, so they can be
- Bruno Medeiros (5/9) Dec 02 2010 Could you detail a bit what do you mean by logical const == const ? That...
- Steven Schveighoffer (7/14) Dec 02 2010 Here is where I show how logical const already exists, it's just clunky ...
- Walter Bright (3/22) Dec 02 2010 What you're doing is keeping an alternate, mutable reference to each obj...
- Steven Schveighoffer (8/30) Dec 03 2010 No I'm not. I'm keeping a portion of the object in a global AA. I'm no...
- Bruno Medeiros (15/38) Dec 03 2010 The statement "logical const == const" is meaningless really. Please use...
- Bruno Medeiros (19/35) Dec 03 2010 Ok. Well, for starters the "const" functions that mutate the object
- Steven Schveighoffer (25/67) Dec 03 2010 Yes, pure requires special handling. I believe that mutable-marked
- Steven Schveighoffer (6/17) Dec 03 2010 I'll add to this that synchronization issues can be handled. They shoul...
- Bruno Medeiros (13/31) Dec 03 2010 If by "does efficient logical const exist" you mean that we can devise
- Bruno Medeiros (22/41) Dec 03 2010 I define logical const as the ability to specify that operations on a
- Steven Schveighoffer (8/24) Dec 03 2010 the mutable member is not marked as shared. It cannot be accessed on a ...
- Bruno Medeiros (10/35) Dec 07 2010 Oh, I see what you mean. I thought something like this (or similar) work...
- Fawzi Mohamed (35/62) Dec 03 2010 No for me the compiler *cannot* verify logical const. Logical const
- Bruno Medeiros (13/40) Dec 07 2010 If you have a different notion of what logical state is, then yeah,
- Bruno Medeiros (5/7) Dec 03 2010 Fresh from this discussion:
- Don (3/23) Nov 30 2010 What are the use cases for logical const? Are there any other important
- Steven Schveighoffer (9/28) Nov 30 2010 Being able to associate data with an object/struct without owning the
- Sean Kelly (2/14) Nov 30 2010 Hm... can a const object mutate globals?
- Jonathan M Davis (5/18) Nov 30 2010 Yes. All it means for a const function to be const is that its this
- Walter Bright (2/3) Nov 30 2010 Yes.
- Sean Kelly (2/6) Nov 30 2010 That's what I thought. Having a transitively const object modify a glob...
- Sean Kelly (2/5) Nov 30 2010 In C++ I'd say the need to lock a mutex to safely read the const data, b...
- Jonathan M Davis (17/42) Dec 01 2010 No. All Unqual does is help you with template constraints and static ifs...
- Walter Bright (4/13) Dec 01 2010 As far as I know, D is the only language with a workable, enforcable, co...
D does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it? And how do we write generic code when it's practically impossible to determine const-ness from a glance? e.g. getDeterminant looks like it should be const, but wouldn't be if it had caching, so writing generic code that uses getDeterminant would be very difficult.
Nov 20 2010
On 2010-11-20 09:21:04 -0500, Peter Alexander <peter.alexander.au gmail.com> said:D does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it?Using a static associative array as a cache will work: class Matrix { double getDeterminant() const { static double[const(Matrix)] cache; if (auto resultptr = (this in cache)) return *resultptr; auto result = /* expensive calculation */; cache[this] = result; return result; } } and it'll continue to work even if you pass an immutable Matrix to different threads (each thread has its own cache). If you don't pass immutable Matrixes across threads, then you don't gain anything by making the function const. The main reason D has const is to facilitate the usage of immutable. If you don't use immutable for a given type you generally shouldn't bother with const either. But in any case, you always can cast away const if you really need to, just be sure of what you're doing. For instance, doing this breaks thread-safety for immutable Matrixes: auto mutableThis = cast(Matrix)this; mutableThis.cachedDeterminant = /* expensive calculation */; -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Nov 20 2010
On 11/21/2010 02:43 AM, Michel Fortin wrote:On 2010-11-20 09:21:04 -0500, Peter Alexander <peter.alexander.au gmail.com> said:I am also interested in what is the D way of doing this. The associative array approach is not suitable for the case of 4x4 matrices in a 3D application where you can have thousonds of them. At some point, the associative array lookup will be more expensive than the actual calculation. j.D does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it?Using a static associative array as a cache will work: class Matrix { double getDeterminant() const { static double[const(Matrix)] cache; if (auto resultptr = (this in cache)) return *resultptr; auto result = /* expensive calculation */; cache[this] = result; return result; } } and it'll continue to work even if you pass an immutable Matrix to different threads (each thread has its own cache). If you don't pass immutable Matrixes across threads, then you don't gain anything by making the function const. The main reason D has const is to facilitate the usage of immutable. If you don't use immutable for a given type you generally shouldn't bother with const either. But in any case, you always can cast away const if you really need to, just be sure of what you're doing. For instance, doing this breaks thread-safety for immutable Matrixes: auto mutableThis = cast(Matrix)this; mutableThis.cachedDeterminant = /* expensive calculation */;
Nov 20 2010
Hello Jordi,On 11/21/2010 02:43 AM, Michel Fortin wrote:[...]On 2010-11-20 09:21:04 -0500, Peter Alexander <peter.alexander.au gmail.com> said:D does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g.[...]Using a static associative array as a cache will work:I am also interested in what is the D way of doing this. The associative array approach is not suitable for the case of 4x4 matrices in a 3D application where you can have thousonds of them. At some point, the associative array lookup will be more expensive than the actual calculation.Cache eviction?
Nov 27 2010
On 21/11/10 3:32 AM, Jordi wrote:On 11/21/2010 02:43 AM, Michel Fortin wrote:I'm a little worried by the lack of replies. I'm beginning to get the feeling that the D way of doing this is to rewrite the const-ness of your program :-( I guess I'll just stick to not using const for now until this is resolved.On 2010-11-20 09:21:04 -0500, Peter Alexander <peter.alexander.au gmail.com> said:I am also interested in what is the D way of doing this. The associative array approach is not suitable for the case of 4x4 matrices in a 3D application where you can have thousonds of them. At some point, the associative array lookup will be more expensive than the actual calculation. j.D does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it?Using a static associative array as a cache will work: class Matrix { double getDeterminant() const { static double[const(Matrix)] cache; if (auto resultptr = (this in cache)) return *resultptr; auto result = /* expensive calculation */; cache[this] = result; return result; } } and it'll continue to work even if you pass an immutable Matrix to different threads (each thread has its own cache). If you don't pass immutable Matrixes across threads, then you don't gain anything by making the function const. The main reason D has const is to facilitate the usage of immutable. If you don't use immutable for a given type you generally shouldn't bother with const either. But in any case, you always can cast away const if you really need to, just be sure of what you're doing. For instance, doing this breaks thread-safety for immutable Matrixes: auto mutableThis = cast(Matrix)this; mutableThis.cachedDeterminant = /* expensive calculation */;
Nov 28 2010
On Sunday 28 November 2010 03:13:03 Peter Alexander wrote:I'm a little worried by the lack of replies. I'm beginning to get the feeling that the D way of doing this is to rewrite the const-ness of your program :-( I guess I'll just stick to not using const for now until this is resolved.Walter is certainly against logical constness, so it's not like the situation is likely to be set up to do it cleanly. IIRC, Andrei said something at one point about it being possible to do it with a library solution, but I have no clue how. Certainly, looking at the situation, it looks like you either make it truly const or you don't make it const at all. I believe that in theory if you are _certain_ that a const variable is in mutable memory and is really const and not immutable, then you can safely cast away the constness and do whatever it is that you need to do. But you don't usually have such guarantees. If you play games with global or class variables where the variable that you want to be mutable in spite of the object's constness is not actually in the object, then you should be able to get around the problem that way, but then the variable isn't really part of the object anymore, and it throws purity out the window. So, _I_ am certainly not aware of a good solution, but if you play games, you should be able to get _something_ working. Overall though, I think that the lesson is that you should really be making const functions truly const and not try and deal with logical const at all. But honestly, given some of the issues with const - particularly the lack of const-correctness for Object and the fact that inout is completely broken - I think that most people are currently avoiding const, so you're not going to have very many people who have messed with the problem and found an appropriate solution. Personally, I've just settled for either making functions truly const or giving up on their constness (much as I _hate_ to do that). - Jonathan M Davis
Nov 28 2010
On 28/11/10 11:25 AM, Jonathan M Davis wrote:So, _I_ am certainly not aware of a good solution, but if you play games, you should be able to get _something_ working. Overall though, I think that the lesson is that you should really be making const functions truly const and not try and deal with logical const at all.Thanks for the reply Jonathan. Your wording here worries me a little. You seem to be implying that logical const is some sort of arbitrary style choice, rather than a need that arises from real-life scenarios. The only way you can write D-style const correct code is if you possess a crystal ball that will tell you whether you plan to cache accessors in the future. No real person can do that, so for all practical purposes, writing const correct code in D is impossible. Again, the Matrix example: the *only* way to implement caching is to make getDeterminant() a non-const method. Great. That would be fine if the change was nice and localised, but what about other functions that use Matrix? e.g. Matrix getInverse(const Matrix m) { ... } Getting the inverse of a matrix requires that you calculate its determinant, so this function won't work anymore because I can't call getDeterminant() with a const Matrix. The only choice I have now is to make getInverse take a non-const Matrix: Matrix getInverse(Matrix m) { ... } Solves the problem, but now I've created a new one: getInverse now has complete write access to my matrix, so when I do something as harmless as: Matrix inv = getInverse(myMatrix); This innocent call has now lost the guarantee that myMatrix will come out unmodified. But wait, there's more: class GameObject { ... const Matrix getWorldTransform() const { return m_worldTransform; } Matrix m_worldTransform; } void renderGameObject(const GameObject obj) { ... } What happens if renderGameObject needs to use getInverse, or getDeterminant? 1. I have to change getWorldTransform to be a non-const function that returns a non-const Matrix. 2. renderGameObject needs to be changed to receive a non-const GameObject. 3. I have lost any guarantee that rendering my GameObjects won't destroy them... Surely I'm not the only person that finds something *incredibly* wrong with this!?
Nov 28 2010
On Sunday 28 November 2010 06:50:53 Peter Alexander wrote:On 28/11/10 11:25 AM, Jonathan M Davis wrote:s,So, _I_ am certainly not aware of a good solution, but if you play game=Oh, I agree that the lack of logical const is a problem, but allowing for=20 mutable member variables within a const object is a whole in the const syst= em,=20 and Walter, at least, is convinced that doing so is completely broken.=20 Certainly, because of immutable in D, I have to agree that in the general c= ase,=20 it wouldn't work. Without immutable, you could presumably get something sim= ilar=20 to what C++ has (if you could ever get Walter to go for it), but once you h= ave=20 immutable, that doesn't work anymore, since anything that's const could act= ually=20 be immutable, and then it's _definitely_ impossible to change it. =46rom what I can tell of the current situation, either you make something = const=20 and guarantee that it's actually constant (which would tend to mean that yo= u're=20 _not_ going to get things like caching - which honestly, is something that = most=20 objects don't need anyway - though obviously there are those where it can h= elp a=20 great deal), or you maintain the state that you want outside the object and= =20 manipulate that. I believe that someone else suggested have an associative = array=20 as a cache outside of the object. That way, you could grab the value from t= here=20 instead. And if you had to calculate a new value, you'd be free to stick it= in=20 the external associate array (or whatever you wanted to use to hold the cac= he). Regardless, with how D works, in the general case, if you want something to= be=20 const, you're going to have to actually let it be const. That means no logi= cal=20 const unless you play games with stuff outside of the object (which=20 unfortunately, throws purity out the window). It's certainly a problem, but= it's=20 not a problem with most objects, and it's not like we're going to talk Walt= er=20 into using mutable like in D. And with immutable in the language, you could= n't=20 do it anyway. So, yes it's a problem. But as far as I can tell, it's one that we're stuck= =20 with. =2D Jonathan M Davisyou should be able to get _something_ working. Overall though, I think that the lesson is that you should really be making const functions truly const and not try and deal with logical const at all.=20 Thanks for the reply Jonathan. =20 Your wording here worries me a little. You seem to be implying that logical const is some sort of arbitrary style choice, rather than a need that arises from real-life scenarios. =20 The only way you can write D-style const correct code is if you possess a crystal ball that will tell you whether you plan to cache accessors in the future. No real person can do that, so for all practical purposes, writing const correct code in D is impossible. =20 Again, the Matrix example: the *only* way to implement caching is to make getDeterminant() a non-const method. Great. That would be fine if the change was nice and localised, but what about other functions that use Matrix? =20 e.g. Matrix getInverse(const Matrix m) { ... } =20 Getting the inverse of a matrix requires that you calculate its determinant, so this function won't work anymore because I can't call getDeterminant() with a const Matrix. The only choice I have now is to make getInverse take a non-const Matrix: =20 Matrix getInverse(Matrix m) { ... } =20 Solves the problem, but now I've created a new one: getInverse now has complete write access to my matrix, so when I do something as harmless as: =20 Matrix inv =3D getInverse(myMatrix); =20 This innocent call has now lost the guarantee that myMatrix will come out unmodified. =20 But wait, there's more: =20 class GameObject { ... const Matrix getWorldTransform() const { return m_worldTransform; } Matrix m_worldTransform; } =20 void renderGameObject(const GameObject obj) { ... } =20 What happens if renderGameObject needs to use getInverse, or getDeterminant? =20 1. I have to change getWorldTransform to be a non-const function that returns a non-const Matrix. =20 2. renderGameObject needs to be changed to receive a non-const GameObject. =20 3. I have lost any guarantee that rendering my GameObjects won't destroy them... =20 Surely I'm not the only person that finds something *incredibly* wrong with this!?
Nov 28 2010
On 11/28/10 9:45 AM, Jonathan M Davis wrote:Oh, I agree that the lack of logical const is a problem, but allowing for mutable member variables within a const object is a whole in the const system, and Walter, at least, is convinced that doing so is completely broken. Certainly, because of immutable in D, I have to agree that in the general case, it wouldn't work. Without immutable, you could presumably get something similar to what C++ has (if you could ever get Walter to go for it), but once you have immutable, that doesn't work anymore, since anything that's const could actually be immutable, and then it's _definitely_ impossible to change it. From what I can tell of the current situation, either you make something const and guarantee that it's actually constant (which would tend to mean that you're _not_ going to get things like caching - which honestly, is something that most objects don't need anyway - though obviously there are those where it can help a great deal), or you maintain the state that you want outside the object and manipulate that. I believe that someone else suggested have an associative array as a cache outside of the object. That way, you could grab the value from there instead. And if you had to calculate a new value, you'd be free to stick it in the external associate array (or whatever you wanted to use to hold the cache). Regardless, with how D works, in the general case, if you want something to be const, you're going to have to actually let it be const. That means no logical const unless you play games with stuff outside of the object (which unfortunately, throws purity out the window). It's certainly a problem, but it's not a problem with most objects, and it's not like we're going to talk Walter into using mutable like in D. And with immutable in the language, you couldn't do it anyway. So, yes it's a problem. But as far as I can tell, it's one that we're stuck with.The library solution to logical constness would gravitate around a union with a const (or immutable) and a regular field of the same type: union { T before; const T after; } Andrei
Nov 28 2010
On 28/11/10 4:37 PM, Andrei Alexandrescu wrote:The library solution to logical constness would gravitate around a union with a const (or immutable) and a regular field of the same type: union { T before; const T after; } AndreiI must be missing something. Surely, when inside a const method, you still can't modify a union, whether you access a const member of the union or not? Could you elaborate?
Nov 28 2010
On 11/28/10 1:09 PM, Peter Alexander wrote:On 28/11/10 4:37 PM, Andrei Alexandrescu wrote:Sorry, that was wrong. I was thinking of a lazily initialized field within an otherwise non-const object. AndreiThe library solution to logical constness would gravitate around a union with a const (or immutable) and a regular field of the same type: union { T before; const T after; } AndreiI must be missing something. Surely, when inside a const method, you still can't modify a union, whether you access a const member of the union or not? Could you elaborate?
Nov 28 2010
On 28/11/10 3:45 PM, Jonathan M Davis wrote:Oh, I agree that the lack of logical const is a problem, but allowing for mutable member variables within a const object is a whole in the const system, and Walter, at least, is convinced that doing so is completely broken.Mutable member variables are only a hole in the const system if the const system is overly strict about what const is. In C++, I view mutable as an extension to the const system that allows one to define member variables that are attached to an object, but ultimately do not affect the observable state of the object. You mentioned having an external cache of Matrix determinants. One way to view mutable would be to see it as an automation and optimisation of this external cache (optimised because it stores the cache right inside the object, rather than in a slow external hash table).Certainly, because of immutable in D, I have to agree that in the general case, it wouldn't work. Without immutable, you could presumably get something similar to what C++ has (if you could ever get Walter to go for it), but once you have immutable, that doesn't work anymore, since anything that's const could actually be immutable, and then it's _definitely_ impossible to change it.Then perhaps what is needed is an intermediate level of immutability. immutable - Data will never, ever change, through any reference. const - Data will never change through this reference. newlevel - Data will never logically change through this reference. The idea here is that you would use this newlevel normally (like you use const normally) and only use this new const when you actually desire strict const-ness e.g. when you are using the resource concurrently. I'll happily admit that it's starting to get on the complex side, but as far as I'm concerned, const in D as it is now is simply unusable. I will happily write const correct code, but not when it means giving up the possibility of writing memoized functions.
Nov 28 2010
On 11/28/10 1:01 PM, Peter Alexander wrote:On 28/11/10 3:45 PM, Jonathan M Davis wrote:Mutable is a disaster with read-write locks. Without mutable, you could surround a C++ object with a read-write lock and count on const member functions being lockable with read locks. With mutable, all that guarantee is out the window.Oh, I agree that the lack of logical const is a problem, but allowing for mutable member variables within a const object is a whole in the const system, and Walter, at least, is convinced that doing so is completely broken.Mutable member variables are only a hole in the const system if the const system is overly strict about what const is. In C++, I view mutable as an extension to the const system that allows one to define member variables that are attached to an object, but ultimately do not affect the observable state of the object.You mentioned having an external cache of Matrix determinants. One way to view mutable would be to see it as an automation and optimisation of this external cache (optimised because it stores the cache right inside the object, rather than in a slow external hash table).This is probably not going to get a lot of community traction :o).Certainly, because of immutable in D, I have to agree that in the general case, it wouldn't work. Without immutable, you could presumably get something similar to what C++ has (if you could ever get Walter to go for it), but once you have immutable, that doesn't work anymore, since anything that's const could actually be immutable, and then it's _definitely_ impossible to change it.Then perhaps what is needed is an intermediate level of immutability. immutable - Data will never, ever change, through any reference. const - Data will never change through this reference. newlevel - Data will never logically change through this reference.The idea here is that you would use this newlevel normally (like you use const normally) and only use this new const when you actually desire strict const-ness e.g. when you are using the resource concurrently. I'll happily admit that it's starting to get on the complex side, but as far as I'm concerned, const in D as it is now is simply unusable. I will happily write const correct code, but not when it means giving up the possibility of writing memoized functions.I think you are able to implement memoization in a library. At any rate, I think "X is unusable because I can't use it with obscure idiom Y" is a dangerous state of mind. Of course memoization is more or less obscure depending on whom you ask, but objective speaking it's simply not that frequent, and definitely not something you can't achieve in a variety of ways. Andrei
Nov 28 2010
Andrei Alexandrescu:Of course memoization is more or less obscure depending on whom you ask, but objective speaking it's simply not that frequent, and definitely not something you can't achieve in a variety of ways.In the air there is the idea of allowing implicit cast of the result of strongly pure functions to immutable values. If you add to this some way to perform catching in a pure way you are good. In another answer I have suggested a built-in memoize that doesn't change the purity of the function it is applied to (so a strongly immutable function too may be memoized and keeps being strongly pure). If that built-in memoize is not good or efficient enough, then you may think about implementing it with library code. Currently it is not possible, because it can't be pure. Adding a kind of trusted_pure" to solve that is possible, but not tidy & safe. So is it possible to invent a way to define a safe pure memoize in library code? I don't know, it seems interesting :-) Bye, bearophile
Nov 28 2010
In another answer I have suggested a built-in memoize that doesn't change the purity of the function it is applied to (so a strongly immutable function too may be memoized and keeps being strongly pure).Ignore that, it's better to keep allow the usage of memoize on strongly pure functions only. Bye, bearophile
Nov 28 2010
On 28/11/10 7:33 PM, Andrei Alexandrescu wrote:Mutable is a disaster with read-write locks. Without mutable, you could surround a C++ object with a read-write lock and count on const member functions being lockable with read locks. With mutable, all that guarantee is out the window.I agree with this, but it could work with an extra storage class. It's clear to me that logical const and binary const are two separate, but useful concepts. In my opinion, choosing one in exclusion of the other (as C++ and D have done) is not a sensible way to approach the issue.This is probably not going to get a lot of community traction :o).Yeah, but I figured it would be a good idea to at least propose a solution rather than just say "this sucks!" without any suggestion of a better way of doing things.I think you are able to implement memoization in a library. At any rate, I think "X is unusable because I can't use it with obscure idiom Y" is a dangerous state of mind. Of course memoization is more or less obscure depending on whom you ask, but objective speaking it's simply not that frequent, and definitely not something you can't achieve in a variety of ways.I work as a game developer, so performance means a lot to me, and (unfortunately for me it seems) memoization is not all that obscure in the kinds of programs I write :-(
Nov 28 2010
Peter Alexander wrote:In C++, I view mutable as an extension to the const system that allows one to define member variables that are attached to an object, but ultimately do not affect the observable state of the object.There is no way a C++ compiler can statically verify this. It's pure convention.immutable - Data will never, ever change, through any reference. const - Data will never change through this reference. newlevel - Data will never logically change through this reference.The problem with newlevel is (to repeat myself) it is unverifiable.The idea here is that you would use this newlevel normally (like you use const normally) and only use this new const when you actually desire strict const-ness e.g. when you are using the resource concurrently. I'll happily admit that it's starting to get on the complex side, but as far as I'm concerned, const in D as it is now is simply unusable. I will happily write const correct code, but not when it means giving up the possibility of writing memoized functions.On the other hand, the C++ notion of const makes it impossible to have nice things like verifiably pure functions. You can write memoized functions in D, you just can't label them as const. You'll be relying on convention. Memoized (logically const) functions are not verifiable in C++, either, so you are not technically worse off.
Nov 28 2010
Walter:You can write memoized functions in D, you just can't label them as const. You'll be relying on convention. Memoized (logically const) functions are not verifiable in C++, either, so you are not technically worse off.I agree with what you say about C++ here. But in theory it's possible to define a built-in memoize that's usable on strongly pure functions (and const member functions that are strongly pure), that keeps them strongly pure. In some functional languages this memoization is automatic, but in D it's probably better to keep it on request, as CTFE. A pure memoized function is useful because you may call it from another pure function, and because pure functions work well with immutability (so you are able to assign to an immutable variable even the result of a memoized strongly pure function). Bye, bearophile
Nov 28 2010
On 28/11/10 9:35 PM, Walter Bright wrote:You can write memoized functions in D, you just can't label them as const. You'll be relying on convention. Memoized (logically const) functions are not verifiable in C++, either, so you are not technically worse off.I am technically worse off, just not from the compilers point of view. I see your point about C++ const being a convention, but that doesn't change the fact that it is still very useful, even if it useless for the compiler. If I give some const object to a function: void render(const GameObject&); GameObject obj; render(obj); I can be sure that my object will come back unmodified. That it is the primary purpose of const. Like you said, it allows you to reason about your programs. Yes, GameObject could be unreasonably mutilated by careless use of mutable, but in practice that simply doesn't happen.
Nov 28 2010
Peter Alexander:If I give some const object to a function: void render(const GameObject&); GameObject obj; render(obj); I can be sure that my object will come back unmodified.render() is free to modify the objects contained inside GameObject, because that const isn't transitive.Yes, GameObject could be unreasonably mutilated by careless use of mutable, but in practice that simply doesn't happen.Likewise, in Python there is no const attribute, yet those program often don't have bugs. The D transitive immutability is more rigid than the C++ const, it has a higher usage cost for the programmer, but it gives you back a stronger enforced semantics of immutability (strong enough for functional parallelism). Bye, bearophile
Nov 28 2010
On 11/28/2010 17:29, bearophile wrote:Peter Alexander:This is simply not true. Observe: class InnerObject { public: f(); }; class GameObject { public: void f() const { this->inner.f(); // Error: const object modified. } private: InnerObject inner; }; In C++, const is transitive for direct members. It is only intransitive for pointer/references, and even these can be made transitive through the use of a transitive-const smart pointer class. -- Rainer Deyke - rainerd eldwood.comIf I give some const object to a function: void render(const GameObject&); GameObject obj; render(obj); I can be sure that my object will come back unmodified.render() is free to modify the objects contained inside GameObject, because that const isn't transitive.
Nov 28 2010
Rainer Deyke:In C++, const is transitive for direct members. It is only intransitive for pointer/references,Right, I have forgotten to specify that.and even these can be made transitive through the use of a transitive-const smart pointer class.I didn't know this. But of course you can do all you want in C++ :-) Bye and thank you, bearophile
Nov 28 2010
Rainer Deyke wrote:In C++, const is transitive for direct members. It is only intransitive for pointer/references, and even these can be made transitive through the use of a transitive-const smart pointer class.You'd have to do *all* of your pointer/ref members that way, with no compilation errors if you miss any spots. Once again, it's a convention, not a guarantee.
Nov 28 2010
Peter Alexander wrote:On 28/11/10 9:35 PM, Walter Bright wrote:Nope, for 5 reasons: 1. use of mutable 2. it's legal to cast away const and modify the data anyway 3. if your object is more than a simple value type, for example if it contains references, the const doesn't apply to them. In particular, if you use the PIMPL idiom, all your const's are worthless. 4. render() may have another mutable reference to that same obj 5. another thread may trash obj's contents at any moment D offers compile time guarantees against 1,2,3, and 5. 4 can be covered if you use pure functions or immutable data.You can write memoized functions in D, you just can't label them as const. You'll be relying on convention. Memoized (logically const) functions are not verifiable in C++, either, so you are not technically worse off.I am technically worse off, just not from the compilers point of view. I see your point about C++ const being a convention, but that doesn't change the fact that it is still very useful, even if it useless for the compiler. If I give some const object to a function: void render(const GameObject&); GameObject obj; render(obj); I can be sure that my object will come back unmodified.That it is the primary purpose of const. Like you said, it allows you to reason about your programs. Yes, GameObject could be unreasonably mutilated by careless use of mutable, but in practice that simply doesn't happen.Sure, if you carefully follow the convention. The problem happens when you a) make a mistake following the convention or b) have to deal with other programmers who may not be so careful. How would you know?
Nov 28 2010
I am technically worse off, just not from the compilers point of view. I see your point about C++ const being a convention, but that doesn't change the fact that it is still very useful, even if it useless for the compiler. If I give some const object to a function: void render(const GameObject&); GameObject obj; render(obj); I can be sure that my object will come back unmodified. That it is the primary purpose of const. Like you said, it allows you to reason about your programs. Yes, GameObject could be unreasonably mutilated by careless use of mutable, but in practice that simply doesn't happen.That is wrong, you can't be sure that will come back unmodified in C++. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Nov 29 2010
Walter Bright <newshound2 digitalmars.com> wrote:As long as it is transitive, I believe it is verifiable: struct S { int n; } struct foo { int n; foo* p; newlevel S s; newlevel int m; void bar( ) newlevel { m++; // works, m is newlevel n++; // compile-time error, n is const for newlevel this s.n = 3; // Works, s.n is newlevel p.n = 4; // compile-time error, p is newlevel, p.n is const for // newlevel this p.m++; // Works, p.m is newlevel } void baz( ) const { m++; // compile-time error, m is const for const this } } void qux( newlevel foo f ) {} void quux( ref foo f ) { f.n++; } void main() { const foo f; f.bar( ); // compile-time error, f is const, cannot call newlevel // functions on it qux( f ); // compile-time error, const(foo) cannot be implicitly // cast to newlevel(foo) newlevel foo g; quux( g ); // compile-time error, newlevel(foo) cannot be implicitly // cast to foo } This leaves const as a strong, transitive guarantee, and newlevel as a transitive guarantee that only newlevel members may be changed. I am not convinced that such an extension of the type system should be made, but I believe you are wrong in that it is not verifiable. but please, do show me my mistake. -- Simenimmutable - Data will never, ever change, through any reference. const - Data will never change through this reference. newlevel - Data will never logically change through this reference.The problem with newlevel is (to repeat myself) it is unverifiable.
Nov 29 2010
Simen kjaeraas wrote:I am not convinced that such an extension of the type system should be made, but I believe you are wrong in that it is not verifiable. but please, do show me my mistake.It is not verifiable because nothing prevents you from assigning: m = random(); That is not logical const.
Nov 29 2010
Walter Bright <newshound2 digitalmars.com> wrote:Simen kjaeraas wrote:Pardon my stubbornness (and perhaps ignorance), but how is this any less logical const than is 'm = 4;' or 'm = n * 3 + cast(int)phaseOfMoon();'? -- SimenI am not convinced that such an extension of the type system should be made, but I believe you are wrong in that it is not verifiable. but please, do show me my mistake.It is not verifiable because nothing prevents you from assigning: m = random(); That is not logical const.
Nov 29 2010
Simen kjaeraas wrote:Walter Bright <newshound2 digitalmars.com> wrote:Logical const means the same value is returned every time, not a different one.Simen kjaeraas wrote:Pardon my stubbornness (and perhaps ignorance), but how is this any less logical const than is 'm = 4;' or 'm = n * 3 + cast(int)phaseOfMoon();'?I am not convinced that such an extension of the type system should be made, but I believe you are wrong in that it is not verifiable. but please, do show me my mistake.It is not verifiable because nothing prevents you from assigning: m = random(); That is not logical const.
Nov 29 2010
Walter Bright <newshound2 digitalmars.com> wrote:Logical const means the same value is returned every time, not a different one.So you would have only pure functions work with logical const? The new keyword 'newlevel' defined earlier could certainly include pure. Also, the compiler could have the automatically generated opEquals ignore fields marked 'newlevel'. This way, logical constness is preserved, no? Going further, one could define mutable state only in (pure) member functions. This state would actually be stored in the object, but would be inaccessible to other member functions, and would not be considered by opEquals. I believe this is the closest we could come to enforcing logical constness. -- Simen
Nov 29 2010
Simen kjaeraas wrote:Walter Bright <newshound2 digitalmars.com> wrote:No, I said that logical const is not verifiable by the compilerLogical const means the same value is returned every time, not a different one.So you would have only pure functions work with logical const?Going further, one could define mutable state only in (pure) member functions. This state would actually be stored in the object, but would be inaccessible to other member functions, and would not be considered by opEquals. I believe this is the closest we could come to enforcing logical constness.It still is not verifiable. That's why logical constness is not a language issue, it is a convention.
Nov 29 2010
Walter Bright <newshound2 digitalmars.com> wrote:It still is not verifiable. That's why logical constness is not a language issue, it is a convention.And finally I understood what you meant. Sorry about this taking a while. -- Simen
Nov 30 2010
Simen kjaeraas wrote:Walter Bright <newshound2 digitalmars.com> wrote:I know it's a tough issue, and worth the effort at trying to explain it.It still is not verifiable. That's why logical constness is not a language issue, it is a convention.And finally I understood what you meant. Sorry about this taking a while.
Nov 30 2010
On 30/11/10 2:00 AM, Walter Bright wrote:Logical const means the same value is returned every time, not a different one.No, it doesn't. Not even D's const can guarantee that: struct Foo { int foo() const { return random(); } } pure is related to const, but they are not the same thing.
Nov 30 2010
On 2010-11-28 09:50:53 -0500, Peter Alexander <peter.alexander.au gmail.com> said:Surely I'm not the only person that finds something *incredibly* wrong with this!?I don't find it wrong, but it can certainly be an annoyance. Most other languages manage without any concept of 'const' at all. How do they do it? One idiom is to implement logical constness as part of the type. For instance, you could have two classes: class Matrix { private float[4][4] values; float getValue(uint i, uint j) { return values[i][j]; } } class MutableMatrix : Matrix { void setValue(uint i, uint j, float value) { values[i][j] = value; } } The first class is *logically* immutable, in the sense that it contains only accessors. The derived one adds methods capable of changing the content. If a function accepts a Matrix, it can't change the matrix's logical content even if you give it a MutableMatrix. The method signatures thus become: Matrix getInverse(Matrix m) { ... } Short of casting m to a MutableMatrix, logical constness is enforced. Here's the mutable parameter version: void invertInPlace(MutableMatrix m) { ... } And now you have your constness garenties. Note that you can implement a similar pattern at the struct level using "alias X this": struct Matrix { private float[4][4] values; float getValue(uint i, uint j) { return values[i][j]; } } struct MutableMatrix { Matrix matrix; alias matrix this; void setValue(uint i, uint j, float value) { values[i][j] = value; } } -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Nov 28 2010
Peter Alexander wrote:Solves the problem, but now I've created a new one: getInverse now has complete write access to my matrix, so when I do something as harmless as: Matrix inv = getInverse(myMatrix); This innocent call has now lost the guarantee that myMatrix will come out unmodified.Carefully examining C++ const reveals that it offers no protection at all against legally modifying a supposedly const type. It's so bad that the DMC++ optimizer completely ignores const, and relies on data flow analysis instead. Const in C++ offers some level of type checking, but beyond that it's more of a convention than a static guarantee. In fact, "logical constness" is a fraud anyway because the underlying data isn't constant at all, one is completely relying on convention. There's nothing at all preventing a supposedly logical-const-correct function from returning a different value every time it is called, and no way for the compiler to detect this. D's const is meant to provide static guarantees, not an unverifiable convention.
Nov 28 2010
On Sun, 28 Nov 2010 16:27:20 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Peter Alexander wrote:This is complete BS. logical const in C++ is not the same as what logical const in D would be. The transitive guarantee is much more to credit for allowing optimizations and static analysis than the omission of mutable members. How does the fact that an Object's monitor is mutable affect the optimizer? In fact, how can *any* const or immutable function be optimized any differently than a mutable function? The only optimizations I can think of are for pure functions. And a pure function with immutable becomes significantly less optimizable if some fields might be mutable. Couple that with the fact that a class may define new mutable members, it would be impossible to statically prove whether a class is completely immutable or logically immutable. But it would be good enough to allow logically-const/immutable to be different than const/immutable. The problem is that this severely complicates the const system. If you are going to argue this case, please do not use C++ as a straw man, it's not the same as D. -SteveSolves the problem, but now I've created a new one: getInverse now has complete write access to my matrix, so when I do something as harmless as: Matrix inv = getInverse(myMatrix); This innocent call has now lost the guarantee that myMatrix will come out unmodified.Carefully examining C++ const reveals that it offers no protection at all against legally modifying a supposedly const type. It's so bad that the DMC++ optimizer completely ignores const, and relies on data flow analysis instead. Const in C++ offers some level of type checking, but beyond that it's more of a convention than a static guarantee. In fact, "logical constness" is a fraud anyway because the underlying data isn't constant at all, one is completely relying on convention. There's nothing at all preventing a supposedly logical-const-correct function from returning a different value every time it is called, and no way for the compiler to detect this.
Nov 29 2010
On Mon, 29 Nov 2010 10:13:01 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:If you are going to argue this case, please do not use C++ as a straw man, it's not the same as D.Reading more of the posts in this thread, it appears many are using C++ as the straw man in their case *for* mutable, so I understand your use of it in refuting it. So this statement is unfair. -Steve
Nov 29 2010
Steven Schveighoffer wrote:I don't understand your comment. In C++ you can write: struct S { mutable int m; int getM() { m = rand(); return m; } }; Where's the "logical const"? It returns a different value every time. Logical const is not a feature of C++.In fact, "logical constness" is a fraud anyway because the underlying data isn't constant at all, one is completely relying on convention. There's nothing at all preventing a supposedly logical-const-correct function from returning a different value every time it is called, and no way for the compiler to detect this.This is complete BS. logical const in C++ is not the same as what logical const in D would be. The transitive guarantee is much more to credit for allowing optimizations and static analysis than the omission of mutable members. How does the fact that an Object's monitor is mutable affect the optimizer? In fact, how can *any* const or immutable function be optimized any differently than a mutable function? The only optimizations I can think of are for pure functions. And a pure function with immutable becomes significantly less optimizable if some fields might be mutable. Couple that with the fact that a class may define new mutable members, it would be impossible to statically prove whether a class is completely immutable or logically immutable. But it would be good enough to allow logically-const/immutable to be different than const/immutable. The problem is that this severely complicates the const system. If you are going to argue this case, please do not use C++ as a straw man, it's not the same as D.
Nov 29 2010
On Mon, 29 Nov 2010 14:33:48 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Steven Schveighoffer wrote:You missed a const decorator there. But in any case, const (even the non-logical variety) does not guarantee purity. Only pure functions do that (always return the same value for the same input) Counter-example: struct S { int m; int getM() const {return rand();} } -SteveI don't understand your comment. In C++ you can write: struct S { mutable int m; int getM() { m = rand(); return m; } }; Where's the "logical const"? It returns a different value every time. Logical const is not a feature of C++.In fact, "logical constness" is a fraud anyway because the underlying data isn't constant at all, one is completely relying on convention. There's nothing at all preventing a supposedly logical-const-correct function from returning a different value every time it is called, and no way for the compiler to detect this.This is complete BS. logical const in C++ is not the same as what logical const in D would be. The transitive guarantee is much more to credit for allowing optimizations and static analysis than the omission of mutable members. How does the fact that an Object's monitor is mutable affect the optimizer? In fact, how can *any* const or immutable function be optimized any differently than a mutable function? The only optimizations I can think of are for pure functions. And a pure function with immutable becomes significantly less optimizable if some fields might be mutable. Couple that with the fact that a class may define new mutable members, it would be impossible to statically prove whether a class is completely immutable or logically immutable. But it would be good enough to allow logically-const/immutable to be different than const/immutable. The problem is that this severely complicates the const system. If you are going to argue this case, please do not use C++ as a straw man, it's not the same as D.
Nov 29 2010
Steven Schveighoffer wrote:But in any case, const (even the non-logical variety) does not guarantee purity. Only pure functions do that (always return the same value for the same input)Right, but C++ doesn't have purity either. I was trying to make the point that C++ does not have a "logical const" language feature. It's only a convention.
Nov 29 2010
On Mon, 29 Nov 2010 14:52:54 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Steven Schveighoffer wrote:Yeah, but saying because a const function in C++ might return a different value because of logical const is, well, completely wrong :) Nobody is expecting const to guarantee purity. Even a D const function cannot make any assumptions about data that is const, because another alias could change it. Having a logical const feature in D would not be a convention, it would be enforced, as much as const is enforced. I don't understand why issues with C++ const or C++'s mutable feature makes any correlations on how a D logical const system would fare. C++ const is not D const, not even close. -SteveBut in any case, const (even the non-logical variety) does not guarantee purity. Only pure functions do that (always return the same value for the same input)Right, but C++ doesn't have purity either. I was trying to make the point that C++ does not have a "logical const" language feature. It's only a convention.
Nov 29 2010
Steven Schveighoffer wrote:Having a logical const feature in D would not be a convention, it would be enforced, as much as const is enforced. I don't understand why issues with C++ const or C++'s mutable feature makes any correlations on how a D logical const system would fare. C++ const is not D const, not even close.Because people coming from C++ ask "why not do it like C++'s?"
Nov 29 2010
On Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Steven Schveighoffer wrote:I don't get it. A way to make a field mutable in a transitively-const system is syntactically similar to C++, but it's not the same. Having a logical-const feature in D does not devolve D's const into C++'s const. If anything it's just a political problem. -SteveHaving a logical const feature in D would not be a convention, it would be enforced, as much as const is enforced. I don't understand why issues with C++ const or C++'s mutable feature makes any correlations on how a D logical const system would fare. C++ const is not D const, not even close.Because people coming from C++ ask "why not do it like C++'s?"
Nov 29 2010
Steven Schveighoffer wrote:On Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Having mutable members destroys any guarantees that const provides. That's not political. And, I repeat, having a mutable type qualifier DOES NOT make logical const a language feature. This is why discussion and understanding of C++'s const system is so important - people impute characteristics into it that it simply does not have.Steven Schveighoffer wrote:I don't get it. A way to make a field mutable in a transitively-const system is syntactically similar to C++, but it's not the same. Having a logical-const feature in D does not devolve D's const into C++'s const. If anything it's just a political problem.Having a logical const feature in D would not be a convention, it would be enforced, as much as const is enforced. I don't understand why issues with C++ const or C++'s mutable feature makes any correlations on how a D logical const system would fare. C++ const is not D const, not even close.Because people coming from C++ ask "why not do it like C++'s?"
Nov 29 2010
On 30-nov-10, at 00:04, Walter Bright wrote:Steven Schveighoffer wrote:logical const is useful for lazy functions and memoization, and if implemented correctly it is perfectly safe. As I said in an older discussions, to have it with the current system all that is needed is some guarantees that the compiler will not disallow "unsafe" changes (by moving to read only memory for example)in some cases. For example casted mutable types, so that casting to mutable works. FawziOn Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright <newshound2 digitalmars.comHaving mutable members destroys any guarantees that const provides. That's not political. And, I repeat, having a mutable type qualifier DOES NOT make logical const a language feature. This is why discussion and understanding of C++'s const system is so important - people impute characteristics into it that it simply does not have.wrote: Steven Schveighoffer wrote:I don't get it. A way to make a field mutable in a transitively- const system is syntactically similar to C++, but it's not the same. Having a logical-const feature in D does not devolve D's const into C++'s const. If anything it's just a political problem.Having a logical const feature in D would not be a convention, it would be enforced, as much as const is enforced. I don't understand why issues with C++ const or C++'s mutable feature makes any correlations on how a D logical const system would fare. C++ const is not D const, not even close.Because people coming from C++ ask "why not do it like C++'s?"
Nov 29 2010
Fawzi Mohamed wrote:logical const is useful for lazy functions and memoization, and if implemented correctly it is perfectly safe. As I said in an older discussions, to have it with the current system all that is needed is some guarantees that the compiler will not disallow "unsafe" changes (by moving to read only memory for example)in some cases. For example casted mutable types, so that casting to mutable works.D allows escape from the type system, but the programmer who does that loses the guarantees, and it's up to him to ensure that the result works. String literals, for example, are going to often wind up in read only memory.
Nov 29 2010
On 11/30/2010 02:35 AM, Walter Bright wrote:Fawzi Mohamed wrote:The problem is that logical const has many perfectly valid use cases. You cannot simply tell people: "Don't use it. It is a fraud". They will still be using casts or not using D. As casting away const is undefined behavior in D, the outcome will be every second non-trivial D program relying on undefined behavior.logical const is useful for lazy functions and memoization, and if implemented correctly it is perfectly safe. As I said in an older discussions, to have it with the current system all that is needed is some guarantees that the compiler will not disallow "unsafe" changes (by moving to read only memory for example)in some cases. For example casted mutable types, so that casting to mutable works.D allows escape from the type system, but the programmer who does that loses the guarantees, and it's up to him to ensure that the result works. String literals, for example, are going to often wind up in read only memory.
Nov 30 2010
On 11/30/10 5:25 AM, Max Samukha wrote:On 11/30/2010 02:35 AM, Walter Bright wrote:I'm not seeing half of non-trivial C++ programs using mutable. AndreiFawzi Mohamed wrote:The problem is that logical const has many perfectly valid use cases. You cannot simply tell people: "Don't use it. It is a fraud". They will still be using casts or not using D. As casting away const is undefined behavior in D, the outcome will be every second non-trivial D program relying on undefined behavior.logical const is useful for lazy functions and memoization, and if implemented correctly it is perfectly safe. As I said in an older discussions, to have it with the current system all that is needed is some guarantees that the compiler will not disallow "unsafe" changes (by moving to read only memory for example)in some cases. For example casted mutable types, so that casting to mutable works.D allows escape from the type system, but the programmer who does that loses the guarantees, and it's up to him to ensure that the result works. String literals, for example, are going to often wind up in read only memory.
Nov 30 2010
On 11/30/2010 05:39 PM, Andrei Alexandrescu wrote:On 11/30/10 5:25 AM, Max Samukha wrote:That was a hyperbole. But logical const is obviously not some obscure corner case. Objects with reference counters, proxy objects, objects keeping access statistics (e.g. for debugging purposes), objects using logger objects, etc - all require logical const. To corroborate, Qt sources have over 600 'mutable' field declarations.On 11/30/2010 02:35 AM, Walter Bright wrote:I'm not seeing half of non-trivial C++ programs using mutable. AndreiFawzi Mohamed wrote:The problem is that logical const has many perfectly valid use cases. You cannot simply tell people: "Don't use it. It is a fraud". They will still be using casts or not using D. As casting away const is undefined behavior in D, the outcome will be every second non-trivial D program relying on undefined behavior.logical const is useful for lazy functions and memoization, and if implemented correctly it is perfectly safe. As I said in an older discussions, to have it with the current system all that is needed is some guarantees that the compiler will not disallow "unsafe" changes (by moving to read only memory for example)in some cases. For example casted mutable types, so that casting to mutable works.D allows escape from the type system, but the programmer who does that loses the guarantees, and it's up to him to ensure that the result works. String literals, for example, are going to often wind up in read only memory.
Nov 30 2010
On 30-nov-10, at 16:39, Andrei Alexandrescu wrote:On 11/30/10 5:25 AM, Max Samukha wrote:The thing is that a lazy structure is very useful in functional programming. A lazy evaluation is something that should be possible using pure and immutable. I find it jarring that to do that one has to avoid D pure and immutable. To be able to safely use pure and immutable as I said one would need some idioms that are guaranteed to be non optimized by the compiler. for example casting a heap allocated type should be guaranteed to remain modifiable behind the back: auto t=new T; auto t2=cast(immutable(typeof(t)))t; auto tModif=cast(typeof(t))t2; // the compiler has not moved or flagged the memory of t, so one can modify tModif. clearly this is unsafe and it is up to the implementer to make sure that the object is really logically const and no function will see the internal changes. This is something that should be done sparingly, probably just in library code implementing lazy evaluation or memoization (but code that might be mixed in).On 11/30/2010 02:35 AM, Walter Bright wrote:I'm not seeing half of non-trivial C++ programs using mutable.Fawzi Mohamed wrote:The problem is that logical const has many perfectly valid use cases. You cannot simply tell people: "Don't use it. It is a fraud". They will still be using casts or not using D. As casting away const is undefined behavior in D, the outcome will be every second non-trivial D program relying on undefined behavior.logical const is useful for lazy functions and memoization, and if implemented correctly it is perfectly safe. As I said in an older discussions, to have it with the current system all that is needed is some guarantees that the compiler will not disallow "unsafe" changes (by moving to read only memory for example)in some cases. For example casted mutable types, so that casting to mutable works.D allows escape from the type system, but the programmer who does that loses the guarantees, and it's up to him to ensure that the result works. String literals, for example, are going to often wind up in read only memory.
Nov 30 2010
Fawzi Mohamed Wrote:The thing is that a lazy structure is very useful in functional programming. A lazy evaluation is something that should be possible using pure and immutable. I find it jarring that to do that one has to avoid D pure and immutable.Don't know what you mean by this.To be able to safely use pure and immutable as I said one would need some idioms that are guaranteed to be non optimized by the compiler. for example casting a heap allocated type should be guaranteed to remain modifiable behind the back: auto t=new T; auto t2=cast(immutable(typeof(t)))t; auto tModif=cast(typeof(t))t2; // the compiler has not moved or flagged the memory of t, so one can modify tModif.This code is valid, the requirements placed on cast will not allow it to move the data. Even types declared to be immutable my be modifiable when cast to Unqual!(T), but the compiler can not guarantee these. If I am wrong, please let me know why.clearly this is unsafe and it is up to the implementer to make sure that the object is really logically const and no function will see the internal changes.Yes, and I don't think compiler support adds any more guarantee than casting those you want to modify in a const function. This Mutable struct is supposed to help verify only modifiable data is cast: https://gist.github.com/721066 I've taken many example use-cases for logical const and added them as unittests. I think it is fairly reasonable if I could just get an answer to my question about concurrency and declaring immutable types.This is something that should be done sparingly, probably just in library code implementing lazy evaluation or memoization (but code that might be mixed in).Could you give an example of how lazy evaluation is achieved by modifying state?
Nov 30 2010
On 1-dic-10, at 04:52, Jesse Phillips wrote:Fawzi Mohamed Wrote:a lazy list (for example one that list all natural numbers) cannot be immutable, without the possibility of a backdoor because all the "next" elements will have to be set at creation time. (Lazy structures can be seen as memoizing a value produced by a pure function forever (i.e. never forgetting it).The thing is that a lazy structure is very useful in functional programming. A lazy evaluation is something that should be possible using pure and immutable. I find it jarring that to do that one has to avoid D pure and immutable.Don't know what you mean by this.The code works now, I would like some assurance that cast(immutable(T)) doesn't do fancy stuff (or some equivalent way to ensure that *some* idiom will remain allowed. If you think about it already now with opCast you cannot really know what opCast does, so a compiler would be allowed to return an immutable copy, or (if it uses whole pages) make the whole memory as read only.To be able to safely use pure and immutable as I said one would need some idioms that are guaranteed to be non optimized by the compiler. for example casting a heap allocated type should be guaranteed to remain modifiable behind the back: auto t=new T; auto t2=cast(immutable(typeof(t)))t; auto tModif=cast(typeof(t))t2; // the compiler has not moved or flagged the memory of t, so one can modify tModif.This code is valid, the requirements placed on cast will not allow it to move the data. Even types declared to be immutable my be modifiable when cast to Unqual!(T), but the compiler can not guarantee these. If I am wrong, please let me know why.you example has an error in parallel, this is a good example of why the casting away should not be made convenient, and a user should just use well tested library code (for example dong thunk evaluation), that might be in a special type or mixed in. You cannot have separate flag, and value without any synchronization, as other threads could see their value in a different order, so they could see dirty=false, but still no determinant. This is somehow related to dataflow variables that can be set several times, but only to the same value (and indeed with a lazy list one can allow two threads to calculate the next element, but then only one should set it (using atomic ops to avoid collistions). I have implemented DataFlow variables (but using the blip paralelization, that delays a task that waits, and resumes it when the value is ready, not with a thread lock) in https://github.com/fawzi/blip/blob/master/blip/parallel/smp/DataFlowVar.d using D1clearly this is unsafe and it is up to the implementer to make sure that the object is really logically const and no function will see the internal changes.Yes, and I don't think compiler support adds any more guarantee than casting those you want to modify in a const function. This Mutable struct is supposed to help verify only modifiable data is cast: https://gist.github.com/721066I've taken many example use-cases for logical const and added them as unittests. I think it is fairly reasonable if I could just get an answer to my question about concurrency and declaring immutable types.lazy structures need to modify the state (as I showed with the linked list example), lazy evaluation alone does not need to modify the state (and is indeed possible in D), but the storing/memoizing of the result needs it. To say the truth in D memoizing can be done with a static variable, but think about making the singly linked list like that, and you should immediately see that if gets *very* inefficient.This is something that should be done sparingly, probably just in library code implementing lazy evaluation or memoization (but code that might be mixed in).Could you give an example of how lazy evaluation is achieved by modifying state?
Dec 02 2010
Max Samukha wrote:The problem is that logical const has many perfectly valid use cases. You cannot simply tell people: "Don't use it. It is a fraud".I am not and never have said "don't use it. It's a fraud." I said that the C++ language has no notion of "logical constness", and that it is nothing more than a popular convention. The notion that C++ supports logical constness is the fraud.They will still be using casts or not using D. As casting away const is undefined behavior in D, the outcome will be every second non-trivial D program relying on undefined behavior.Yes, it's undefined behavior. That doesn't mean you cannot use it, just that you are responsible for getting it right rather than the compiler.
Nov 30 2010
On Mon, 29 Nov 2010 18:04:31 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Steven Schveighoffer wrote:What guarantees? Const provides no guarantees. class C { static C theCommonOne; int x; void foo() const { theCommonOne.x = 5; // theCommonOne could be this } } Only immutable provides guarantees. As I said earlier, the place where logical const would cause issues is immutable. Immutable data is currently implicitly shareable, and the compiler may take steps to optimize strongly-pure function calls, which require immutable parameters. Any logical const system would have to either work with those requirements or force them to be reevaluated.On Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Having mutable members destroys any guarantees that const provides. That's not political.Steven Schveighoffer wrote:I don't get it. A way to make a field mutable in a transitively-const system is syntactically similar to C++, but it's not the same. Having a logical-const feature in D does not devolve D's const into C++'s const. If anything it's just a political problem.Having a logical const feature in D would not be a convention, it would be enforced, as much as const is enforced. I don't understand why issues with C++ const or C++'s mutable feature makes any correlations on how a D logical const system would fare. C++ const is not D const, not even close.Because people coming from C++ ask "why not do it like C++'s?"And, I repeat, having a mutable type qualifier DOES NOT make logical const a language feature. This is why discussion and understanding of C++'s const system is so important - people impute characteristics into it that it simply does not have.I've proven in the past that logical const is equivalent to full const. That is, I can emulate logical const without any casts in the current const regime. Making it more efficient and simpler is all we would be doing. -Steve
Nov 30 2010
Steven Schveighoffer wrote:What guarantees? Const provides no guarantees.1. That nobody will modify any of the data structure accessed through the const reference provided. Thus, if your data is immutable, unique, or the function is pure, you are guaranteed it will not be modified. 2. That another thread will not be modifying any of that data structure.
Nov 30 2010
On Tue, 30 Nov 2010 14:07:51 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Steven Schveighoffer wrote:If your data is immutable it will not be modified. That is a contract of immutable, not a const function. If the function is pure, and *all* arguments to the function are const, the data wil not be modified, that is verifiable. If the data is unique is not enforced by the compiler, so it's up to you to ensure this. Sounds like a convention to me. The example that I gave does not seem to you like it would surprise someone? I passed in a const object and it got modified, even though no casts were used. If you add a portion to the object that is mutable or head-const, this alters the rule, but does not detract from the guarantees. The rule then just contains an exception for the mutable portion.What guarantees? Const provides no guarantees.1. That nobody will modify any of the data structure accessed through the const reference provided. Thus, if your data is immutable, unique, or the function is pure, you are guaranteed it will not be modified.2. That another thread will not be modifying any of that data structure.That rule is not affected by logical const. -Steve
Nov 30 2010
Steven Schveighoffer wrote:The example that I gave does not seem to you like it would surprise someone? I passed in a const object and it got modified, even though no casts were used.No, it doesn't surprise me. Const on one object does not apply to another object.
Nov 30 2010
Walter Bright wrote:Steven Schveighoffer wrote:const C c =3D C.theCommonOne; auto old =3D c.x; c.foo(); assert (old =3D=3D c.x); // Fails and this does not surprise you? Jerome --=20 mailto:jeberger free.fr http://jeberger.free.fr Jabber: jeberger jabber.frThe example that I gave does not seem to you like it would surprise someone? I passed in a const object and it got modified, even though no casts were used.=20 No, it doesn't surprise me. Const on one object does not apply to another object.
Nov 30 2010
On Tue, 30 Nov 2010 15:16:04 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Steven Schveighoffer wrote:So this: void myfn(const(C) n) { assert(n.x == 1); n.foo(); assert(n.x == 5); } Wouldn't be surprising to you? -SteveThe example that I gave does not seem to you like it would surprise someone? I passed in a const object and it got modified, even though no casts were used.No, it doesn't surprise me. Const on one object does not apply to another object.
Nov 30 2010
Steven Schveighoffer wrote:On Tue, 30 Nov 2010 15:16:04 -0500, Walter Bright <newshound2 digitalmars.com> wrote:No. There are a lot of things a member "x" could be. The const only applies to the instance data.Steven Schveighoffer wrote:So this: void myfn(const(C) n) { assert(n.x == 1); n.foo(); assert(n.x == 5); } Wouldn't be surprising to you?The example that I gave does not seem to you like it would surprise someone? I passed in a const object and it got modified, even though no casts were used.No, it doesn't surprise me. Const on one object does not apply to another object.
Nov 30 2010
On Tue, 30 Nov 2010 16:05:16 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Steven Schveighoffer wrote:If you read my previous code, x is instance data. I'll reiterate the class definition: class C { static C theCommonOne; int x; void foo() const { theCommonOne.x = 5; // theCommonOne could be this } } If you find the above unsurprising, you are in the minority. I find it surprising, and invalid that anyone would write code this way. People simply just don't do that normally. It's just written to demonstrate a point that the compiler does not guarantee anything via const, it's guaranteed by convention. The compiler simply helps you follow the convention. -SteveOn Tue, 30 Nov 2010 15:16:04 -0500, Walter Bright <newshound2 digitalmars.com> wrote:No. There are a lot of things a member "x" could be. The const only applies to the instance data.Steven Schveighoffer wrote:So this: void myfn(const(C) n) { assert(n.x == 1); n.foo(); assert(n.x == 5); } Wouldn't be surprising to you?The example that I gave does not seem to you like it would surprise someone? I passed in a const object and it got modified, even though no casts were used.No, it doesn't surprise me. Const on one object does not apply to another object.
Nov 30 2010
Steven Schveighoffer wrote:If you find the above unsurprising, you are in the minority. I find it surprising, and invalid that anyone would write code this way. People simply just don't do that normally. It's just written to demonstrate a point that the compiler does not guarantee anything via const, it's guaranteed by convention. The compiler simply helps you follow the convention.Ok, I see what you mean now. Your code is relying on there being a mutable alias of the same object. This is not surprising behavior. It is explicit in how const is defined. It makes sense that const does not have immutable behavior, because otherwise there wouldn't be both const and immutable type constructors. You're wrong in saying the compiler doesn't guarantee anything with const. I listed the things it does guarantee.
Nov 30 2010
On Tue, 30 Nov 2010 16:53:14 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Steven Schveighoffer wrote:The literal guarantee is that things aren't modified through that reference. The semantic result to someone calling the function is that it doesn't guarantee that the data referred to won't change, or that part of C's state cannot change through that reference. If I see a function like: void foo(const(C) c); it doesn't mean that foo cannot modify the object referred to by c, it just means that foo won't modify data referenced through c. But a C could store some data in a global variable, possibly even uniquely associated with each instance (I have shown this in a very old post proving logical const == const). Then logically, the author of C could consider that data a part of C. I have no way to stop him from editing that logical part of C, I'm relying on the author of C not to count mutable state as part of the state of C. Adding logical const just provides a location in the object itself for this data that is not supposed to be part of C's state. It's not changing the guarantees that const already provides (which is very little, but helps you follow the correct conventions). When it comes to immutable and pure, we would need new rules. -SteveIf you find the above unsurprising, you are in the minority. I find it surprising, and invalid that anyone would write code this way. People simply just don't do that normally. It's just written to demonstrate a point that the compiler does not guarantee anything via const, it's guaranteed by convention. The compiler simply helps you follow the convention.Ok, I see what you mean now. Your code is relying on there being a mutable alias of the same object. This is not surprising behavior. It is explicit in how const is defined. It makes sense that const does not have immutable behavior, because otherwise there wouldn't be both const and immutable type constructors. You're wrong in saying the compiler doesn't guarantee anything with const. I listed the things it does guarantee.
Dec 01 2010
Steven Schveighoffer wrote:If I see a function like: void foo(const(C) c); it doesn't mean that foo cannot modify the object referred to by c, it just means that foo won't modify data referenced through c. But a C could store some data in a global variable, possibly even uniquely associated with each instance (I have shown this in a very old post proving logical const == const). Then logically, the author of C could consider that data a part of C. I have no way to stop him from editing that logical part of C, I'm relying on the author of C not to count mutable state as part of the state of C. Adding logical const just provides a location in the object itself for this data that is not supposed to be part of C's state. It's not changing the guarantees that const already provides (which is very little, but helps you follow the correct conventions).foo() could only modify c if it has, via some other means, acquired a mutable reference to c. If the user does not provide this other mutable reference, then foo() cannot modify c. foo() cannot go out and grab or create such a reference. This is quite the opposite from what you're proposing. Currently, the user has to make it possible for foo() to modify c, whereas your proposal is that foo() can modify c despite all attempts by the user to stop it. Furthermore, if you mark foo() as pure, the compiler can guarantee there is no such hidden mutable reference.
Dec 01 2010
On Wed, 01 Dec 2010 18:34:34 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Steven Schveighoffer wrote:You're assuming the domain of c's state stops at its scope. In fact, except for in pure functions, c's 'state' includes all global variables as well. There is nothing stopping a programmer from using something outside the object/struct itself to store some state of c. Those external data members would essentially be mutable (as I have shown in the past). This takes most of the teeth out of const's guarantees.If I see a function like: void foo(const(C) c); it doesn't mean that foo cannot modify the object referred to by c, it just means that foo won't modify data referenced through c. But a C could store some data in a global variable, possibly even uniquely associated with each instance (I have shown this in a very old post proving logical const == const). Then logically, the author of C could consider that data a part of C. I have no way to stop him from editing that logical part of C, I'm relying on the author of C not to count mutable state as part of the state of C. Adding logical const just provides a location in the object itself for this data that is not supposed to be part of C's state. It's not changing the guarantees that const already provides (which is very little, but helps you follow the correct conventions).foo() could only modify c if it has, via some other means, acquired a mutable reference to c. If the user does not provide this other mutable reference, then foo() cannot modify c. foo() cannot go out and grab or create such a reference.This is quite the opposite from what you're proposing. Currently, the user has to make it possible for foo() to modify c, whereas your proposal is that foo() can modify c despite all attempts by the user to stop it.Not exactly. My proposal recognizes that we have to trust the programmer to only use mutable state (or external state in the current const implementation) to represent 'non-state' variables, that is, variables that are not considered part of the object itself. This includes references to unowned data (like an output stream) or cached computations (which are not part of the state, they are an optimization). My proposal allows the programmer to store that state inside the object. All const guarantees on the actual state variables is fully enforced. The question is, is the user of c concerned about const-ifying data members that are non-state members? Usually no. They only care about variables that are part of the state of the object.Furthermore, if you mark foo() as pure, the compiler can guarantee there is no such hidden mutable reference.Pure is an area where logical const would have to follow special rules. My inclination is that pure functions would have no access to members marked as 'no-state'. That would at least be consistent with the current implementation of logical const. This can have implications as far as copying value-types (what do you do with the mutable members?), but I think we could work out those rules. -Steve
Dec 02 2010
On 01/12/2010 21:09, Steven Schveighoffer wrote:On Tue, 30 Nov 2010 16:53:14 -0500, Walter Bright <newshound2 digitalmars.com> wrote:So now you do agree that (D's) const does provide guarantees, right? -- Bruno Medeiros - Software EngineerSteven Schveighoffer wrote:The literal guarantee is that things aren't modified through that reference.If you find the above unsurprising, you are in the minority. I find it surprising, and invalid that anyone would write code this way. People simply just don't do that normally. It's just written to demonstrate a point that the compiler does not guarantee anything via const, it's guaranteed by convention. The compiler simply helps you follow the convention.Ok, I see what you mean now. Your code is relying on there being a mutable alias of the same object. This is not surprising behavior. It is explicit in how const is defined. It makes sense that const does not have immutable behavior, because otherwise there wouldn't be both const and immutable type constructors. You're wrong in saying the compiler doesn't guarantee anything with const. I listed the things it does guarantee.
Dec 02 2010
On Thu, 02 Dec 2010 12:59:22 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:On 01/12/2010 21:09, Steven Schveighoffer wrote:It guarantees something very focused, and possible to work around without resorting to unsafe code. That's my point. The guarantee is well-defined and useful because it helps write correct code, but I don't see how a logical const guarantee is mythical whereas D's current const guarantee is impermeable. I have shown examples of how const does not guarantee an object's state doesn't change. It's some work to make it happen (which is good), but 'guarantee' is too strong a word in my opinion. I'd say it's a tool that helps you follow a good convention, just like logical const would. -SteveOn Tue, 30 Nov 2010 16:53:14 -0500, Walter Bright <newshound2 digitalmars.com> wrote:So now you do agree that (D's) const does provide guarantees, right?Steven Schveighoffer wrote:The literal guarantee is that things aren't modified through that reference.If you find the above unsurprising, you are in the minority. I find it surprising, and invalid that anyone would write code this way. People simply just don't do that normally. It's just written to demonstrate a point that the compiler does not guarantee anything via const, it's guaranteed by convention. The compiler simply helps you follow the convention.Ok, I see what you mean now. Your code is relying on there being a mutable alias of the same object. This is not surprising behavior. It is explicit in how const is defined. It makes sense that const does not have immutable behavior, because otherwise there wouldn't be both const and immutable type constructors. You're wrong in saying the compiler doesn't guarantee anything with const. I listed the things it does guarantee.
Dec 02 2010
Steven Schveighoffer wrote:I have shown examples of how const does not guarantee an object's state doesn't change.Yes, as is well documented, const is a read only view. It is not immutable. That is why immutable is a separate attribute.
Dec 02 2010
Wouldn't be surprising to you? -SteveNo need to go that far. void fn(const A, A) {...} A a; fn(a, a); // what do you expect here? As far as i know D/C++ have the same treatment for const member functions. It has been this way forever. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Nov 30 2010
so wrote:No need to go that far. void fn(const A, A) {...} A a; fn(a, a); // what do you expect here? As far as i know D/C++ have the same treatment for const member functions. It has been this way forever.Yup. It's why I think it is worthwhile that I spend time in this thread explaining things over and over. There are a lot of mistaken assumptions and baggage people carry around about const in both D and C++. (One of the ironies of C++ const is that programmers assume it behaves like D const/immutable, but rely on it not doing so.)
Nov 30 2010
2010/11/30 Walter Bright <newshound2 digitalmars.com>so wrote:I've been following this thread on and off, but is there a definition somewhere of exactly what "const" means in D2 and exactly what that guaranties or doesn't guaranty?No need to go that far. void fn(const A, A) {...} A a; fn(a, a); // what do you expect here? As far as i know D/C++ have the same treatment for const member functions. It has been this way forever.Yup. It's why I think it is worthwhile that I spend time in this thread explaining things over and over. There are a lot of mistaken assumptions and baggage people carry around about const in both D and C++.(One of the ironies of C++ const is that programmers assume it behaves like D const/immutable, but rely on it not doing so.)
Nov 30 2010
Andrew Wiley wrote:I've been following this thread on and off, but is there a definition somewhere of exactly what "const" means in D2 and exactly what that guaranties or doesn't guaranty?Const provides a read-only "view" of a reference and anything reachable through that reference. It guarantees that no other thread can read or write that reference or anything reachable through it. It does not guarantee that there isn't a mutable alias to that reference elsewhere in the same thread that may modify it.
Nov 30 2010
On Tue, 30 Nov 2010 15:03:43 -0800 Walter Bright <newshound2 digitalmars.com> wrote:Andrew Wiley wrote:hrough=20I've been following this thread on and off, but is there a definition=20 somewhere of exactly what "const" means in D2 and exactly what that=20 guaranties or doesn't guaranty?=20=20 Const provides a read-only "view" of a reference and anything reachable t=that reference. It guarantees that no other thread can read or write that==20reference or anything reachable through it. =20 It does not guarantee that there isn't a mutable alias to that reference==20elsewhere in the same thread that may modify it.What would be the consequences if D had no const, only immutable (that, IIU= C, removes the latter non-guarantee)? Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 01 2010
spir wrote:What would be the consequences if D had no const, only immutable (that, IIUC, removes the latter non-guarantee)?You'd have to write most every function twice, once to take immutable args and again for mutable ones.
Dec 01 2010
Walter Bright wrote:spir wrote:Doesn't 'inout' do almost the same thing? The only difference I can see between const and inout, is that inout tells which parameters could be aliased with the return value.What would be the consequences if D had no const, only immutable (that, IIUC, removes the latter non-guarantee)?You'd have to write most every function twice, once to take immutable args and again for mutable ones.
Dec 02 2010
On Thursday, December 02, 2010 01:18:31 Don wrote:Walter Bright wrote:Except that doesn't inout actually produce multiple versions of the function, whereas with const, you only get the one? - Jonathan M Davisspir wrote:Doesn't 'inout' do almost the same thing? The only difference I can see between const and inout, is that inout tells which parameters could be aliased with the return value.What would be the consequences if D had no const, only immutable (that, IIUC, removes the latter non-guarantee)?You'd have to write most every function twice, once to take immutable args and again for mutable ones.
Dec 02 2010
Jonathan M Davis wrote:On Thursday, December 02, 2010 01:18:31 Don wrote:No. My understanding is that the constness of the return value is determined at the call site, but otherwise, it's as if all 'inout' parameters were const.Walter Bright wrote:Except that doesn't inout actually produce multiple versions of the function,spir wrote:Doesn't 'inout' do almost the same thing? The only difference I can see between const and inout, is that inout tells which parameters could be aliased with the return value.What would be the consequences if D had no const, only immutable (that, IIUC, removes the latter non-guarantee)?You'd have to write most every function twice, once to take immutable args and again for mutable ones.whereas with const, you only get the one? - Jonathan M Davis
Dec 02 2010
On Thu, 02 Dec 2010 15:25:49 -0500, Don <nospam nospam.com> wrote:Jonathan M Davis wrote:inout is different in that parameters cannot implicitly cast to inout. It's actually on the same level as immutable and mutable.On Thursday, December 02, 2010 01:18:31 Don wrote:Walter Bright wrote:spir wrote:Doesn't 'inout' do almost the same thing? The only difference I can see between const and inout, is that inout tells which parameters could be aliased with the return value.What would be the consequences if D had no const, only immutable (that, IIUC, removes the latter non-guarantee)?You'd have to write most every function twice, once to take immutable args and again for mutable ones.This is correct, except for the implicit casting thing mentioned above. -SteveExcept that doesn't inout actually produce multiple versions of the function,No. My understanding is that the constness of the return value is determined at the call site, but otherwise, it's as if all 'inout' parameters were const.
Dec 02 2010
Don Wrote:Jonathan M Davis wrote:Inside a function, inout(T) should be considered a subtype of const(T). Nothing should be convertible to inout.On Thursday, December 02, 2010 01:18:31 Don wrote:No. My understanding is that the constness of the return value is determined at the call site, but otherwise, it's as if all 'inout' parameters were const.Walter Bright wrote:Except that doesn't inout actually produce multiple versions of the function,spir wrote:Doesn't 'inout' do almost the same thing? The only difference I can see between const and inout, is that inout tells which parameters could be aliased with the return value.What would be the consequences if D had no const, only immutable (that, IIUC, removes the latter non-guarantee)?You'd have to write most every function twice, once to take immutable args and again for mutable ones.
Dec 02 2010
Don wrote:Walter Bright wrote:inout applies at the top level, but you cannot define a struct that has inout fields.spir wrote:Doesn't 'inout' do almost the same thing? The only difference I can see between const and inout, is that inout tells which parameters could be aliased with the return value.What would be the consequences if D had no const, only immutable (that, IIUC, removes the latter non-guarantee)?You'd have to write most every function twice, once to take immutable args and again for mutable ones.
Dec 02 2010
On Wednesday 01 December 2010 03:13:08 spir wrote:On Tue, 30 Nov 2010 15:03:43 -0800 Walter Bright <newshound2 digitalmars.com> wrote:The biggest problem would be that no function could then work on both a mutable and an immutable value (unless it could be copied by value). With const, you can pass both mutable and immutable stuff to it. Without const, any and all functions which would want to deal with both would have to be duplicated. That includes stuff like member functions. And of course, as C++ shows, there are plenty of cases where having const but no immutable can be quite valuable. Just the fact that you can pass an object to a function and know with reasonable certainty (and more certainty in D than C++) than that object won't be altered can be extremely valuable. Sure, many languages get by without const, but I think that they're definitely worse off for it. And with immutable added to the mix, I think that const is that much more important. - Jonathan M DavisAndrew Wiley wrote:What would be the consequences if D had no const, only immutable (that, IIUC, removes the latter non-guarantee)?I've been following this thread on and off, but is there a definition somewhere of exactly what "const" means in D2 and exactly what that guaranties or doesn't guaranty?Const provides a read-only "view" of a reference and anything reachable through that reference. It guarantees that no other thread can read or write that reference or anything reachable through it. It does not guarantee that there isn't a mutable alias to that reference elsewhere in the same thread that may modify it.
Dec 01 2010
On Wed, 1 Dec 2010 03:22:39 -0800 Jonathan M Davis <jmdavisProg gmx.com> wrote:utable=20What would be the consequences if D had no const, only immutable (that, IIUC, removes the latter non-guarantee)? =20=20 The biggest problem would be that no function could then work on both a m=and an immutable value (unless it could be copied by value). With const, =you can=20pass both mutable and immutable stuff to it. Without const, any and all f=unctions=20which would want to deal with both would have to be duplicated. That incl=udes=20stuff like member functions.Right, but isn't this the main point of Unqual!? (Would unqualify immutable= as well, no?).And of course, as C++ shows, there are plenty of cases where having const=but no=20immutable can be quite valuable. Just the fact that you can pass an objec=t to a=20function and know with reasonable certainty (and more certainty in D than=C++)=20than that object won't be altered can be extremely valuable. Sure, many=20 languages get by without const, but I think that they're definitely worse=off for=20it. And with immutable added to the mix, I think that const is that much =more=20important.For this case, I prefere the "in" qualifier. (And imo value parameters shou= ld be "in" by default). Unless I miss important use cases, seems I would be happy with "immutable" = and "in". denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 01 2010
On 29/11/2010 23:04, Walter Bright wrote:Steven Schveighoffer wrote:That is not true, trivially. (assuming we are talking about D) Having mutable members only (slightly) modifies the guarantees of const. For example: class Foo { int x, y, z; mutable int blah; } void someFunc(const Foo foo) { ... } here someFunc is still guaranteed to not modify any data transitively reachable through the foo reference, _with the exception of mutable members_. Is this still a useful guarantee? Well yes, for example in Peter's Matrix example, I could pass a const(Matrix) as an argument and still be confident that at most, only the mutable members would be change (the cached determinant), but not the logical state. The compiler would be able to check this, just as much as with D's current const. Also, it would still guarantee that immutable data passed to that function is not transitively modified (with the exception of mutable members). And that is the main point of const. A more interesting question is whether mutable members would significantly alter the guarantees of *immutable*. They would not changue the guarantee of immutable with regards to single-threaded optimization. So if a function has an immutable object, it can still assume the non-mutable members don't change, and optimize accordingly. However, a big thing that could no longer be guaranteed, is that you would be able to safely pass an immutable object to a function running in another thread, without synchronization. This is because said function would be allowed to mutate the mutable members, but these could be being accessed concurrently, so... -- Bruno Medeiros - Software EngineerOn Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Having mutable members destroys any guarantees that const provides. That's not political.Steven Schveighoffer wrote:I don't get it. A way to make a field mutable in a transitively-const system is syntactically similar to C++, but it's not the same. Having a logical-const feature in D does not devolve D's const into C++'s const. If anything it's just a political problem.Having a logical const feature in D would not be a convention, it would be enforced, as much as const is enforced. I don't understand why issues with C++ const or C++'s mutable feature makes any correlations on how a D logical const system would fare. C++ const is not D const, not even close.Because people coming from C++ ask "why not do it like C++'s?"
Dec 02 2010
On 29/11/10 8:58 PM, Walter Bright wrote:Because people coming from C++ ask "why not do it like C++'s?"No one is asking this.
Nov 29 2010
Peter Alexander wrote:On 29/11/10 8:58 PM, Walter Bright wrote:I interpreted this from Jonathon as asking for it: "Without immutable, you could presumably get something similar to what C++ has (if you could ever get Walter to go for it)," You also wrote: "I can be sure that my object will come back unmodified. That it is the primary purpose of const. Like you said, it allows you to reason about your programs. Yes, GameObject could be unreasonably mutilated by careless use of mutable, but in practice that simply doesn't happen." Which implies that you regard C++'s system as sufficient. I pointed out 5 reasons why the "be sure" is incorrect. I believe it is necessary to discuss and understand these points in order to justify why D does not adopt C++'s system.Because people coming from C++ ask "why not do it like C++'s?"No one is asking this.
Nov 29 2010
On 29/11/10 11:13 PM, Walter Bright wrote:Peter Alexander wrote:That was in response to your claim that C++'s const provides no guarantees. I was just giving a counterexample. Just to clarify my position - I *do not* want to copy C++'s const system. - D style immutability is useful. - C++ style logical const is also useful. - I think they can both work side by side by introducing a new level of const and the mutable keyword.On 29/11/10 8:58 PM, Walter Bright wrote:I interpreted this from Jonathon as asking for it: "Without immutable, you could presumably get something similar to what C++ has (if you could ever get Walter to go for it)," You also wrote: "I can be sure that my object will come back unmodified. That it is the primary purpose of const. Like you said, it allows you to reason about your programs. Yes, GameObject could be unreasonably mutilated by careless use of mutable, but in practice that simply doesn't happen." Which implies that you regard C++'s system as sufficient. I pointed out 5 reasons why the "be sure" is incorrect. I believe it is necessary to discuss and understand these points in order to justify why D does not adopt C++'s system.Because people coming from C++ ask "why not do it like C++'s?"No one is asking this.
Nov 30 2010
Peter Alexander wrote:Just to clarify my position - I *do not* want to copy C++'s const system. - D style immutability is useful. - C++ style logical const is also useful. - I think they can both work side by side by introducing a new level of const and the mutable keyword.Once mutable is introduced, the const attribute becomes no longer useful. You (and the compiler) can no longer look at a function signature and reason about the const-ness behavior. If a new attribute is introduced, "newlevel", you really cannot reason about the behavior at all. I wish to emphasize that the compiler cannot provide any verification that logical constness is indeed happening. (It cannot do it in C++, either, the notion that the C++ type system supports logical constness is incorrect.)
Nov 30 2010
Surely I'm not the only person that finds something *incredibly* wrong with this!?Sorry but I see nothing but the misconception of const-correctness with the given example. I hope you are aware of what you are asking. For your particular example: First you started by class, you should use struct. (sorry being lame here but happens...) Second why do mark something const if it is not, this is a wrong approach even in C++ which provides explicit "mutable". How are you going to do caching anyways, if static array is not the case? struct matrix { f32[4][4] m; f32 det_cache; // ? f32 mag_cache; // ? f32 whatever_cache; // ? }; -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Nov 29 2010
On 29/11/10 3:19 PM, so wrote:Second why do mark something const if it is not, this is a wrong approach even in C++ which provides explicit "mutable".It is logical const. It is the correct approach in C++.How are you going to do caching anyways, if static array is not the case? struct matrix { f32[4][4] m; f32 det_cache; // ? f32 mag_cache; // ? f32 whatever_cache; // ? };I described the mechanisms of this in the original post. You have a 'is_dirty' member function, which is marked as true if you modify the matrix. The getDeterminant function only calculates the determinant if is_dirty is true, otherwise it returns the cached version.
Nov 29 2010
On 28/11/2010 14:50, Peter Alexander wrote:1. I have to change getWorldTransform to be a non-const function that returns a non-const Matrix. 2. renderGameObject needs to be changed to receive a non-const GameObject. 3. I have lost any guarantee that rendering my GameObjects won't destroy them... Surely I'm not the only person that finds something *incredibly* wrong with this!?Indeed, you should not try to use D's const for the use-case of logical const. Rather, logical const is simply not supported, on a static type level. But why is that such a big problem actually? A lot of other languages const, and yet are consider generally much safer than C++. Why didn't they add logical const? Similarly for D, D has a lot of features that generally make it safer to program in C++ (with the same level of performance), such that in total they more than compensate for the lack of logical const. That is no good reason to not use D. -- Bruno Medeiros - Software Engineer
Dec 02 2010
Jonathan M Davis <jmdavisProg gmx.com> wrote:So, _I_ am certainly not aware of a good solution, but if you play games, you should be able to get _something_ working. Overall though, I think that the lesson is that you should really be making const functions truly const and not try and deal with logical const at all.Highly unsafe, but works in the cases you'd expect. Use with extreme caution: import std.traits; system ref Unqual!T unqualified( T )( ref const T t ) { Unqual!(T)* p = cast(Unqual!(T)*)&t; return *p; } -- Simen
Nov 28 2010
Jonathan M Davis wrote:Walter is certainly against logical constness,I don't know of any language that provides logical constness as a feature. I know of no way to make it a statically verifiable attribute. Doing it with runtime checks is arbitrarily complex (imagine having to verify with a runtime check that one didn't change a 1Gb data structure). D has a focus on providing features that promote verifiability of programs. As programs inevitably get more complex, I believe this is crucial (as opposed to the older technique of relying on convention).
Nov 28 2010
On Sunday 28 November 2010 14:02:02 Walter Bright wrote:Jonathan M Davis wrote:The more I look at your comments on the matter, the more I see that logical constness does not really give you any guarantees in the general case, because it's technically possible to cast it away and ignore it or just make all of your member variables mutable and ignore it. But (if nothing else, due to programmers not abusing it that badly) programmers don't usually view it that way. From the programmers point of view, it does provide guarantees, and in practice, I believe that it generally holds. But it is true that when you look at it, it's convention that's holding it together (albeit one which is generally followed). So, I do think that D has taken the correct route (though logical constness _can_ be really nice at times), but I'm sure that most programmers aren't going to see it that way (certainly not without a fari bit of convincing), especially when so many of them are used to going without it entirely. I'm glad to finally understand why you're against logical constness though. I'd never quite understood it before. It's definitely food for thought. - Jonathan M DavisWalter is certainly against logical constness,I don't know of any language that provides logical constness as a feature. I know of no way to make it a statically verifiable attribute. Doing it with runtime checks is arbitrarily complex (imagine having to verify with a runtime check that one didn't change a 1Gb data structure). D has a focus on providing features that promote verifiability of programs. As programs inevitably get more complex, I believe this is crucial (as opposed to the older technique of relying on convention).
Nov 28 2010
Jonathan M Davis wrote:On Sunday 28 November 2010 14:02:02 Walter Bright wrote:More than that, nothing at all about the mutable qualifier says that there can be only one value assigned to it. Logical constness is not supported.Jonathan M Davis wrote:The more I look at your comments on the matter, the more I see that logical constness does not really give you any guarantees in the general case, because it's technically possible to cast it away and ignore it or just make all of your member variables mutable and ignore it.Walter is certainly against logical constness,I don't know of any language that provides logical constness as a feature. I know of no way to make it a statically verifiable attribute. Doing it with runtime checks is arbitrarily complex (imagine having to verify with a runtime check that one didn't change a 1Gb data structure). D has a focus on providing features that promote verifiability of programs. As programs inevitably get more complex, I believe this is crucial (as opposed to the older technique of relying on convention).But (if nothing else, due to programmers not abusing it that badly) programmers don't usually view it that way. From the programmers point of view, it does provide guarantees, and in practice, I believe that it generally holds.I'd rather not conflate an unchecked convention with a guarantee.But it is true that when you look at it, it's convention that's holding it together (albeit one which is generally followed). So, I do think that D has taken the correct route (though logical constness _can_ be really nice at times), but I'm sure that most programmers aren't going to see it that way (certainly not without a fari bit of convincing), especially when so many of them are used to going without it entirely.These kinds of things are what make C++ expensive to code in. Expensive in that it costs you plenty to hire a competent C++ programmer. Even then, you have no guarantee that your coding team is actually following those conventions. Too many times, individual coders will find it expedient to "cheat" on them, or simply make a mistake. How would you know if they did or not?I'm glad to finally understand why you're against logical constness though. I'd never quite understood it before. It's definitely food for thought.The const/immutable regime in D is unique as far as I know, and is a bit of a shock to those coming from the C++ world.
Nov 28 2010
On 11/28/10 4:02 PM, Walter Bright wrote:Jonathan M Davis wrote:Logical immutability can be implemented with reasonable guarantees (s we discussed a while back in private). The question is whether the effort is justified. AndreiWalter is certainly against logical constness,I don't know of any language that provides logical constness as a feature. I know of no way to make it a statically verifiable attribute. Doing it with runtime checks is arbitrarily complex (imagine having to verify with a runtime check that one didn't change a 1Gb data structure). D has a focus on providing features that promote verifiability of programs. As programs inevitably get more complex, I believe this is crucial (as opposed to the older technique of relying on convention).
Nov 28 2010
Peter Alexander:So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it? And how do we write generic code when it's practically impossible to determine const-ness from a glance? e.g. getDeterminant looks like it should be const, but wouldn't be if it had caching, so writing generic code that uses getDeterminant would be very difficult.I am not expert about this, so take this cum grano salis. This topic is a mess, but I have some ideas: 1) A type system a cost, because it gives rigidity and troubles, but it gives you something back. I I think D const/immutable/pure give you enough back only if you look at them with the eyes of functional programming. This means that their full potential (and return of investiment) will be visible only when a D compiler/implementation will manage multicores efficiently, revealing one of the true advantages of functional programming. It will take some years at best. 2) Time ago Andrei has shown me articles that explain what's good to put inside a class/struct and what's good to leave outside as free module-level function, in a C++/D-like language. In this case I think the determinant() is better to be free, because it's a generic algorithm that often doesn't need to know how the matrix is implemented. So if you leave it outside the class, you may use it (in theory) on other implementations of matrices too. So this is a possibly good design: class Matrix { // ... } pure double determinant(M)(const M mat) if (IsMatrix!M) { // ... } Or maybe even: pure double determinant(M)(const M mat) if (IsSquareMatrix!M) { // ... } Then you want to cache determinant() because it performs a long computation. One way to solve it is to use a simple memoize(), or implement determinant() as opCall of a little struct that performs the memoization. Memoization requires lot of memory and it needs a bit of time to compare the input data with the memoized keys. If you don't want to waste too much memory in caching data for the memoization, then you may create an immutable matrix, and then compute its determinant only once and store it somewhere... The memoization may be smart and store the determinant of parts of the input data, and don't compute it again if only part of the input changes. The memoization saves you most of the re-computation time, but it costs you some memory. If you don't want to pay both the re-computation time and the memory costs used by the memoization, then you need to not use const/immutable/pure (as done in Python) and keep the semantics of the code in your head instead of encoded in the type system (because as I have said D2 type system is designed for functional programming, and C++-style const is not good enough for functional programming). Otherwise you have to punch holes in the type system, and cast away constness where you want, but this is a dangerous solution. 3) If you look at your program with functional programming eyes, in Haskell the language solves your problem with lazyness managed almost transparently by the language (so computations are performed just in time), automatic memoization (so it often computes your determinant only once), and immutability (so you have an immutable view of the matrix, even if the data structure itself that implements the matrix may be able to support a kind of "versioning", so when you want to change few of its elements it doesn't copy of the whole matrix). 4) It will be good to have some way to memoize functions in D. But currently there is no way to create a memoizing function that is pure. Function purity and transitive immutability go along in pairs, so I think D will need, as Haskell, some way to perform a pure memoization. To do this you need "logical purity" :-) A way to solve this is to add a built-in memoize usable only on strongly pure functions. So you will have: class Matrix { // ... } memoize pure double determinant(M)(const M mat) if (IsMatrix!M) { // strongly pure // ... } If a built-in strongly pure memoize is not possible or desired, then you need something like a trusted_pure to implement it manually and hope the programmer is not doing something wrong... Sorry for the confused answer, I am not good enough about this yet. *lights a candle for Peyton-Jones* Bye, bearophile
Nov 28 2010
bearophile wrote:1) A type system a cost, because it gives rigidity and troubles, but it gives you something back. I I think D const/immutable/pure give you enough back only if you look at them with the eyes of functional programming. This means that their full potential (and return of investiment) will be visible only when a D compiler/implementation will manage multicores efficiently, revealing one of the true advantages of functional programming. It will take some years at best.While const/etc. has many advantages in concurrent programming, your statement vastly undervalues its primary utility. That utility is it provides the user with a powerful tool with which to understand and reason about his programs. C++ const does not have either of those utilities. C++ const is an unenforcable convention, and offers no guarantees. Logical constness is a convention, not anything that is statically checkable in C++. To emphasize, logical constness is simply not a feature of the C++ type system.
Nov 28 2010
Walter Bright:While const/etc. has many advantages in concurrent programming, your statement vastly undervalues its primary utility. That utility is it provides the user with a powerful tool with which to understand and reason about his programs.I see. I haven't written large programs that use transitive const/immutable yet, so I have to accept your word :-) Thank you for your answer. Bye, bearophile
Nov 28 2010
I am not expert about this, so take this cum grano salis. This topic is a mess, but I have some ideas: 1) A type system a cost, because it gives rigidity and troubles, but it gives you something back. I I think D const/immutable/pure give you enough back only if you look at them with the eyes of functional programming. This means that their full potential (and return of investiment) will be visible only when a D compiler/implementation will manage multicores efficiently, revealing one of the true advantages of functional programming. It will take some years at best. 2) Time ago Andrei has shown me articles that explain what's good to put inside a class/struct and what's good to leave outside as free module-level function, in a C++/D-like language. In this case I think the determinant() is better to be free, because it's a generic algorithm that often doesn't need to know how the matrix is implemented. So if you leave it outside the class, you may use it (in theory) on other implementations of matrices too. So this is a possibly good design: class Matrix { // ... } pure double determinant(M)(const M mat) if (IsMatrix!M) { // ... } Or maybe even: pure double determinant(M)(const M mat) if (IsSquareMatrix!M) { // ... }Great that you can remember something like that, even the most C++ programmers don't know that either it is right or wrong.Then you want to cache determinant() because it performs a long computation. One way to solve it is to use a simple memoize(), or implement determinant() as opCall of a little struct that performs the memoization. Memoization requires lot of memory and it needs a bit of time to compare the input data with the memoized keys. If you don't want to waste too much memory in caching data for the memoization, then you may create an immutable matrix, and then compute its determinant only once and store it somewhere... The memoization may be smart and store the determinant of parts of the input data, and don't compute it again if only part of the input changes. The memoization saves you most of the re-computation time, but it costs you some memory. If you don't want to pay both the re-computation time and the memory costs used by the memoization, then you need to not use const/immutable/pure (as done in Python) and keep the semantics of the code in your head instead of encoded in the type system (because as I have said D2 type system is designed for functional programming, and C++-style const is not good enough for functional programming). Otherwise you have to punch holes in the type system, and cast away constness where you want, but this is a dangerous solution. 3) If you look at your program with functional programming eyes, in Haskell the language solves your problem with lazyness managed almost transparently by the language (so computations are performed just in time), automatic memoization (so it often computes your determinant only once), and immutability (so you have an immutable view of the matrix, even if the data structure itself that implements the matrix may be able to support a kind of "versioning", so when you want to change few of its elements it doesn't copy of the whole matrix). 4) It will be good to have some way to memoize functions in D. But currently there is no way to create a memoizing function that is pure. Function purity and transitive immutability go along in pairs, so I think D will need, as Haskell, some way to perform a pure memoization. To do this you need "logical purity" :-) A way to solve this is to add a built-in memoize usable only on strongly pure functions. So you will have: class Matrix { // ... } memoize pure double determinant(M)(const M mat) if (IsMatrix!M) { // strongly pure // ... } If a built-in strongly pure memoize is not possible or desired, then you need something like a trusted_pure to implement it manually and hope the programmer is not doing something wrong... Sorry for the confused answer, I am not good enough about this yet. *lights a candle for Peyton-Jones* Bye, bearophileI can understand Peter using Matrix just for example but still i have to say. A small vector/matrix with an extra field to memoize things perform likely worse than their usual implementations. That small extra field might just destroy any alignments, optimizations, which are scarier than the computation itself. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Nov 29 2010
On 30/11/10 6:21 AM, so wrote:I can understand Peter using Matrix just for example but still i have to say. A small vector/matrix with an extra field to memoize things perform likely worse than their usual implementations. That small extra field might just destroy any alignments, optimizations, which are scarier than the computation itself.This may be true for this particular case, but has no bearing on the discussion in general.
Nov 30 2010
On Sat, 20 Nov 2010 09:21:04 -0500, Peter Alexander <peter.alexander.au gmail.com> wrote:D does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it?This has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind. The thing is we *already* have a hidden field that is logically const -- an object's monitor. Regardless of an object's constancy, you can always mutate the monitor. The compiler does it by logically inserting a cast away from const, so that's what I'd suggest. In reality, once you get into the realm of logical const, the compiler no longer helps you. Any guarantees are now provided by you, not the compiler. I've proposed a very complex system to allow you to indicate only certain fields as const which also worked with the existing const system. While I think Walter and Andrei agreed it was possible, I agreed with them that it was too complex to ask users to deal with. What I'd like to see is a solution like this: struct S { mutable int i; int x; } S s; lconst(S) *ls = &s; // new keyword! ls.x = 5; // error ls.i = 5; // OK const(S) *cs = &s; ls = cs; // error, less restrictive const version I think a library solution would be more crude than what the compiler could do in this regard. But another keyword, and complicating the const system further is probably too much tax for this feature. In the end, I'd say just cast away const for the fields you want to change, and at least you can get your work done.And how do we write generic code when it's practically impossible to determine const-ness from a glance? e.g. getDeterminant looks like it should be const, but wouldn't be if it had caching, so writing generic code that uses getDeterminant would be very difficult.This is the point of Walter's objection. Logical const is not const, so you would not be able to guarantee anything when you see const. This kind of feature should only be used where the 'mutable' member is not considered part of the state of the object. I.e. a cached calculation is a good example of this. This rule is not enforceable, so it is up to you to determine applicability. -Steve
Nov 29 2010
On 11/29/10 8:56 AM, Steven Schveighoffer wrote:On Sat, 20 Nov 2010 09:21:04 -0500, Peter Alexander <peter.alexander.au gmail.com> wrote:In fact it's possible to provide logical const with guarantees. You need a construct that keeps together a bool, a pure function/delegate/method, and a value, and works like this: class X { int x; lconst int y = { return x + 42; }; void m1() { x = 2; invalidate(y); } void m2() const { writeln(y); } } You can use the intrinsic invalidate() against a non-const lconst field, but not against const lconst. Reading that field checks whether the field has been invalidated. If so, it writes the field with the result of the function/delegate/method and turns the validation flag on. With this system in place, I think the lconst value is guaranteed to be as strong as const. I recall I managed to convince Walter that the system works, but we agreed it would cost too much for what it does. I think it can be implemented as a library artifact under certain assumptions. AndreiD does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it?This has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind. The thing is we *already* have a hidden field that is logically const -- an object's monitor. Regardless of an object's constancy, you can always mutate the monitor. The compiler does it by logically inserting a cast away from const, so that's what I'd suggest. In reality, once you get into the realm of logical const, the compiler no longer helps you. Any guarantees are now provided by you, not the compiler.
Nov 29 2010
On Mon, 29 Nov 2010 10:56:14 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:On 11/29/10 8:56 AM, Steven Schveighoffer wrote:This only solves the caching issue (which I agree is a large issue). There are other cases where you want a mutable pointer to other data that the object does not own. Such as a widget having a pointer to its window. Assuming that window draw routines need to be non-const, a widget cannot draw itself. You need to keep the widget to window relationship outside the class, or cast. Both of these solutions are awkward at best.On Sat, 20 Nov 2010 09:21:04 -0500, Peter Alexander <peter.alexander.au gmail.com> wrote:In fact it's possible to provide logical const with guarantees. You need a construct that keeps together a bool, a pure function/delegate/method, and a value, and works like this: class X { int x; lconst int y = { return x + 42; }; void m1() { x = 2; invalidate(y); } void m2() const { writeln(y); } } You can use the intrinsic invalidate() against a non-const lconst field, but not against const lconst. Reading that field checks whether the field has been invalidated. If so, it writes the field with the result of the function/delegate/method and turns the validation flag on. With this system in place, I think the lconst value is guaranteed to be as strong as const.D does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it?This has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind. The thing is we *already* have a hidden field that is logically const -- an object's monitor. Regardless of an object's constancy, you can always mutate the monitor. The compiler does it by logically inserting a cast away from const, so that's what I'd suggest. In reality, once you get into the realm of logical const, the compiler no longer helps you. Any guarantees are now provided by you, not the compiler.I recall I managed to convince Walter that the system works, but we agreed it would cost too much for what it does. I think it can be implemented as a library artifact under certain assumptions.It would be nice to have user-defined annotations, so we could play with possible implementations. As I recall, the acceptance of annotations in D had not occurred when logical const was last discussed, and this seems like a perfect candidate for annotations. -Steve
Nov 29 2010
On 2010-11-29 11:24:56 -0500, "Steven Schveighoffer" <schveiguy yahoo.com> said:On Mon, 29 Nov 2010 10:56:14 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:You can pass the drawing context (the window in your case) as an argument do the draw function. -- Michel Fortin michel.fortin michelf.com http://michelf.com/In fact it's possible to provide logical const with guarantees. You need a construct that keeps together a bool, a pure function/delegate/method, and a value, and works like this: class X { int x; lconst int y = { return x + 42; }; void m1() { x = 2; invalidate(y); } void m2() const { writeln(y); } } You can use the intrinsic invalidate() against a non-const lconst field, but not against const lconst. Reading that field checks whether the field has been invalidated. If so, it writes the field with the result of the function/delegate/method and turns the validation flag on. With this system in place, I think the lconst value is guaranteed to be as strong as const.This only solves the caching issue (which I agree is a large issue). There are other cases where you want a mutable pointer to other data that the object does not own. Such as a widget having a pointer to its window. Assuming that window draw routines need to be non-const, a widget cannot draw itself. You need to keep the widget to window relationship outside the class, or cast. Both of these solutions are awkward at best.
Nov 29 2010
On Mon, 29 Nov 2010 11:53:27 -0500, Michel Fortin <michel.fortin michelf.com> wrote:On 2010-11-29 11:24:56 -0500, "Steven Schveighoffer" <schveiguy yahoo.com> said:That's the "keep the relationship outside the class" solution. I find this solution substandard -- a widget should know where it is located. Another solution I didn't mention is to just not mark draw as const, and then you just have to follow the convention that draw should not mutate any internal state. I don't like this either. -SteveOn Mon, 29 Nov 2010 10:56:14 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:You can pass the drawing context (the window in your case) as an argument do the draw function.In fact it's possible to provide logical const with guarantees. You need a construct that keeps together a bool, a pure function/delegate/method, and a value, and works like this: class X { int x; lconst int y = { return x + 42; }; void m1() { x = 2; invalidate(y); } void m2() const { writeln(y); } } You can use the intrinsic invalidate() against a non-const lconst field, but not against const lconst. Reading that field checks whether the field has been invalidated. If so, it writes the field with the result of the function/delegate/method and turns the validation flag on. With this system in place, I think the lconst value is guaranteed to be as strong as const.This only solves the caching issue (which I agree is a large issue). There are other cases where you want a mutable pointer to other data that the object does not own. Such as a widget having a pointer to its window. Assuming that window draw routines need to be non-const, a widget cannot draw itself. You need to keep the widget to window relationship outside the class, or cast. Both of these solutions are awkward at best.
Nov 29 2010
On 11/29/10 10:24 AM, Steven Schveighoffer wrote:This only solves the caching issue (which I agree is a large issue). There are other cases where you want a mutable pointer to other data that the object does not own. Such as a widget having a pointer to its window. Assuming that window draw routines need to be non-const, a widget cannot draw itself. You need to keep the widget to window relationship outside the class, or cast. Both of these solutions are awkward at best.Would Rebindable help that situation?Absent annotations, would a parameterized type suffice? AndreiI recall I managed to convince Walter that the system works, but we agreed it would cost too much for what it does. I think it can be implemented as a library artifact under certain assumptions.It would be nice to have user-defined annotations, so we could play with possible implementations. As I recall, the acceptance of annotations in D had not occurred when logical const was last discussed, and this seems like a perfect candidate for annotations.
Nov 29 2010
On Mon, 29 Nov 2010 13:06:17 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:On 11/29/10 10:24 AM, Steven Schveighoffer wrote:No, the issue is not one of rebinding, it's that you want to call a non-const function (drawLine, etc on the window) while drawing a widget (which is not likely to change the state of the widget). In essence, the pointer to the window that the widget contains is an association, not an ownership. But because const is transitive (not a bad thing in most cases), the compiler assumes you want to protect that reference as well.This only solves the caching issue (which I agree is a large issue). There are other cases where you want a mutable pointer to other data that the object does not own. Such as a widget having a pointer to its window. Assuming that window draw routines need to be non-const, a widget cannot draw itself. You need to keep the widget to window relationship outside the class, or cast. Both of these solutions are awkward at best.Would Rebindable help that situation?Most likely not. How do you say that the 'draw' function switches the widget to a different parameterized type? With const, you can just slap a const on the end of the function. Here is some example of what I mean: class Widget { mutable Window location; int x, y, width, height; void draw() const { location.drawRectangle(x, y, width, height); } } where Window.drawRectangle is not a const function. -SteveAbsent annotations, would a parameterized type suffice?I recall I managed to convince Walter that the system works, but we agreed it would cost too much for what it does. I think it can be implemented as a library artifact under certain assumptions.It would be nice to have user-defined annotations, so we could play with possible implementations. As I recall, the acceptance of annotations in D had not occurred when logical const was last discussed, and this seems like a perfect candidate for annotations.
Nov 29 2010
On 11/29/2010 08:58 PM, Steven Schveighoffer wrote:Most likely not. How do you say that the 'draw' function switches the widget to a different parameterized type? With const, you can just slap a const on the end of the function. Here is some example of what I mean: class Widget { mutable Window location; int x, y, width, height; void draw() const { location.drawRectangle(x, y, width, height); } } where Window.drawRectangle is not a const function. -SteveI think we could try to devise a library solution. For example, the problem could be solved with a templated struct + "alias this", provided bugs in "alias this" are fixed. The following code works even with the current implementation (dmd 2.050): struct Mutable(T) { T value; property ref Unqual!T mutable() const { auto p = cast(Unqual!T*)&value; return *p; } alias mutable this; ref opAssign(U)(auto ref U v) const { return mutable() = v; } } class Window { void drawRectangle(int x, int y, int width, int height) { writeln("Draw"); } } class C { int x, y, width, height; Mutable!Window location; this() { location = new Window; } void draw() const { location.drawRectangle(x, y, width, height); } } void main() { const C c = new C; c.draw(); } (opAssign shouldn't be necessary. The compiler should properly forward the assignment operation to the value returned by "mutable")
Nov 29 2010
On 11/29/2010 11:22 PM, Max Samukha wrote:On 11/29/2010 08:58 PM, Steven Schveighoffer wrote:"C" is a remnant of the test I used locally. Should be renamed to Widget. p temporary in "mutable()" should not be necessary but compilation fails without it. Also, can a cast from lvalue result in lvalue wherever this makes sense?Most likely not. How do you say that the 'draw' function switches the widget to a different parameterized type? With const, you can just slap a const on the end of the function. Here is some example of what I mean: class Widget { mutable Window location; int x, y, width, height; void draw() const { location.drawRectangle(x, y, width, height); } } where Window.drawRectangle is not a const function. -SteveI think we could try to devise a library solution. For example, the problem could be solved with a templated struct + "alias this", provided bugs in "alias this" are fixed. The following code works even with the current implementation (dmd 2.050): struct Mutable(T) { T value; property ref Unqual!T mutable() const { auto p = cast(Unqual!T*)&value; return *p; } alias mutable this; ref opAssign(U)(auto ref U v) const { return mutable() = v; } } class Window { void drawRectangle(int x, int y, int width, int height) { writeln("Draw"); } } class C { int x, y, width, height; Mutable!Window location; this() { location = new Window; } void draw() const { location.drawRectangle(x, y, width, height); } } void main() { const C c = new C; c.draw(); } (opAssign shouldn't be necessary. The compiler should properly forward the assignment operation to the value returned by "mutable")
Nov 29 2010
On Mon, 29 Nov 2010 16:22:55 -0500, Max Samukha <spambox d-coding.com> wrote:On 11/29/2010 08:58 PM, Steven Schveighoffer wrote:I agree, a library solution would be ideal. Except the language says what results from your code (casting away const and then mutating) is undefined behavior. This means all bets are off, it can crash your program. I'm unsure how the compiler could take that route, but that's what's in the spec. Maybe because of the way const works, it never really is undefined, but there will always be that loophole. -SteveMost likely not. How do you say that the 'draw' function switches the widget to a different parameterized type? With const, you can just slap a const on the end of the function. Here is some example of what I mean: class Widget { mutable Window location; int x, y, width, height; void draw() const { location.drawRectangle(x, y, width, height); } } where Window.drawRectangle is not a const function. -SteveI think we could try to devise a library solution. For example, the problem could be solved with a templated struct + "alias this", provided bugs in "alias this" are fixed. The following code works even with the current implementation (dmd 2.050): struct Mutable(T) { T value; property ref Unqual!T mutable() const { auto p = cast(Unqual!T*)&value; return *p; } alias mutable this; ref opAssign(U)(auto ref U v) const { return mutable() = v; } } class Window { void drawRectangle(int x, int y, int width, int height) { writeln("Draw"); } } class C { int x, y, width, height; Mutable!Window location; this() { location = new Window; } void draw() const { location.drawRectangle(x, y, width, height); } } void main() { const C c = new C; c.draw(); } (opAssign shouldn't be necessary. The compiler should properly forward the assignment operation to the value returned by "mutable")
Nov 29 2010
On 11/29/2010 11:39 PM, Steven Schveighoffer wrote:On Mon, 29 Nov 2010 16:22:55 -0500, Max Samukha <spambox d-coding.com> wrote:Fair point.On 11/29/2010 08:58 PM, Steven Schveighoffer wrote:I agree, a library solution would be ideal. Except the language says what results from your code (casting away const and then mutating) is undefined behavior. This means all bets are off, it can crash your program. I'm unsure how the compiler could take that route, but that's what's in the spec. Maybe because of the way const works, it never really is undefined, but there will always be that loophole. -SteveMost likely not. How do you say that the 'draw' function switches the widget to a different parameterized type? With const, you can just slap a const on the end of the function. Here is some example of what I mean: class Widget { mutable Window location; int x, y, width, height; void draw() const { location.drawRectangle(x, y, width, height); } } where Window.drawRectangle is not a const function. -SteveI think we could try to devise a library solution. For example, the problem could be solved with a templated struct + "alias this", provided bugs in "alias this" are fixed. The following code works even with the current implementation (dmd 2.050): struct Mutable(T) { T value; property ref Unqual!T mutable() const { auto p = cast(Unqual!T*)&value; return *p; } alias mutable this; ref opAssign(U)(auto ref U v) const { return mutable() = v; } } class Window { void drawRectangle(int x, int y, int width, int height) { writeln("Draw"); } } class C { int x, y, width, height; Mutable!Window location; this() { location = new Window; } void draw() const { location.drawRectangle(x, y, width, height); } } void main() { const C c = new C; c.draw(); } (opAssign shouldn't be necessary. The compiler should properly forward the assignment operation to the value returned by "mutable")
Nov 29 2010
Steven Schveighoffer <schveiguy yahoo.com> wrote:Except the language says what results from your code (casting away const and then mutating) is undefined behavior. This means all bets are off, it can crash your program. I'm unsure how the compiler could take that route, but that's what's in the spec. Maybe because of the way const works, it never really is undefined, but there will always be that loophole.The thing is, immutable is implicitly castable to const, and immutable data could be stored in write-protected memory. Apart from that, I believe it is safe to cast away const. -- Simen
Nov 29 2010
Simen kjaeraas Wrote:Steven Schveighoffer <schveiguy yahoo.com> wrote:In fact, couldn't opAssign be ref opAssign(U)(auto ref U v) if(Unqual!U == U) { return mutable() = v; } This way only mutable references are only ever assigned to it? Then it can be marked as trusted and used in safe code as it will never cause the program to crash?Except the language says what results from your code (casting away const and then mutating) is undefined behavior. This means all bets are off, it can crash your program. I'm unsure how the compiler could take that route, but that's what's in the spec. Maybe because of the way const works, it never really is undefined, but there will always be that loophole.The thing is, immutable is implicitly castable to const, and immutable data could be stored in write-protected memory. Apart from that, I believe it is safe to cast away const. -- Simen
Nov 29 2010
Jesse Phillips <jessekphillips+D gmail.com> wrote:In fact, couldn't opAssign be ref opAssign(U)(auto ref U v) if(Unqual!U == U) { return mutable() = v; } This way only mutable references are only ever assigned to it? Then it can be marked as trusted and used in safe code as it will never cause the program to crash?Indeed it could. This brings me to this code: struct Mutable( T ) if ( is( T : Unqual!T ) ) { private T _payload; this( Unqual!T t ) { _payload = t; } trusted property ref T get( )( ) const { T* p = cast( T* )&_payload; return *p; } alias get this; trusted ref opAssign( U )( auto ref U u ) const if ( is( Unqual!U == U ) && is( U : T ) ) { T* p = cast( T* )&_payload; return *p = u; } bool opEquals( )( ref const Mutable other ) const { return _payload == other._payload; } bool opEquals( U )( auto ref U other ) const if ( is( U : T ) || is( T : U ) ) { return _payload == other; } } It works in many cases, but not for function calls: void bar( int n ) {} class A { Mutable!int n; } A a = new A; bar(a.n);//Error: function bar (int n) is not callable using argument types (const(Mutable!(int))) -- Simen
Nov 29 2010
Simen kjaeraas <simen.kjaras gmail.com> wrote:Wait. Correction: In the case when the object harboring the mutable member is moved into read-only memory (I do not know if this might actually happen), this ceases to be true. -- SimenThis way only mutable references are only ever assigned to it? Then it can be marked as trusted and used in safe code as it will never cause the program to crash?Indeed it could.
Nov 29 2010
Simen kjaeraas Wrote:Simen kjaeraas <simen.kjaras gmail.com> wrote:Well, the error I get seems to indicate an issue with alias this: lconst.d(5): Error: function lconst.bar (int n) is not callable using argument types (Mutable!(int)) lconst.d(5): Error: cannot implicitly convert expression (a.n) of type Mutable!(int) to int So what you are saying is that Mutable should not work on value types right? So if we make these assertions: static assert(!__traits(compiles, new class { Mutable!int n; })); immutable A a = new immutable(A); int i = 8; static assert(!__traits(compiles, a.n = &i)); static assert(__traits(compiles, *a.n = i)); static assert(__traits(compiles, bar(a.n))); The last one being what I believe to be an alias this bug, the rest assert correctly: https://gist.github.com/721066Wait. Correction: In the case when the object harboring the mutable member is moved into read-only memory (I do not know if this might actually happen), this ceases to be true. -- SimenThis way only mutable references are only ever assigned to it? Then it can be marked as trusted and used in safe code as it will never cause the program to crash?Indeed it could.
Nov 29 2010
Jesse Phillips Wrote:So what you are saying is that Mutable should not work on value types right? So if we make these assertions: static assert(!__traits(compiles, new class { Mutable!int n; })); immutable A a = new immutable(A); int i = 8; static assert(!__traits(compiles, a.n = &i)); static assert(__traits(compiles, *a.n = i)); static assert(__traits(compiles, bar(a.n))); The last one being what I believe to be an alias this bug, the rest assert correctly: https://gist.github.com/721066Oops didn't give the full context of my assertions (though it is available in the gist: void bar( int n ) {} class A { Mutable!(int*) n; }
Nov 29 2010
Simen kjaeraas <simen.kjaras gmail.com> wrote:It works in many cases, but not for function callsSome more testing brought this bug to my attention: void bar( ref int n ) { n++; } void main( string[] args ) { const int n = args.length * 0; assert( is( typeof( n ) == const(int) ) ); bar( n ); // Uhm... assert( n == 1 ); // WTF?!? } I would say this is seriously bad. http://d.puremagic.com/issues/show_bug.cgi?id=5291 -- Simen
Nov 29 2010
On 11/30/10, Simen kjaeraas <simen.kjaras gmail.com> wrote:Simen kjaeraas <simen.kjaras gmail.com> wrote:It seems this is a problem with ref. If you use "in" the compiler gets some of its senses back: void bar( in int n ) { n++; } void main( string[] args ) { const int n = args.length * 0; assert( is( typeof( n ) == const(int) ) ); bar( n ); // compile error assert( n == 1 ); }It works in many cases, but not for function callsSome more testing brought this bug to my attention: void bar( ref int n ) { n++; } void main( string[] args ) { const int n = args.length * 0; assert( is( typeof( n ) == const(int) ) ); bar( n ); // Uhm... assert( n == 1 ); // WTF?!? } I would say this is seriously bad. http://d.puremagic.com/issues/show_bug.cgi?id=5291 -- Simen
Nov 29 2010
Oh this one is even more spectacular: void bar( ref int n ) { n++; } void main( string[] args ) { immutable int n = args.length * 0; assert( is( typeof( n ) == const(int) ) ); bar( n ); // Uhm... assert( n == 1 ); } K:\code>dmd bug.d -release -O -inline K:\code>bug object.Error: assert(0) or HLT instruction I'll update the bugzilla entry. On 11/30/10, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:On 11/30/10, Simen kjaeraas <simen.kjaras gmail.com> wrote:Simen kjaeraas <simen.kjaras gmail.com> wrote:It seems this is a problem with ref. If you use "in" the compiler gets some of its senses back: void bar( in int n ) { n++; } void main( string[] args ) { const int n = args.length * 0; assert( is( typeof( n ) == const(int) ) ); bar( n ); // compile error assert( n == 1 ); }It works in many cases, but not for function callsSome more testing brought this bug to my attention: void bar( ref int n ) { n++; } void main( string[] args ) { const int n = args.length * 0; assert( is( typeof( n ) == const(int) ) ); bar( n ); // Uhm... assert( n == 1 ); // WTF?!? } I would say this is seriously bad. http://d.puremagic.com/issues/show_bug.cgi?id=5291 -- Simen
Nov 29 2010
Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:Oh this one is even more spectacular: void bar( ref int n ) { n++; } void main( string[] args ) { immutable int n = args.length * 0; assert( is( typeof( n ) == const(int) ) ); bar( n ); // Uhm... assert( n == 1 ); } K:\code>dmd bug.d -release -O -inline K:\code>bug object.Error: assert(0) or HLT instruction I'll update the bugzilla entry.Don't. It's your first assert, failing as const(int) != immutable(int) -- Simen
Nov 29 2010
No no I need more sleep, I forgot about the first assert. Sorry about that.. On 11/30/10, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:Oh this one is even more spectacular: void bar( ref int n ) { n++; } void main( string[] args ) { immutable int n = args.length * 0; assert( is( typeof( n ) == const(int) ) ); bar( n ); // Uhm... assert( n == 1 ); } K:\code>dmd bug.d -release -O -inline K:\code>bug object.Error: assert(0) or HLT instruction I'll update the bugzilla entry. On 11/30/10, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:On 11/30/10, Simen kjaeraas <simen.kjaras gmail.com> wrote:Simen kjaeraas <simen.kjaras gmail.com> wrote:It seems this is a problem with ref. If you use "in" the compiler gets some of its senses back: void bar( in int n ) { n++; } void main( string[] args ) { const int n = args.length * 0; assert( is( typeof( n ) == const(int) ) ); bar( n ); // compile error assert( n == 1 ); }It works in many cases, but not for function callsSome more testing brought this bug to my attention: void bar( ref int n ) { n++; } void main( string[] args ) { const int n = args.length * 0; assert( is( typeof( n ) == const(int) ) ); bar( n ); // Uhm... assert( n == 1 ); // WTF?!? } I would say this is seriously bad. http://d.puremagic.com/issues/show_bug.cgi?id=5291 -- Simen
Nov 29 2010
On Mon, 29 Nov 2010 17:21:27 -0500, Simen kjaeraas <simen.kjaras gmail.com> wrote:Steven Schveighoffer <schveiguy yahoo.com> wrote:One would have to ensure that data with mutable members never makes it into ROM. This should be easy for the compiler since the full type is always known at construction time. There are other issues with logical const that would need to be addressed, specifically pure functions and implicit sharing. -SteveExcept the language says what results from your code (casting away const and then mutating) is undefined behavior. This means all bets are off, it can crash your program. I'm unsure how the compiler could take that route, but that's what's in the spec. Maybe because of the way const works, it never really is undefined, but there will always be that loophole.The thing is, immutable is implicitly castable to const, and immutable data could be stored in write-protected memory. Apart from that, I believe it is safe to cast away const.
Nov 30 2010
On Tue, 30 Nov 2010 08:04:57 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:On Mon, 29 Nov 2010 17:21:27 -0500, Simen kjaeraas <simen.kjaras gmail.com> wrote:I should clarify that this only applies if logical const is a language feature, not a library feature. So in the context of this sub-thread, you are right, one needs to be careful. -SteveSteven Schveighoffer <schveiguy yahoo.com> wrote:One would have to ensure that data with mutable members never makes it into ROM. This should be easy for the compiler since the full type is always known at construction time.Except the language says what results from your code (casting away const and then mutating) is undefined behavior. This means all bets are off, it can crash your program. I'm unsure how the compiler could take that route, but that's what's in the spec. Maybe because of the way const works, it never really is undefined, but there will always be that loophole.The thing is, immutable is implicitly castable to const, and immutable data could be stored in write-protected memory. Apart from that, I believe it is safe to cast away const.
Nov 30 2010
Most likely not. How do you say that the 'draw' function switches the widget to a different parameterized type? With const, you can just slap a const on the end of the function. Here is some example of what I mean: class Widget { mutable Window location; int x, y, width, height; void draw() const { location.drawRectangle(x, y, width, height); } } where Window.drawRectangle is not a const function. -SteveLooking at the replies seems i am the only one that didn't quite get the example. 1. Why do you call it "location". Is it something like a context/handle? 2. Why does Widget have a draw function? 3. If the Window doesn't provide a const draw function, why do you insist on having one? Because Widget actually doesn't change? Well in this example it does and if the details of drawRectangle is not available to you, that change might be a serious one. IMO for this particular example mutable just encourages a bad design. On the other hand, mutable mutex is clear and reasonable since threads are different from usual program flow. Which as Sean said we have a solution. I don't think we can have "mutable" within current const design. With the vast differences between D and C++ const system we shouldn't have it anyhow. One another thing i don't quite get is why cast from immutable/const is allowed. Is it because of the cases that we know the function doesn't change anything but still lacks the immutable/const signature? Thank you! -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 01 2010
Since i called it a bad design, i am entitled to introduce a better design. interface renderer { void draw(rect rects, size_t n); } class widget { void draw(renderer r) { ... } } -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 01 2010
On Wed, 01 Dec 2010 18:38:23 +0200, so <so so.do> wrote:Since i called it a bad design, i am entitled to introduce a better design. interface renderer { void draw(rect rects, size_t n); } class widget { void draw(renderer r) { ... } }Pfft sorry for that abomination! interface renderer { void draw(rect[] rects); } class widget { rect r; window owner; void draw(renderer) const { ... } } -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 01 2010
On Wed, 01 Dec 2010 11:49:36 -0500, so <so so.do> wrote:On Wed, 01 Dec 2010 18:38:23 +0200, so <so so.do> wrote:This requires you to store the widget-renderer relationship outside the widget. Since each widget has exactly one location where it lives, this is awkward. Much better to just store the relationship on each widget. -SteveSince i called it a bad design, i am entitled to introduce a better design. interface renderer { void draw(rect rects, size_t n); } class widget { void draw(renderer r) { ... } }Pfft sorry for that abomination! interface renderer { void draw(rect[] rects); } class widget { rect r; window owner; void draw(renderer) const { ... } }
Dec 01 2010
On 1-dic-10, at 22:18, Steven Schveighoffer wrote:On Wed, 01 Dec 2010 11:49:36 -0500, so <so so.do> wrote:indeed that is one of the main things that I want from logical const: being able to store/memoize values in a structure, not outside it. It is ok to have to jump some hoops to get it, but it should be possible (casting that is guaranteed to work in some clearly defined circumstances would do it for example). FawziOn Wed, 01 Dec 2010 18:38:23 +0200, so <so so.do> wrote:This requires you to store the widget-renderer relationship outside the widget. Since each widget has exactly one location where it lives, this is awkward. Much better to just store the relationship on each widget.Since i called it a bad design, i am entitled to introduce a better design. interface renderer { void draw(rect rects, size_t n); } class widget { void draw(renderer r) { ... } }Pfft sorry for that abomination! interface renderer { void draw(rect[] rects); } class widget { rect r; window owner; void draw(renderer) const { ... } }
Dec 02 2010
so Wrote:One another thing i don't quite get is why cast from immutable/const is allowed. Is it because of the cases that we know the function doesn't change anything but still lacks the immutable/const signature? Thank you!I believe it is because cast doesn't do any checking. Cast is meant for breaking out of the type system and that is a big selling point for D. This is why casting should be avoided and to! should be used instead. Thinking about it I think I'll post another thread about opCast.
Dec 01 2010
On Wed, 01 Dec 2010 11:09:08 -0500, so <so so.do> wrote:It is where the widget is located, i.e. a physical window where the widget is drawn.Most likely not. How do you say that the 'draw' function switches the widget to a different parameterized type? With const, you can just slap a const on the end of the function. Here is some example of what I mean: class Widget { mutable Window location; int x, y, width, height; void draw() const { location.drawRectangle(x, y, width, height); } } where Window.drawRectangle is not a const function. -SteveLooking at the replies seems i am the only one that didn't quite get the example. 1. Why do you call it "location". Is it something like a context/handle?2. Why does Widget have a draw function?Really?3. If the Window doesn't provide a const draw function, why do you insist on having one? Because Widget actually doesn't change? Well in this example it does and if the details of drawRectangle is not available to you, that change might be a serious one.What changes? x, y, width and height do not change. That is considered part of the widget's state. The location is part of the state but not completely, only the association with the location is considered part of the state. This is not exactly conveyed in the type, if we have tail- references then we could use tail-mutable references as well, which means we get head-const :) Note that drawRectangle does not take any references, so there is no possibility that any of widget's state changes. That is provable. -Steve
Dec 01 2010
Sorry about that one, it was a misunderstanding on my part, was thinking i removed that. And thanks for the reply!2. Why does Widget have a draw function?Really?
Dec 01 2010
On 11/29/2010 11:56 PM, Steven Schveighoffer wrote:On Sat, 20 Nov 2010 09:21:04 -0500, Peter Alexander <peter.alexander.au gmail.com> wrote:This entire thread has been very instructive. Thanks everybody. This post is the one that, in my humble opinion of C++ game developer (like the original poster), moves more towards a solution. I have one question though, that comes from the "C++ const being totally ignored by dmc's optimizer", combined with the emphasys on D's const being verifiable by dmd: Is the "const" keyword (both in C++, and in D) for the developer writing code, or for the compiler compiling it? From what Walter said (and it makes a lot of sense to me), in the case of C++ it is only a hint to the programmer that the compiler will enforce you, but internally the compiler cannot really use it for anything. On the other side, in D, it is a hint to the compiler but it will only use it if is verifiable. If that is the case, why do we need the "const" keyword in the first place? The compiler can already verify that the code is const and do the proper opimizations. Steve's solution would be on the side of "useful for the programmer, enforced by the compiler, uselss for the compiler". Please let me know if i wrote something that doesn't make sense. JordiD does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it?This has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind. The thing is we *already* have a hidden field that is logically const -- an object's monitor. Regardless of an object's constancy, you can always mutate the monitor. The compiler does it by logically inserting a cast away from const, so that's what I'd suggest. In reality, once you get into the realm of logical const, the compiler no longer helps you. Any guarantees are now provided by you, not the compiler. I've proposed a very complex system to allow you to indicate only certain fields as const which also worked with the existing const system. While I think Walter and Andrei agreed it was possible, I agreed with them that it was too complex to ask users to deal with. What I'd like to see is a solution like this: struct S { mutable int i; int x; } S s; lconst(S) *ls = &s; // new keyword! ls.x = 5; // error ls.i = 5; // OK const(S) *cs = &s; ls = cs; // error, less restrictive const version I think a library solution would be more crude than what the compiler could do in this regard. But another keyword, and complicating the const system further is probably too much tax for this feature. In the end, I'd say just cast away const for the fields you want to change, and at least you can get your work done.And how do we write generic code when it's practically impossible to determine const-ness from a glance? e.g. getDeterminant looks like it should be const, but wouldn't be if it had caching, so writing generic code that uses getDeterminant would be very difficult.This is the point of Walter's objection. Logical const is not const, so you would not be able to guarantee anything when you see const. This kind of feature should only be used where the 'mutable' member is not considered part of the state of the object. I.e. a cached calculation is a good example of this. This rule is not enforceable, so it is up to you to determine applicability. -Steve
Dec 01 2010
Jordi Wrote:Is the "const" keyword (both in C++, and in D) for the developer writing code, or for the compiler compiling it?It is for the compiler to enforce what the developer is saying it is.From what Walter said (and it makes a lot of sense to me), in the case of C++ it is only a hint to the programmer that the compiler will enforce you, but internally the compiler cannot really use it for anything.But the compiler is does not enforce it. At least people assume it is transitive when it is not.On the other side, in D, it is a hint to the compiler but it will only use it if is verifiable. If that is the case, why do we need the "const" keyword in the first place? The compiler can already verify that the code is const and do the proper opimizations.It can, unless the source is not available. Though it could probably output the information when creating .di files.Steve's solution would be on the side of "useful for the programmer, enforced by the compiler, uselss for the compiler".But it isn't enforced by the compiler. It can enforce that you only modify things you claimed could be modified. But it can't enforce that it is logically const. It could prevent the modifiable parts from influencing the result, but then the requested caching isn't possible.Please let me know if i wrote something that doesn't make sense. JordiI do not believe you will run into issues with casting in a const function if logical const is truely happening. The areas that could be an issue is when what you are casting is immutable and placed in read-only memory. In which case you should avoid creating immutable objects that use logical const. Personally I would like to know how pointers/references in an immutable are dealt with when creating or passing them to a thread/process. struct Foo { int* foo; } immutable Foo f; I believe that if this data is copied, such as in concurrent program, the data referenced by foo may also be placed in read only memory as immutable is defined to never change (though it can due to casting). Is this correct?
Dec 01 2010
On Wed, 01 Dec 2010 12:43:38 -0500, Jesse Phillips <jessekphillips+D gmail.com> wrote:Jordi Wrote:This isn't practical. .di files are not object files, so they can be altered too easily. Note also that the compiler cannot do any optimizations for *const* functions, it can only restrict access (useful for programmer).On the other side, in D, it is a hint to the compiler but it will only use it if is verifiable. If that is the case, why do we need the "const" keyword in the first place? The compiler can already verify that the code is const and do the proper opimizations.It can, unless the source is not available. Though it could probably output the information when creating .di files.The current const regime cannot enforce that you cannot modify the logical state of the object, because it does not know if any global variables are part of the state. Logically const cannot be enforced, and it cannot be prevented by const alone. A pure function enforces that you are not modifying external state. If storage for non-state data is allowed inside the object (thereby enabling logically const data), then special rules would need to be followed for pure and immutable functions. -SteveSteve's solution would be on the side of "useful for the programmer, enforced by the compiler, uselss for the compiler".But it isn't enforced by the compiler. It can enforce that you only modify things you claimed could be modified. But it can't enforce that it is logically const. It could prevent the modifiable parts from influencing the result, but then the requested caching isn't possible.
Dec 01 2010
On 29/11/2010 14:56, Steven Schveighoffer wrote:This has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind.Could you detail a bit what do you mean by logical const == const ? That doesn't sound right to me. -- Bruno Medeiros - Software Engineer
Dec 02 2010
On Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:On 29/11/2010 14:56, Steven Schveighoffer wrote:Here is where I show how logical const already exists, it's just clunky to use. BTW, this was before TLS, so the example would have to be updated a bit. http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=58927 -SteveThis has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind.Could you detail a bit what do you mean by logical const == const ? That doesn't sound right to me.
Dec 02 2010
Steven Schveighoffer wrote:On Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:What you're doing is keeping an alternate, mutable reference to each object. This does not mean that logical const == const.On 29/11/2010 14:56, Steven Schveighoffer wrote:Here is where I show how logical const already exists, it's just clunky to use. BTW, this was before TLS, so the example would have to be updated a bit. http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmar .D&article_id=58927This has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind.Could you detail a bit what do you mean by logical const == const ? That doesn't sound right to me.
Dec 02 2010
On Thu, 02 Dec 2010 20:38:01 -0500, Walter Bright <newshound2 digitalmars.com> wrote:Steven Schveighoffer wrote:No I'm not. I'm keeping a portion of the object in a global AA. I'm not storing a mutable reference to the object itself. When you call a const function, *no data* that is defined within the data of the object is modified. It is true logical const, not a hack (in contrast, the example I gave in this thread is a hack). -SteveOn Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:What you're doing is keeping an alternate, mutable reference to each object. This does not mean that logical const == const.On 29/11/2010 14:56, Steven Schveighoffer wrote:Here is where I show how logical const already exists, it's just clunky to use. BTW, this was before TLS, so the example would have to be updated a bit. http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=58927This has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind.Could you detail a bit what do you mean by logical const == const ? That doesn't sound right to me.
Dec 03 2010
On 03/12/2010 01:38, Walter Bright wrote:Steven Schveighoffer wrote:The statement "logical const == const" is meaningless really. Please use better terms people. ~_~' What Steven was trying to say, I think, is that you can always emulate the behavior of logical const in D in a valid (safe) way, and that therefore the current D const system doesn't actually offer more guarantees than having logical const (by this I mean having mutable members). Whether this is true or not, that's the question. I don't think it is true, I've argued that in a reply to the previous post. Note that the other extreme, which you mentioned: "Having mutable members destroys any guarantees that const provides. ", is also not true. (again, argued in another post). -- Bruno Medeiros - Software EngineerOn Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:What you're doing is keeping an alternate, mutable reference to each object. This does not mean that logical const == const.On 29/11/2010 14:56, Steven Schveighoffer wrote:Here is where I show how logical const already exists, it's just clunky to use. BTW, this was before TLS, so the example would have to be updated a bit. http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=58927This has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind.Could you detail a bit what do you mean by logical const == const ? That doesn't sound right to me.
Dec 03 2010
On 02/12/2010 21:04, Steven Schveighoffer wrote:On Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:Ok. Well, for starters the "const" functions that mutate the object state cannot be pure. (if you manage to mutate it without casts in a pure function, it's because of a compiler bug) Second, there's the TLS thing. I don't think you can just "update it a bit", there would be significant changes, maybe not in code size, but in runtime effect: You would need to make it global shared, and thus have to synchronize the access to the _mutable global. This is quite significant. The above are not trivial differences, so I do not agree that it constitutes full logical const, only a limited form of it. More concretely, it doesn't constitute logical const in in the sense where you can use that as argument to say "logical const already exists, it's just clunky to use", so let's add it to the language formally. Like if mutable members where just syntax sugar, or a betterment of safety rules. There is one thing however that doesn't feel right in all of this, with regards to passing immutable objects to other threads. But I think I need to read TDPL concurrency chapter to clarify some things first of all. -- Bruno Medeiros - Software EngineerOn 29/11/2010 14:56, Steven Schveighoffer wrote:Here is where I show how logical const already exists, it's just clunky to use. BTW, this was before TLS, so the example would have to be updated a bit. http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=58927 -SteveThis has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind.Could you detail a bit what do you mean by logical const == const ? That doesn't sound right to me.
Dec 03 2010
On Fri, 03 Dec 2010 08:00:43 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:On 02/12/2010 21:04, Steven Schveighoffer wrote:Yes, pure requires special handling. I believe that mutable-marked members of an object should not be considered part of the object, just related to the object. So it would make sense to disallow access to those members in pure functions when the objects are const or immutable. It would at least make the semantics the same as logical const is defined here. When the object itself is not const or immutable, I think possibly access to the mutable members should be allowed, but I haven't worked through the details in my head yet.On Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:Ok. Well, for starters the "const" functions that mutate the object state cannot be pure. (if you manage to mutate it without casts in a pure function, it's because of a compiler bug)On 29/11/2010 14:56, Steven Schveighoffer wrote:Here is where I show how logical const already exists, it's just clunky to use. BTW, this was before TLS, so the example would have to be updated a bit. http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=58927 -SteveThis has been discussed at length on this newsgroup, and I argued for it for a long time. You will not get any traction with Walter, because I've already proven that logical const == const, and it still doesn't change his mind.Could you detail a bit what do you mean by logical const == const ? That doesn't sound right to me.Second, there's the TLS thing. I don't think you can just "update it a bit", there would be significant changes, maybe not in code size, but in runtime effect: You would need to make it global shared, and thus have to synchronize the access to the _mutable global. This is quite significant.I actually re-read the code and realized that it should work without any changes (sans the issue you bring up below with implicit sharing of immutable).The above are not trivial differences, so I do not agree that it constitutes full logical const, only a limited form of it. More concretely, it doesn't constitute logical const in in the sense where you can use that as argument to say "logical const already exists, it's just clunky to use", so let's add it to the language formally. Like if mutable members where just syntax sugar, or a betterment of safety rules.I disagree, I think it does prove logical const already exists. How do you define logical const?There is one thing however that doesn't feel right in all of this, with regards to passing immutable objects to other threads. But I think I need to read TDPL concurrency chapter to clarify some things first of all.Implicit sharing of immutable is definitely an issue to consider. If an object is immutable, then the mutable data is also implicitly sharable since the mutable data rides around with the object. I don't have a good answer for this yet, but I don't think it's a deal-killer. We may need to adjust the rules regarding sharing immutable data (i.e. you can't implicitly share immutable data, but you can cast it to shared to share it). I thought implicit unshared felt completely wrong when it was first introduced, now I think it's completely correct. -Steve
Dec 03 2010
On Fri, 03 Dec 2010 08:22:01 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:On Fri, 03 Dec 2010 08:00:43 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:I'll add to this that synchronization issues can be handled. They should not play a role in 'does logical const exist', they should only play a role in 'does efficient logical const exist'. -SteveThe above are not trivial differences, so I do not agree that it constitutes full logical const, only a limited form of it. More concretely, it doesn't constitute logical const in in the sense where you can use that as argument to say "logical const already exists, it's just clunky to use", so let's add it to the language formally. Like if mutable members where just syntax sugar, or a betterment of safety rules.I disagree, I think it does prove logical const already exists. How do you define logical const?
Dec 03 2010
On 03/12/2010 14:03, Steven Schveighoffer wrote:On Fri, 03 Dec 2010 08:22:01 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:If by "does efficient logical const exist" you mean that we can devise some language rules/changes to make logical const work in D in a safe way, without losing the safety (and performance) guarantees that we have with current D with regards to immutability and concurrency, then yes, I think we can devise such a system. I definitely don't agree with Walter that "Having mutable members destroys any guarantees that const provides. " (unless we were to do it exactly like C++, which obviously we wouldn't) Whether it is desirable or not to implement such rules in D anytime soon (if at all), well... that's another question altogether... -- Bruno Medeiros - Software EngineerOn Fri, 03 Dec 2010 08:00:43 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:I'll add to this that synchronization issues can be handled. They should not play a role in 'does logical const exist', they should only play a role in 'does efficient logical const exist'. -SteveThe above are not trivial differences, so I do not agree that it constitutes full logical const, only a limited form of it. More concretely, it doesn't constitute logical const in in the sense where you can use that as argument to say "logical const already exists, it's just clunky to use", so let's add it to the language formally. Like if mutable members where just syntax sugar, or a betterment of safety rules.I disagree, I think it does prove logical const already exists. How do you define logical const?
Dec 03 2010
On 03/12/2010 13:22, Steven Schveighoffer wrote:On Fri, 03 Dec 2010 08:00:43 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:I define logical const as the ability to specify that operations on a given object reference will not modify the logical state of that object (through that reference), and the ability for the compiler to verify that statically. Logical state is defined (_not very precisely though_) as the data subset of an object which is relevant for opEquals calculations. So in that Matrix example the elements of the Matrix arrays are part of the logical state, the cached determinant is not. Mutable members is one way to implement support for logical const in a language. (There could be other ways.)The above are not trivial differences, so I do not agree that it constitutes full logical const, only a limited form of it. More concretely, it doesn't constitute logical const in in the sense where you can use that as argument to say "logical const already exists, it's just clunky to use", so let's add it to the language formally. Like if mutable members where just syntax sugar, or a betterment of safety rules.I disagree, I think it does prove logical const already exists. How do you define logical const?You mean if you wanted to pass a mutable object back and forth? No it wouldn't, if I understood you correctly. It would merely compile, but not work (in the general case). So you would create an object, cast it to shared (which means access would now need to be synchronized), and pass it to another thread, right? However when you pass to another thread the TLS part of the object state is lost (aka the _mutable part). That might be valid for cached data that can be recalculated (like the determinant), but it would not be valid for other kinds of mutable data that the object would require and should not be cleared (like the parent Widget in the other example you gave). -- Bruno Medeiros - Software EngineerSecond, there's the TLS thing. I don't think you can just "update it a bit", there would be significant changes, maybe not in code size, but in runtime effect: You would need to make it global shared, and thus have to synchronize the access to the _mutable global. This is quite significant.I actually re-read the code and realized that it should work without any changes (sans the issue you bring up below with implicit sharing of immutable).
Dec 03 2010
On Fri, 03 Dec 2010 11:23:48 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:On 03/12/2010 13:22, Steven Schveighoffer wrote: > I actually re-read the code and realized that it should work without any > changes (sans the issue you bring up below with implicit sharing of > immutable). > You mean if you wanted to pass a mutable object back and forth? No it wouldn't, if I understood you correctly. It would merely compile, but not work (in the general case). So you would create an object, cast it to shared (which means access would now need to be synchronized), and pass it to another thread, right? However when you pass to another thread the TLS part of the object state is lost (aka the _mutable part). That might be valid for cached data that can be recalculated (like the determinant), but it would not be valid for other kinds of mutable data that the object would require and should not be cleared (like the parent Widget in the other example you gave).the mutable member is not marked as shared. It cannot be accessed on a shared instance. I guess it should be explicitly noted that a logical const notation (such as 'mutable') would not affect shared status, only const status. shared does not implicitly cast to unshared or unshared const. -Steve
Dec 03 2010
On 03/12/2010 18:23, Steven Schveighoffer wrote:On Fri, 03 Dec 2010 11:23:48 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:Oh, I see what you mean. I thought something like this (or similar) worked: shared X foo = new shared(X); synchronized(foo) { foo.mutable(2); } I was thinking that the synchronized block would remove the shared type qualifier from foo inside the block. -- Bruno Medeiros - Software EngineerOn 03/12/2010 13:22, Steven Schveighoffer wrote:the mutable member is not marked as shared. It cannot be accessed on a shared instance. I guess it should be explicitly noted that a logical const notation (such as 'mutable') would not affect shared status, only const status. shared does not implicitly cast to unshared or unshared const. -SteveI actually re-read the code and realized that it should work withoutanychanges (sans the issue you bring up below with implicit sharing of immutable).You mean if you wanted to pass a mutable object back and forth? No it wouldn't, if I understood you correctly. It would merely compile, but not work (in the general case). So you would create an object, cast it to shared (which means access would now need to be synchronized), and pass it to another thread, right? However when you pass to another thread the TLS part of the object state is lost (aka the _mutable part). That might be valid for cached data that can be recalculated (like the determinant), but it would not be valid for other kinds of mutable data that the object would require and should not be cleared (like the parent Widget in the other example you gave).
Dec 07 2010
On 3-dic-10, at 17:23, Bruno Medeiros wrote:On 03/12/2010 13:22, Steven Schveighoffer wrote:No for me the compiler *cannot* verify logical const. Logical const can be verified only in some occasions: for example a place where to store the result of a suspended evaluation (what functional languages call a thunk). A dataflow variable for example in general cannot be verified by the compiler, but should also be logically const. Normally the user should use only suspended pure operations or dataflow variables, so it is safe. Still in D I want to be able to *implement* them, and that cannot be verified by the compiler. As for why I want to be able to implement it in D, the reasons (beyond the fact that it is a system language) is that in some occasions one can implement it much more efficiently that any generic implementation (for example if one knows that the value cannot be x, x can be directly used to mark the need to update it, and if one knows the functions and arguments to call an explicit thunk (i.e. closure with heap allocation) can also be spared. So for me it is ok that the hole to implement them is ugly (haskell for example has unsafePerformIO), but I want an officially sanctioned hole. I am actually against using "mutable", it that solution should be accepeted then the name should look much worse, like unsafeValue or something like that. Casual use should be discouraged.On Fri, 03 Dec 2010 08:00:43 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:I define logical const as the ability to specify that operations on a given object reference will not modify the logical state of that object (through that reference), and the ability for the compiler to verify that statically.The above are not trivial differences, so I do not agree that it constitutes full logical const, only a limited form of it. More concretely, it doesn't constitute logical const in in the sense where you can use that as argument to say "logical const already exists, it's just clunky to use", so let's add it to the language formally. Like if mutable members where just syntax sugar, or a betterment of safety rules.I disagree, I think it does prove logical const already exists. How do you define logical const?Logical state is defined (_not very precisely though_) as the data subset of an object which is relevant for opEquals calculations. So in that Matrix example the elements of the Matrix arrays are part of the logical state, the cached determinant is not.No for me logical const means that all methods applied to the object will always return the same value, it is not connected with the data stored, that is exactly the reason one wants mutable values.Mutable members is one way to implement support for logical const in a language. (There could be other ways.)yes In any case I find tail const (or weak const, as I prefer to call it, which is simply const on referred data, but not on the one that is always locally stored on the stack) more important, and *that* can be enforced by the compiler. I think that "in" should mean the weak const, not const. Fawzi
Dec 03 2010
On 03/12/2010 23:09, Fawzi Mohamed wrote:On 3-dic-10, at 17:23, Bruno Medeiros wrote:If you have a different notion of what logical state is, then yeah, could be that the compiler cannot verify it. But considering what I meant with logical state, the compiler can verify it. Let me go back and restate what I meant by logical const: For any type, the programmer can define a subset of that type's data that composes the "logical state". He will do that using annotations on the type's members for example. Then logical const is being able to annotate function parameters (or any variable) to indicate that the function will not change the "logical state" (aka, the previously defined data subset) of that argument, and have the compiler verify this. -- Bruno Medeiros - Software EngineerOn 03/12/2010 13:22, Steven Schveighoffer wrote:No for me the compiler *cannot* verify logical const. Logical const can be verified only in some occasions: for example a place where to store the result of a suspended evaluation (what functional languages call a thunk). A dataflow variable for example in general cannot be verified by the compiler, but should also be logically const.On Fri, 03 Dec 2010 08:00:43 -0500, Bruno Medeiros <brunodomedeiros+spam com.gmail> wrote:I define logical const as the ability to specify that operations on a given object reference will not modify the logical state of that object (through that reference), and the ability for the compiler to verify that statically.The above are not trivial differences, so I do not agree that it constitutes full logical const, only a limited form of it. More concretely, it doesn't constitute logical const in in the sense where you can use that as argument to say "logical const already exists, it's just clunky to use", so let's add it to the language formally. Like if mutable members where just syntax sugar, or a betterment of safety rules.I disagree, I think it does prove logical const already exists. How do you define logical const?
Dec 07 2010
On 03/12/2010 13:00, Bruno Medeiros wrote:(if you manage to mutate it without casts in a pure function, it's because of a compiler bug)Fresh from this discussion: http://d.puremagic.com/issues/show_bug.cgi?id=5311 -- Bruno Medeiros - Software Engineer
Dec 03 2010
Peter Alexander wrote:D does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it? And how do we write generic code when it's practically impossible to determine const-ness from a glance? e.g. getDeterminant looks like it should be const, but wouldn't be if it had caching, so writing generic code that uses getDeterminant would be very difficult.What are the use cases for logical const? Are there any other important ones, apart from caching?
Nov 30 2010
On Tue, 30 Nov 2010 10:06:40 -0500, Don <nospam nospam.com> wrote:Peter Alexander wrote:Being able to associate data with an object/struct without owning the data. Deep in this thread I give an example of having a widget who knows its location and knows how to draw itself in that location. Such a function cannot be marked const even though no widget data is changed, since the draw function is not const. But the Widget doesn't own the location, it's just referencing it. At that point, const moves from compiler-verified to documentation only. -SteveD does not support logical const due to the weak guarantees that it provides. So, without logical const, how are D users supposed to provide lazy evaluation and memoization in their interfaces, given that the interface should *seem* const, e.g. class Matrix { double getDeterminant() const { /* expensive calculation */ } } If it turns out that getDeterminant is called often with the raw matrix data remaining unchanged, how can we add caching to this class without rewriting the const-ness of all code that touches it? And how do we write generic code when it's practically impossible to determine const-ness from a glance? e.g. getDeterminant looks like it should be const, but wouldn't be if it had caching, so writing generic code that uses getDeterminant would be very difficult.What are the use cases for logical const? Are there any other important ones, apart from caching?
Nov 30 2010
Steven Schveighoffer Wrote:On Tue, 30 Nov 2010 10:06:40 -0500, Don <nospam nospam.com> wrote:Hm... can a const object mutate globals?What are the use cases for logical const? Are there any other important ones, apart from caching?Being able to associate data with an object/struct without owning the data. Deep in this thread I give an example of having a widget who knows its location and knows how to draw itself in that location. Such a function cannot be marked const even though no widget data is changed, since the draw function is not const. But the Widget doesn't own the location, it's just referencing it.
Nov 30 2010
On Tuesday, November 30, 2010 09:10:03 Sean Kelly wrote:Steven Schveighoffer Wrote:Yes. All it means for a const function to be const is that its this pointer/reference is const. If you don't want it to mess with globals, you need to make it pure. - Jonathan M DavisOn Tue, 30 Nov 2010 10:06:40 -0500, Don <nospam nospam.com> wrote:Hm... can a const object mutate globals?What are the use cases for logical const? Are there any other important ones, apart from caching?Being able to associate data with an object/struct without owning the data. Deep in this thread I give an example of having a widget who knows its location and knows how to draw itself in that location. Such a function cannot be marked const even though no widget data is changed, since the draw function is not const. But the Widget doesn't own the location, it's just referencing it.
Nov 30 2010
Sean Kelly wrote:Hm... can a const object mutate globals?Yes.
Nov 30 2010
Walter Bright Wrote:Sean Kelly wrote:That's what I thought. Having a transitively const object modify a global doesn't seem much different to me than having it modify a non-owned aliased object. Excepting of course that the compiler can't know which references denote ownership and non-ownership, so the current behavior seems correct from a pragmatic perspective if nothing else.Hm... can a const object mutate globals?Yes.
Nov 30 2010
Don Wrote:What are the use cases for logical const? Are there any other important ones, apart from caching?In C++ I'd say the need to lock a mutex to safely read the const data, but synchronized largely obviates that need in D.
Nov 30 2010
On Wednesday, December 01, 2010 06:17:56 spir wrote:On Wed, 1 Dec 2010 03:22:39 -0800 Jonathan M Davis <jmdavisProg gmx.com> wrote:No. All Unqual does is help you with template constraints and static ifs. It doesn't actually change the type. In the function itself, you're still going to end up with a mutable, const, or immutable type to deal with. Unqual!T just makes it so that you don't have to check for every combination of const, immutable, shared, etc.Right, but isn't this the main point of Unqual!? (Would unqualify immutable as well, no?).What would be the consequences if D had no const, only immutable (that, IIUC, removes the latter non-guarantee)?The biggest problem would be that no function could then work on both a mutable and an immutable value (unless it could be copied by value). With const, you can pass both mutable and immutable stuff to it. Without const, any and all functions which would want to deal with both would have to be duplicated. That includes stuff like member functions."in" _is_ const. It's essentially an alias for const scope. Also, a classic example for the use of const which immutable doesn't help you with it all is returning member variables by reference or which are reference types when you don't want the caller to be able to modify them. Without const, you couldn't do that. const is huge. I'd _hate_ to see const go. The fact that D has const is one of the best things that it has going for it IMHO. I _hate_ the fact that languages like Java don't. It drives me nuts. Sure, you _can_ write programs without const - people do it all the time - but you have far fewer guarantees about your code, and it's much harder to determine which a function may or may not alter the value of a variable when you call it. - Jonathan M DavisAnd of course, as C++ shows, there are plenty of cases where having const but no immutable can be quite valuable. Just the fact that you can pass an object to a function and know with reasonable certainty (and more certainty in D than C++) than that object won't be altered can be extremely valuable. Sure, many languages get by without const, but I think that they're definitely worse off for it. And with immutable added to the mix, I think that const is that much more important.For this case, I prefere the "in" qualifier. (And imo value parameters should be "in" by default). Unless I miss important use cases, seems I would be happy with "immutable" and "in".
Dec 01 2010
Jonathan M Davis wrote:Also, a classic example for the use of const which immutable doesn't help you with it all is returning member variables by reference or which are reference types when you don't want the caller to be able to modify them. Without const, you couldn't do that. const is huge. I'd _hate_ to see const go. The fact that D has const is one of the best things that it has going for it IMHO. I _hate_ the fact that languages like Java don't. It drives me nuts. Sure, you _can_ write programs without const - people do it all the time - but you have far fewer guarantees about your code, and it's much harder to determine which a function may or may not alter the value of a variable when you call it.As far as I know, D is the only language with a workable, enforcable, const system. That means we're the pioneers in getting this done right. It's up to us to show that it is an advantage.
Dec 01 2010