www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Logical const

reply Peter Alexander <peter.alexander.au gmail.com> writes:
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
next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
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
parent reply Jordi <jordi rovira.cat> writes:
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.

 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 */;

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.
Nov 20 2010
next sibling parent BCS <anon anon.com> writes:
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
prev sibling next sibling parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
On 21/11/10 3:32 AM, Jordi wrote:
 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.

 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 */;

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.

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.
Nov 28 2010
next sibling parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
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
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
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;
 }


 Andrei

I 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
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 11/28/10 1:09 PM, Peter Alexander wrote:
 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;
 }


 Andrei

I 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?

Sorry, that was wrong. I was thinking of a lazily initialized field within an otherwise non-const object. Andrei
Nov 28 2010
prev sibling next sibling parent Michel Fortin <michel.fortin michelf.com> writes:
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
prev sibling next sibling parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
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
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 11/28/10 1:01 PM, Peter Alexander wrote:
 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.

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.
 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.

This is probably not going to get a lot of community traction :o).
 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
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
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
parent bearophile <bearophileHUGS lycos.com> writes:
 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
prev sibling parent Peter Alexander <peter.alexander.au gmail.com> writes:
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
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
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
prev sibling next sibling parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
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
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
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
parent reply Rainer Deyke <rainerd eldwood.com> writes:
On 11/28/2010 17:29, bearophile wrote:
 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.

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.com
Nov 28 2010
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
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
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
Peter Alexander wrote:
 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.

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.
 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
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
parent reply Walter Bright <newshound2 digitalmars.com> writes:
Simen kjaeraas wrote:
 Walter Bright <newshound2 digitalmars.com> wrote:
 
 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.

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();'?

Logical const means the same value is returned every time, not a different one.
Nov 29 2010
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
Simen kjaeraas wrote:
 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?

No, I said that logical const is not verifiable by the compiler
 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
parent Walter Bright <newshound2 digitalmars.com> writes:
Simen kjaeraas wrote:
 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.

I know it's a tough issue, and worth the effort at trying to explain it.
Nov 30 2010
prev sibling parent Peter Alexander <peter.alexander.au gmail.com> writes:
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
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
Steven Schveighoffer wrote:
 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.

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++.
Nov 29 2010
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
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
parent reply Walter Bright <newshound2 digitalmars.com> writes:
Peter Alexander wrote:
 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.

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.
Nov 29 2010
parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
On 29/11/10 11:13 PM, Walter Bright wrote:
 Peter Alexander wrote:
 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.

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.

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.
Nov 30 2010
parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
Steven Schveighoffer wrote:
 On Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright 
 <newshound2 digitalmars.com> wrote:
 
 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?"

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 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.
Nov 29 2010
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
parent reply Max Samukha <spambox d-coding.com> writes:
On 11/30/2010 02:35 AM, Walter Bright wrote:
 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.

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.
Nov 30 2010
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 11/30/10 5:25 AM, Max Samukha wrote:
 On 11/30/2010 02:35 AM, Walter Bright wrote:
 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.

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.

I'm not seeing half of non-trivial C++ programs using mutable. Andrei
Nov 30 2010
parent Max Samukha <spambox d-coding.com> writes:
On 11/30/2010 05:39 PM, Andrei Alexandrescu wrote:
 On 11/30/10 5:25 AM, Max Samukha wrote:
 On 11/30/2010 02:35 AM, Walter Bright wrote:
 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.

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.

I'm not seeing half of non-trivial C++ programs using mutable. Andrei

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.
Nov 30 2010
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent =?UTF-8?B?IkrDqXLDtG1lIE0uIEJlcmdlciI=?= <jeberger free.fr> writes:
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable

Walter Bright wrote:
 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.

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.fr
Nov 30 2010
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
Steven Schveighoffer wrote:
 On Tue, 30 Nov 2010 15:16:04 -0500, Walter Bright 
 <newshound2 digitalmars.com> wrote:
 
 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.

So this: void myfn(const(C) n) { assert(n.x == 1); n.foo(); assert(n.x == 5); } Wouldn't be surprising to you?

No. There are a lot of things a member "x" could be. The const only applies to the instance data.
Nov 30 2010
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
On 01/12/2010 21:09, Steven Schveighoffer wrote:
 On Tue, 30 Nov 2010 16:53:14 -0500, Walter Bright
 <newshound2 digitalmars.com> wrote:

 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.

The literal guarantee is that things aren't modified through that reference.

So now you do agree that (D's) const does provide guarantees, right? -- Bruno Medeiros - Software Engineer
Dec 02 2010
parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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:
 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.

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. 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 Davis
Dec 01 2010
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On Wed, 1 Dec 2010 03:22:39 -0800
Jonathan M Davis <jmdavisProg gmx.com> wrote:

 What would be the consequences if D had no const, only immutable (that,
 IIUC, removes the latter non-guarantee)? =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, =

 pass both mutable and immutable stuff to it. Without const, any and all f=

 which would want to deal with both would have to be duplicated. That incl=

 stuff 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=

 immutable can be quite valuable. Just the fact that you can pass an objec=

 function and know with reasonable certainty (and more certainty in D than=

 than 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=

 it. And with immutable added to the mix, I think that const is that much =

 important.

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
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent reply Don <nospam nospam.com> writes:
Walter Bright wrote:
 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.

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.
Dec 02 2010
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
Don wrote:
 Walter Bright wrote:
 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.

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.

inout applies at the top level, but you cannot define a struct that has inout fields.
Dec 02 2010
prev sibling parent reply Don <nospam nospam.com> writes:
Jonathan M Davis wrote:
 On Thursday, December 02, 2010 01:18:31 Don wrote:
 Walter Bright wrote:
 spir wrote:
 What would be the consequences if D had no const, only immutable
 (that, IIUC, removes the latter non-guarantee)?

args and again for mutable ones.

The only difference I can see between const and inout, is that inout tells which parameters could be aliased with the return value.

Except 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.
 whereas with const, you only get the one?
 
 - Jonathan M Davis

Dec 02 2010
parent Jason House <jason.james.house gmail.com> writes:
Don Wrote:

 Jonathan M Davis wrote:
 On Thursday, December 02, 2010 01:18:31 Don wrote:
 Walter Bright wrote:
 spir wrote:
 What would be the consequences if D had no const, only immutable
 (that, IIUC, removes the latter non-guarantee)?

args and again for mutable ones.

The only difference I can see between const and inout, is that inout tells which parameters could be aliased with the return value.

Except 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.

Inside a function, inout(T) should be considered a subtype of const(T). Nothing should be convertible to inout.
Dec 02 2010
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday, December 02, 2010 01:18:31 Don wrote:
 Walter Bright wrote:
 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.

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.

Except that doesn't inout actually produce multiple versions of the function, whereas with const, you only get the one? - Jonathan M Davis
Dec 02 2010
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 02 Dec 2010 15:25:49 -0500, Don <nospam nospam.com> wrote:

 Jonathan M Davis wrote:
 On Thursday, December 02, 2010 01:18:31 Don wrote:
 Walter Bright wrote:
 spir wrote:
 What would be the consequences if D had no const, only immutable
 (that, IIUC, removes the latter non-guarantee)?

args and again for mutable ones.

The only difference I can see between const and inout, is that inout tells which parameters could be aliased with the return value.



inout is different in that parameters cannot implicitly cast to inout. It's actually on the same level as immutable and mutable.
  Except 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.

This is correct, except for the implicit casting thing mentioned above. -Steve
Dec 02 2010
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
On 29/11/2010 23:04, Walter Bright wrote:
 Steven Schveighoffer wrote:
 On Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright
 <newshound2 digitalmars.com> wrote:

 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?"

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 mutable members destroys any guarantees that const provides. That's not political.

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 Engineer
Dec 02 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 01 Dec 2010 18:34:34 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 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.

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.
 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
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:
 On Tue, 30 Nov 2010 16:53:14 -0500, Walter Bright
 <newshound2 digitalmars.com> wrote:

 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.

The literal guarantee is that things aren't modified through that reference.

So now you do agree that (D's) const does provide guarantees, right?

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. -Steve
Dec 02 2010
prev sibling next sibling parent Peter Alexander <peter.alexander.au gmail.com> writes:
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
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
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 (Java, C#, etc.) have no support at all for something like logical 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
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 11/28/10 4:02 PM, Walter Bright wrote:
 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).

Logical immutability can be implemented with reasonable guarantees (s we discussed a while back in private). The question is whether the effort is justified. Andrei
Nov 28 2010
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
Jonathan M Davis wrote:
 On Sunday 28 November 2010 14:02:02 Walter Bright wrote:
 Jonathan M Davis wrote:
 Walter is certainly against logical constness,

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).

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.

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.
 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
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 28 Nov 2010 16:27:20 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 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.

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. -Steve
Nov 29 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling next sibling parent so <so so.do> writes:
 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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 29 Nov 2010 14:33:48 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 Steven Schveighoffer wrote:
 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.

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.

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++.

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();} } -Steve
Nov 29 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 29 Nov 2010 14:52:54 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 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.

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. -Steve
Nov 29 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 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?"

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. -Steve
Nov 29 2010
prev sibling next sibling parent Fawzi Mohamed <fawzi gmx.ch> writes:
On 30-nov-10, at 00:04, Walter Bright wrote:

 Steven Schveighoffer wrote:
 On Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright <newshound2 digitalmars.com 
 wrote:
 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?"

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 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.

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. Fawzi
Nov 29 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 29 Nov 2010 18:04:31 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 Steven Schveighoffer wrote:
 On Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright  
 <newshound2 digitalmars.com> wrote:

 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?"

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 mutable members destroys any guarantees that const provides. That's not political.

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.
 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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 30 Nov 2010 14:07:51 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 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.

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.
 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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 30 Nov 2010 15:16:04 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 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.

So this: void myfn(const(C) n) { assert(n.x == 1); n.foo(); assert(n.x == 5); } Wouldn't be surprising to you? -Steve
Nov 30 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 30 Nov 2010 16:05:16 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 Steven Schveighoffer wrote:
 On Tue, 30 Nov 2010 15:16:04 -0500, Walter Bright  
 <newshound2 digitalmars.com> wrote:

 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.

void myfn(const(C) n) { assert(n.x == 1); n.foo(); assert(n.x == 5); } Wouldn't be surprising to you?

No. There are a lot of things a member "x" could be. The const only applies to the instance data.

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. -Steve
Nov 30 2010
prev sibling next sibling parent so <so so.do> writes:
 Wouldn't be surprising to you?

 -Steve

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. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Nov 30 2010
prev sibling next sibling parent Andrew Wiley <debio264 gmail.com> writes:
--001636c59a2ee85c5004964caa25
Content-Type: text/plain; charset=ISO-8859-1

2010/11/30 Walter Bright <newshound2 digitalmars.com>

 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++.

somewhere of exactly what "const" means in D2 and exactly what that guaranties or doesn't guaranty?
 (One of the ironies of C++ const is that programmers assume it behaves like
 D const/immutable, but rely on it not doing so.)

--001636c59a2ee85c5004964caa25 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable <div class=3D"gmail_quote">2010/11/30 Walter Bright <span dir=3D"ltr">&lt;<= a href=3D"mailto:newshound2 digitalmars.com">newshound2 digitalmars.com</a>= &gt;</span><br><blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .8ex= ;border-left:1px #ccc solid;padding-left:1ex;"> <div class=3D"im">so wrote:<br> <blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .8ex;border-left:1p= x #ccc solid;padding-left:1ex"> No need to go that far.<br> <br> void fn(const A, A) {...}<br> A a;<br> fn(a, a); // what do you expect here?<br> <br> As far as i know D/C++ have the same treatment for const member functions.<= br> It has been this way forever.<br> </blockquote> <br> <br></div> Yup. It&#39;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 a= nd baggage people carry around about const in both D and C++.<br> <br></blockquote><div><br></div><div>I&#39;ve been following this thread on= and off, but is there a definition somewhere of exactly what &quot;const&q= uot; means in D2 and exactly what that guaranties or doesn&#39;t guaranty?= =A0</div> <blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .8ex;border-left:1p= x #ccc solid;padding-left:1ex;"> <br> (One of the ironies of C++ const is that programmers assume it behaves like= D const/immutable, but rely on it not doing so.)<br> </blockquote></div><br> --001636c59a2ee85c5004964caa25--
Nov 30 2010
prev sibling parent spir <denis.spir gmail.com> writes:
On Tue, 30 Nov 2010 15:03:43 -0800
Walter Bright <newshound2 digitalmars.com> wrote:

 Andrew Wiley wrote:
 I'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

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=

 reference or anything reachable through it.
=20
 It does not guarantee that there isn't a mutable alias to that reference=

 elsewhere 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
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday 28 November 2010 06:50:53 Peter Alexander wrote:
 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 game=


 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. =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!?

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 Davis
Nov 28 2010
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday 28 November 2010 14:02:02 Walter Bright wrote:
 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).

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 Davis
Nov 28 2010
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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:

 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.

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. Andrei
Nov 29 2010
next sibling parent Michel Fortin <michel.fortin michelf.com> writes:
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:
 
 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.

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/
Nov 29 2010
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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?
 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.

Absent annotations, would a parameterized type suffice? Andrei
Nov 29 2010
next sibling parent reply Max Samukha <spambox d-coding.com> writes:
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.

 -Steve

I 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
next sibling parent Max Samukha <spambox d-coding.com> writes:
On 11/29/2010 11:22 PM, Max Samukha wrote:
 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.

 -Steve

I 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")

"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?
Nov 29 2010
prev sibling next sibling parent Max Samukha <spambox d-coding.com> writes:
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:

 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.

 -Steve

I 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")

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. -Steve

Fair point.
Nov 29 2010
prev sibling parent reply Jesse Phillips <jessekphillips+D gmail.com> writes:
Simen kjaeraas Wrote:

 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

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?
Nov 29 2010
parent reply Jesse Phillips <jessekphillips+D gmail.com> writes:
Simen kjaeraas Wrote:

 Simen kjaeraas <simen.kjaras gmail.com> wrote:
 
 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.

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. -- Simen

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/721066
Nov 29 2010
parent Jesse Phillips <jessekphillips+D gmail.com> writes:
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/721066

Oops 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
prev sibling parent Jesse Phillips <jessekphillips+D gmail.com> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:
 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.

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.
 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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:
 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?

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.
 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.

Absent annotations, would a parameterized type suffice?

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. -Steve
Nov 29 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:

 On Mon, 29 Nov 2010 10:56:14 -0500, Andrei Alexandrescu   
 <SeeWebsiteForEmail erdani.org> 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.

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.

You can pass the drawing context (the window in your case) as an argument do the draw function.

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. -Steve
Nov 29 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:
 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.

 -Steve

I 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")

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. -Steve
Nov 29 2010
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
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
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
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
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Simen kjaeraas <simen.kjaras gmail.com> wrote:

 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.

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. -- Simen
Nov 29 2010
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Simen kjaeraas <simen.kjaras gmail.com> wrote:

 It works in many cases, but not for function calls

Some 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
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 11/30/10, Simen kjaeraas <simen.kjaras gmail.com> wrote:
 Simen kjaeraas <simen.kjaras gmail.com> wrote:

 It works in many cases, but not for function calls

Some 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

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 ); }
Nov 29 2010
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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 works in many cases, but not for function calls

Some 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

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 ); }

Nov 29 2010
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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 works in many cases, but not for function calls

Some 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

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 ); }


Nov 29 2010
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 29 Nov 2010 17:21:27 -0500, Simen kjaeraas  
<simen.kjaras gmail.com> wrote:

 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.

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. -Steve
Nov 30 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:

 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.

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.

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. -Steve
Nov 30 2010
prev sibling next sibling parent reply Jordi <jordi rovira.cat> writes:
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:

 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

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. Jordi
Dec 01 2010
parent Jesse Phillips <jessekphillips+D gmail.com> writes:
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.
 
 Jordi

I 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
prev sibling next sibling parent so <so so.do> writes:
 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.

 -Steve

Looking 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
prev sibling next sibling parent so <so so.do> writes:
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
prev sibling next sibling parent so <so so.do> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 01 Dec 2010 11:09:08 -0500, so <so so.do> 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.

 -Steve

Looking 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?

It is where the widget is located, i.e. a physical window where the widget is drawn.
 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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:

 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 { ... } }

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. -Steve
Dec 01 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 01 Dec 2010 12:43:38 -0500, Jesse Phillips  
<jessekphillips+D gmail.com> wrote:

 Jordi Wrote:

 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.

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).
 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.

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. -Steve
Dec 01 2010
prev sibling next sibling parent so <so so.do> writes:
 2. Why does Widget have a draw function?

Really?

Sorry about that one, it was a misunderstanding on my part, was thinking i removed that. And thanks for the reply!
Dec 01 2010
prev sibling next sibling parent Fawzi Mohamed <fawzi gmx.ch> writes:
On 1-dic-10, at 22:18, Steven Schveighoffer wrote:

 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:

 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 { ... } }

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.

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). Fawzi
Dec 02 2010
prev sibling next sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
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
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
Steven Schveighoffer wrote:
 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:
 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.

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=58927

What you're doing is keeping an alternate, mutable reference to each object. This does not mean that logical const == const.
Dec 02 2010
parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
On 03/12/2010 01:38, Walter Bright wrote:
 Steven Schveighoffer wrote:
 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:
 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.

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

What you're doing is keeping an alternate, mutable reference to each object. This does not mean that logical const == const.

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 Engineer
Dec 03 2010
prev sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
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:

 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.

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 -Steve

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 Engineer
Dec 03 2010
next sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
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
prev sibling next sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
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:

 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?

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.)
 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).

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 Engineer
Dec 03 2010
next sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
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:

 On 03/12/2010 13:22, Steven Schveighoffer wrote:
 I actually re-read the code and realized that it should work without

 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

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 Engineer
Dec 07 2010
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
On 03/12/2010 23:09, Fawzi Mohamed wrote:
 On 3-dic-10, at 17:23, Bruno Medeiros wrote:

 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:

 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?

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.

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.

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 Engineer
Dec 07 2010
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
On 03/12/2010 14:03, Steven Schveighoffer wrote:
 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:

 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?

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'. -Steve

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 Engineer
Dec 03 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:
 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.

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 -Steve
Dec 02 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 02 Dec 2010 20:38:01 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 Steven Schveighoffer wrote:
 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:
 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.

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

What you're doing is keeping an alternate, mutable reference to each object. This does not mean that logical const == const.

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). -Steve
Dec 03 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:
 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:
 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.

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 -Steve

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)

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.
 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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:

 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?

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'. -Steve
Dec 03 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling parent Fawzi Mohamed <fawzi gmx.ch> writes:
On 3-dic-10, at 17:23, Bruno Medeiros wrote:

 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:

 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?

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.

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.
 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
prev sibling next sibling parent so <so so.do> writes:
 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
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Walter Bright <newshound2 digitalmars.com> wrote:

 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.

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. -- Simen
Nov 29 2010
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Walter Bright <newshound2 digitalmars.com> wrote:

 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.

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();'? -- Simen
Nov 29 2010
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
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
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
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
prev sibling next sibling parent reply Don <nospam nospam.com> writes:
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
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 30 Nov 2010 10:06:40 -0500, Don <nospam nospam.com> wrote:

 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?

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. -Steve
Nov 30 2010
next sibling parent reply Sean Kelly <sean invisibleduck.org> writes:
Steven Schveighoffer Wrote:

 On Tue, 30 Nov 2010 10:06:40 -0500, Don <nospam nospam.com> wrote:
 
 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.

Hm... can a const object mutate globals?
Nov 30 2010
parent reply Walter Bright <newshound2 digitalmars.com> writes:
Sean Kelly wrote:
 Hm... can a const object mutate globals?

Yes.
Nov 30 2010
parent Sean Kelly <sean invisibleduck.org> writes:
Walter Bright Wrote:

 Sean Kelly wrote:
 Hm... can a const object mutate globals?

Yes.

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.
Nov 30 2010
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, November 30, 2010 09:10:03 Sean Kelly wrote:
 Steven Schveighoffer Wrote:
 On Tue, 30 Nov 2010 10:06:40 -0500, Don <nospam nospam.com> wrote:
 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.

Hm... can a const object mutate globals?

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 Davis
Nov 30 2010
prev sibling parent Sean Kelly <sean invisibleduck.org> writes:
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
prev sibling next sibling parent reply Fawzi Mohamed <fawzi gmx.ch> writes:
On 30-nov-10, at 16:39, Andrei Alexandrescu wrote:

 On 11/30/10 5:25 AM, Max Samukha wrote:
 On 11/30/2010 02:35 AM, Walter Bright wrote:
 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.

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.

I'm not seeing half of non-trivial C++ programs using mutable.

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).
Nov 30 2010
parent Jesse Phillips <jessekphillips+D gmail.com> writes:
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
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
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:
 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.

Right, but isn't this the main point of Unqual!? (Would unqualify immutable as well, no?).

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.
 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.

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".

"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 Davis
Dec 01 2010
parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 30 Nov 2010 16:53:14 -0500, Walter Bright  
<newshound2 digitalmars.com> wrote:

 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.

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. -Steve
Dec 01 2010
prev sibling parent Fawzi Mohamed <fawzi gmx.ch> writes:
On 1-dic-10, at 04:52, Jesse Phillips wrote:

 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.

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).
 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.

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.
 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

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 D1
 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?

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.
Dec 02 2010