Re: logical const idea - scratchspace
On 14-05-2012 21:51, Steven Schveighoffer wrote: I have an idea on how to create logical const without any language or compiler changes -- it will exist purely in druntime. The idea is based on this: Whenever you allocate an object, you use a memory block. An object, by default, has the following memory layout: monitor - (void *).sizeof bytes vtbl - (void *).sizeof bytes interface_vtbls[] - (void *)sizeof x number of interfaces. So by default, 8 bytes on 32bit, 16 bytes on 64 bit. Add any members, and they may increase the size. This object goes into the GC heap. Yet the GC heap has only 16, 32, 64, 128, etc. sized blocks. So for instance a class object that requires 24 bytes actually consumes 32. This leaves 8 bytes of scratch space. Using a druntime lookup we can get access to that entire memory block, including the scratch space. And since we use druntime to look it up, *not* the object and its contained members (which remember don't include the scratch space), it is *not* typed as const or immutable, or whatever the class data is. In essence, a const(MyObj) is a pointer to a struct that looks like: struct FicticiousMyObjStruct { const(MyObj_data); // not a reference, the actual data ubyte[8] scratchspace; } So we need two pieces for this proposal: 1. An accessor in Object for this scratch space. This should be a) efficient, and b) opaque. 2. An allocator for a new object that can allocate a minimal scratch space. So for instance, if your object consumes 32 bytes, but you need 20 bytes of scratch space, you want the runtime to allocate a 64 byte block. So instead of saying new MyObject, you'd say newScratchSpace!MyObject(20) And I think that's it. Since nothing before this proposal ever referred to or used that scratch space, it's not in danger of breaking existing code. The only caveat is, it can't properly be typed as shared or not (it could be accessible from multiple threads, depending on if the actual type is immutable). It also should be recommended that the scratch space not contain any GC pointers, since it's *not* participating in the type system properly, the GC may not treat it as a pointer. That renders it useless for caching e.g. a string though... And of course, we need a better name than newScratchSpace. -Steve -- - Alex
Re: logical const idea - scratchspace
On Mon, May 14, 2012 at 03:51:09PM -0400, Steven Schveighoffer wrote: I have an idea on how to create logical const without any language or compiler changes -- it will exist purely in druntime. [...] In essence, a const(MyObj) is a pointer to a struct that looks like: struct FicticiousMyObjStruct { const(MyObj_data); // not a reference, the actual data ubyte[8] scratchspace; } So we need two pieces for this proposal: 1. An accessor in Object for this scratch space. This should be a) efficient, and b) opaque. 2. An allocator for a new object that can allocate a minimal scratch space. So for instance, if your object consumes 32 bytes, but you need 20 bytes of scratch space, you want the runtime to allocate a 64 byte block. So instead of saying new MyObject, you'd say newScratchSpace!MyObject(20) And I think that's it. Since nothing before this proposal ever referred to or used that scratch space, it's not in danger of breaking existing code. The only caveat is, it can't properly be typed as shared or not (it could be accessible from multiple threads, depending on if the actual type is immutable). [...] This is all cool and everything, but I'm having a bit of trouble imagining how this helps us to implement logical const. Is the idea merely for every object to come with some sort of extra untyped non-const space that can be used for memoization, storing temporary state, etc.? How is this different from introducing a 'mutable' keyword to the language, or some other such change? (A mutable member of a const object is essentially the same as a member stored in this 'scratch space' of yours.) This still doesn't help establish logical constness, since nothing stops you from creating a class with all data inside the scratch space, in which case const/immutable no longer has any meaning. Then we will have devolved back to the land of C++'s const, where calling an immutable method can freely modify arbitrary data (except that instead of a cast we use scratchspace). T -- Just because you survived after you did it, doesn't mean it wasn't stupid!
Re: logical const idea - scratchspace
On 14.05.2012 23:51, Steven Schveighoffer wrote: I have an idea on how to create logical const without any language or compiler changes -- it will exist purely in druntime. The idea is based on this: Whenever you allocate an object, you use a memory block. An object, by default, has the following memory layout: monitor - (void *).sizeof bytes vtbl - (void *).sizeof bytes interface_vtbls[] - (void *)sizeof x number of interfaces. So by default, 8 bytes on 32bit, 16 bytes on 64 bit. Add any members, and they may increase the size. This object goes into the GC heap. Yet the GC heap has only 16, 32, 64, 128, etc. sized blocks. So for instance a class object that requires 24 bytes actually consumes 32. This leaves 8 bytes of scratch space. Using a druntime lookup we can get access to that entire memory block, including the scratch space. And since we use druntime to look it up, *not* the object and its contained members (which remember don't include the scratch space), it is *not* typed as const or immutable, or whatever the class data is. In essence, a const(MyObj) is a pointer to a struct that looks like: struct FicticiousMyObjStruct { const(MyObj_data); // not a reference, the actual data ubyte[8] scratchspace; } So we need two pieces for this proposal: 1. An accessor in Object for this scratch space. This should be a) efficient, and b) opaque. 2. An allocator for a new object that can allocate a minimal scratch space. So for instance, if your object consumes 32 bytes, but you need 20 bytes of scratch space, you want the runtime to allocate a 64 byte block. So instead of saying new MyObject, you'd say newScratchSpace!MyObject(20) Hack of the year? It looks somewhat backwards but I like it. Especially the no changes in the compiler/language. And I think that's it. Since nothing before this proposal ever referred to or used that scratch space, it's not in danger of breaking existing code. The only caveat is, it can't properly be typed as shared or not (it could be accessible from multiple threads, depending on if the actual type is immutable). I take it that you just love reusing slack space found after the sloppy D runtime in some beneficial nontrivial way! :) It also should be recommended that the scratch space not contain any GC pointers, since it's *not* participating in the type system properly, the GC may not treat it as a pointer. And of course, we need a better name than newScratchSpace. -Steve -- Dmitry Olshansky
Re: logical const idea - scratchspace
On Mon, 14 May 2012 15:56:37 -0400, Alex Rønne Petersen xtzgzo...@gmail.com wrote: On 14-05-2012 21:51, Steven Schveighoffer wrote: It also should be recommended that the scratch space not contain any GC pointers, since it's *not* participating in the type system properly, the GC may not treat it as a pointer. That renders it useless for caching e.g. a string though... Yes, it does. Unless you know the size of the string (so you can allocate enough scratch space to hold it). It's not perfect, for sure. But it might be better than nothing... -Steve
Re: logical const idea - scratchspace
On 14-05-2012 22:13, Steven Schveighoffer wrote: On Mon, 14 May 2012 15:56:37 -0400, Alex Rønne Petersen xtzgzo...@gmail.com wrote: On 14-05-2012 21:51, Steven Schveighoffer wrote: It also should be recommended that the scratch space not contain any GC pointers, since it's *not* participating in the type system properly, the GC may not treat it as a pointer. That renders it useless for caching e.g. a string though... Yes, it does. Unless you know the size of the string (so you can allocate enough scratch space to hold it). It's not perfect, for sure. But it might be better than nothing... -Steve But is there any reason we can't just have the GC check the scratch space? If it's all zero, it clearly contains nothing of interest, but if it's non-zero, just scan it like regular object memory. -- - Alex
Re: logical const idea - scratchspace
On 14-05-2012 21:51, Steven Schveighoffer wrote: I have an idea on how to create logical const without any language or compiler changes -- it will exist purely in druntime. The idea is based on this: Whenever you allocate an object, you use a memory block. An object, by default, has the following memory layout: monitor - (void *).sizeof bytes vtbl - (void *).sizeof bytes interface_vtbls[] - (void *)sizeof x number of interfaces. So by default, 8 bytes on 32bit, 16 bytes on 64 bit. Add any members, and they may increase the size. This object goes into the GC heap. Yet the GC heap has only 16, 32, 64, 128, etc. sized blocks. So for instance a class object that requires 24 bytes actually consumes 32. This leaves 8 bytes of scratch space. Using a druntime lookup we can get access to that entire memory block, including the scratch space. And since we use druntime to look it up, *not* the object and its contained members (which remember don't include the scratch space), it is *not* typed as const or immutable, or whatever the class data is. In essence, a const(MyObj) is a pointer to a struct that looks like: struct FicticiousMyObjStruct { const(MyObj_data); // not a reference, the actual data ubyte[8] scratchspace; } So we need two pieces for this proposal: 1. An accessor in Object for this scratch space. This should be a) efficient, and b) opaque. 2. An allocator for a new object that can allocate a minimal scratch space. So for instance, if your object consumes 32 bytes, but you need 20 bytes of scratch space, you want the runtime to allocate a 64 byte block. So instead of saying new MyObject, you'd say newScratchSpace!MyObject(20) And I think that's it. Since nothing before this proposal ever referred to or used that scratch space, it's not in danger of breaking existing code. The only caveat is, it can't properly be typed as shared or not (it could be accessible from multiple threads, depending on if the actual type is immutable). It also should be recommended that the scratch space not contain any GC pointers, since it's *not* participating in the type system properly, the GC may not treat it as a pointer. And of course, we need a better name than newScratchSpace. -Steve Another concern I have is that this couples a feature tightly to the implementation of the GC. What if another GC doesn't use the same allocation scheme? -- - Alex
Re: logical const idea - scratchspace
On 14-05-2012 23:06, Alex Rønne Petersen wrote: On 14-05-2012 22:13, Steven Schveighoffer wrote: On Mon, 14 May 2012 15:56:37 -0400, Alex Rønne Petersen xtzgzo...@gmail.com wrote: On 14-05-2012 21:51, Steven Schveighoffer wrote: It also should be recommended that the scratch space not contain any GC pointers, since it's *not* participating in the type system properly, the GC may not treat it as a pointer. That renders it useless for caching e.g. a string though... Yes, it does. Unless you know the size of the string (so you can allocate enough scratch space to hold it). It's not perfect, for sure. But it might be better than nothing... -Steve But is there any reason we can't just have the GC check the scratch space? If it's all zero, it clearly contains nothing of interest, but if it's non-zero, just scan it like regular object memory. Further, we could use a user marking scheme where writing anything non-zero to the space flags it dirty or something. There are probably lots of ways we could do this. -- - Alex
Re: logical const idea - scratchspace
On Mon, 14 May 2012 17:11:14 -0400, Alex Rønne Petersen xtzgzo...@gmail.com wrote: Another concern I have is that this couples a feature tightly to the implementation of the GC. What if another GC doesn't use the same allocation scheme? newScratchSpace uses GC.malloc to ensure the block is big enough. The GC must support returning a block of memory large enough to hold the requested bytes. It's not tightly coupled, even though it depends on the GC. -Steve
Re: logical const without casts!
Steven Schveighoffer , dans le message (digitalmars.D:145767), a écrit : On Thu, 29 Sep 2011 13:26:11 -0400, Steven Schveighoffer schvei...@yahoo.com wrote: On Thu, 29 Sep 2011 13:06:55 -0400, Simen Kjaeraas simen.kja...@gmail.com wrote: On Thu, 29 Sep 2011 16:54:24 +0200, Steven Schveighoffer schvei...@yahoo.com wrote: I just thought of an interesting way to make a logical const object without casts. It requires a little extra storage, but works without changes to the current compiler (and requires no casts). [snip] What do people think about this? This is what I think about it: I agree this breaks immutability, and needs to be addressed. I think probably implicit casting of delegates (or items that containe delegates) to immutable from strong-pure functions should be disallowed. But it's not the pattern I described, and uses a relatively new trick (implicit immutable casting). I'll file a bug for this case. http://d.puremagic.com/issues/show_bug.cgi?id=6741 Well, if the langage wants to be consistent, you should prevent implicit casting of delegates to const, not just to immutable. What you revealed is very interesting, but it is clearly a bug. A delegate context pointer in a const object should be const, and the delegate function should be const with regard to its context pointer if you want the function to be called when the delegate is const. Constness has to apply to delegates just like it applies to structs. The explanation gets messy. An example: This is what the compiler should say: class A { int n; void delegate( ) dg; } pure A createAnA( int n ) { A result = new A; result.n = n; result.dg = (){ result.n++; }; return result; } void main( ) { immutable A tmp = createAnA( 3 ); assert( tmp.n == 3 ); tmp.dg(); ^error: cannot call non const delegate A.dg from an immutable object. assert( tmp.n == 3 ); } An this is the fix: class A { int n; void delegate( ) const dg; // or const(void delegate()) dg; } pure A createAnA( int n ) { A result = new A; result.n = n; // result.dg = (){ result.n++; }; would result in: // error: non const (unamed) delegate cannot be assigned to // const delegate A.dg. result.dg = (){ result.n+1; }; // this one is fine. return result; } void main( ) { immutable A tmp = createAnA( 3 ); assert( tmp.n == 3 ); tmp.dg(); // OK, A.dg is const so it can be called assert( tmp.n == 3 ); } I guess you could also hide a mutable pointer of an immutable object during its construction with the same trick. -- Christophe
Re: logical const without casts!
On Fri, 30 Sep 2011 09:27:31 -0400, Christophe trav...@phare.normalesup.org wrote: Steven Schveighoffer , dans le message (digitalmars.D:145767), a écrit : On Thu, 29 Sep 2011 13:26:11 -0400, Steven Schveighoffer schvei...@yahoo.com wrote: On Thu, 29 Sep 2011 13:06:55 -0400, Simen Kjaeraas simen.kja...@gmail.com wrote: On Thu, 29 Sep 2011 16:54:24 +0200, Steven Schveighoffer schvei...@yahoo.com wrote: I just thought of an interesting way to make a logical const object without casts. It requires a little extra storage, but works without changes to the current compiler (and requires no casts). [snip] What do people think about this? This is what I think about it: I agree this breaks immutability, and needs to be addressed. I think probably implicit casting of delegates (or items that containe delegates) to immutable from strong-pure functions should be disallowed. But it's not the pattern I described, and uses a relatively new trick (implicit immutable casting). I'll file a bug for this case. http://d.puremagic.com/issues/show_bug.cgi?id=6741 Well, if the langage wants to be consistent, you should prevent implicit casting of delegates to const, not just to immutable. What you revealed is very interesting, but it is clearly a bug. A delegate context pointer in a const object should be const, and the delegate function should be const with regard to its context pointer if you want the function to be called when the delegate is const. Constness has to apply to delegates just like it applies to structs. The explanation gets messy. If const must be a part of the signature, then you have lost the typeless aspect of the context pointer. If we expose the const (or not const) nature of the pointer, then you lose the interoperability that delegates provide. The beauty of delegates is you don't *have to* care what the type of the context pointer is. That's defined by the function the delegate represents. It's why, for example, a function accepting a delegate does not distinguish between a delegate literal and a delegate to a member function of type Foo or a delegate to a member function of type const(Bar). You must think of the context pointer as a hidden parameter to the delegate, as defined when the delegate was created *not* when it is called. The fact that it's actually stored with the delegate pointer is irrelevant. Conceptually, it's not stored anywhere, it's just a parameter. But in the case of the bug I filed, we are talking about a compiler-sanctioned implicit transformation to immutable. We *must* guarantee that when we allow an implicit transformation, that there are no existing mutable copies of the data. An explicit transformation should be allowed, because then the user has accepted responsibility. The same is not true for const. It's *perfectly legal* to have a const and mutable reference to an object at the same time. The only reason transitive const exists and is necessary is to support transitive immtuable. Const is never guaranteed to prevent changing data on an object: class C { int x; void multiply(C other) const {other.x *= x;} } Is multiply guaranteed not to change the object? No: c.multiply(c); However, if c is immutable, it *is* guaranteed, because there's no way to pass c as the parameter to multiply. That is why I think the solution works -- if you allow the compiler to enforce the rules of const and immutable, you can still do unexpected things, but you do not break the guarantees of immutability. It's definitely a loophople, but I think it's valid. I guess you could also hide a mutable pointer of an immutable object during its construction with the same trick. But saving a mutable object as immutable requires a cast. It means compiler, I take responsibility for ensuring this no longer has any mutable references. When the cast is *not* required, it's the compiler's responsibility. -Steve
Re: logical const without casts!
Steven Schveighoffer , dans le message (digitalmars.D:145821), a écrit : If const must be a part of the signature, then you have lost the typeless aspect of the context pointer. If we expose the const (or not const) nature of the pointer, then you lose the interoperability that delegates provide. The beauty of delegates is you don't *have to* care what the type of the context pointer is. That's defined by the function the delegate represents. No, you don't lose the typeless aspect of the context pointer. You lose a litle bit of that aspect, but not that much. In D, const is transitive. That means: everything you touch via a const object remains const. non-const delegate context pointer breaks that rule. You have const member functions, and initially, delegates were member functions associated with an object, so why could not the delegate be const ? A const delegate would just be a delegate that promise it doesn't touch to its context. It's not a type-defined delegate. If I implement a delegate at the library level, when I call the function, I have to make a cast to access the context pointer. With cast removing implicitely the constness of the context pointer, I get the current D implementation of delegates. This is legal, but this is also undefined behavior. I believe that langage delegate work that way, that they are thus undefined behavior, and this should be corrected. I agree the langage could stipulate that delegate context pointer are exceptions, to allow the valuable trick like you revealed. But, to me, that is not the current policy about constness. Do you see the problem in the following code: class Foo { this(ref int _i) { i = () { return _i; } } ref int delegate() dg; ref int i() const { return dg(); } } int globalInt; immutable Foo globalFoo = Foo(globalInt); int bar(const Foo foo) { return foo.i++; } int globalBar() { return bar(globalFoo); } Answer: In multithreaded application, globalFoo, which is immutable, is automatically shared. However, globalInt is not. globalBar allows you to access to (and modify) globalInt. But globalInt is not protected for concurrent access. And globalInt from which thread is accessed ? Possible solutions: - do nothing, and let the programmer introducing multi-threading in the application deal with this, even if the programmers of Foo, bar and buggy did not cared to document the impact of their code for multithreaded applications. - forbid to put/call any delegate into an immutable object: that almost means forbiding to put/call a delegate into a const object. - what I propose: implement the separate kind of const delegates, that allows to protect their context pointers, and that you can safely call from const/immutable data. But in the case of the bug I filed, we are talking about a compiler-sanctioned implicit transformation to immutable. We *must* guarantee that when we allow an implicit transformation, that there are no existing mutable copies of the data. An explicit transformation should be allowed, because then the user has accepted responsibility. With my proposal, you can very easily keep an immutable reference in a const delegate. The delegate will just not be callable if its function pointer is not const with regard to the context pointer. In any case, if the langage decides that delegate context pointer should escape const protection, great care should be taken to make sure they don't escape purity. In my example, there should be a compiler error if I tried to declare Foo.i and bar pure (maybe there is already an error, I didn't test). -- Christophe
Re: logical const without casts!
Am 29.09.2011, 16:54 Uhr, schrieb Steven Schveighoffer schvei...@yahoo.com: I just thought of an interesting way to make a logical const object without casts. It requires a little extra storage, but works without changes to the current compiler (and requires no casts). Here's the code, then I'll talk about the implications: import std.stdio; class D { D getme() { return this;} void foo() {writeln(mutable!);} } class C { D delegate() d; this() { auto dinst = new D; this.d = dinst.getme; } void bar() const { d().foo();} } void main() { auto c = new C; c.bar(); } [...] What do people think about this? -Steve I think this is a creative use of delegates! class Vendor { void sell() { ++_sold; } @property int sold() { return _sold; } private: int _sold = 1; } class Car { this(Vendor vendor) { _vendorDlg = () { return vendor; }; } Vendor getVendor() const { return _vendorDlg(); } private: Vendor delegate() _vendorDlg; } void main(string[] args) { immutable Car car = new immutable(Car)(new Vendor()); Vendor vendor = car.getVendor(); vendor.sell(); }
Re: logical const without casts!
On 2011-09-29 14:54:24 +, Steven Schveighoffer schvei...@yahoo.com said: I just thought of an interesting way to make a logical const object without casts. It requires a little extra storage, but works without changes to the current compiler (and requires no casts). Here's the code, then I'll talk about the implications: import std.stdio; class D { D getme() { return this;} void foo() {writeln(mutable!);} } class C { D delegate() d; this() { auto dinst = new D; this.d = dinst.getme; } void bar() const { d().foo();} } void main() { auto c = new C; c.bar(); } outputs: mutable! So how does it work? It works because delegates and especially the delegate data is *not* affected by const. So even when C is temporarily cast to const, the delegate is not affected (i.e. it's context pointer is not temporarily cast to const). Doesn't this poke holes in const? Of course it does, but no more holes than are present via another logical const scheme (i.e. using a globally stored AA to retrieve the data). This is a hole in the transitive const, because the delegate contains a pointer to mutable data. It also is a potential source of of low level races since returning that type from a pure function could make it immutable, which can then make this mutable data accessible to multiple threads with no synchronization or atomics to protect the data's integrity. I'm actually thinking that very controlled patterns of logical const like this could be implemented via mixin, and be sanctioned by the library. The way this pattern works, you can dictate as the author of a class whether that class can be a logically const part of another object or not, simply by choosing to implement getme or not. Whatever the implementation I think this is deeply needed. It is needed because people are trying all sorts of things to work around const transitivity, many of which are subtly unsafe. -- Michel Fortin michel.for...@michelf.com http://michelf.com/
Re: logical const without casts!
On Fri, 30 Sep 2011 12:16:03 -0400, Christophe trav...@phare.normalesup.org wrote: Steven Schveighoffer , dans le message (digitalmars.D:145821), a écrit : If const must be a part of the signature, then you have lost the typeless aspect of the context pointer. If we expose the const (or not const) nature of the pointer, then you lose the interoperability that delegates provide. The beauty of delegates is you don't *have to* care what the type of the context pointer is. That's defined by the function the delegate represents. No, you don't lose the typeless aspect of the context pointer. You lose a litle bit of that aspect, but not that much. In D, const is transitive. That means: everything you touch via a const object remains const. non-const delegate context pointer breaks that rule. But transitive const by itself doesn't get you any guarantees. It does not prevent you from modifying that (mutable) value via another pointer. What it does is allow code that works with both transitive immutable and mutable without having to copy code. You have const member functions, and initially, delegates were member functions associated with an object, so why could not the delegate be const ? A const delegate would just be a delegate that promise it doesn't touch to its context. It's not a type-defined delegate. A delegate for a const member function today already does not touch its context. There is no need to expose the type. If I implement a delegate at the library level, when I call the function, I have to make a cast to access the context pointer. With cast removing implicitely the constness of the context pointer, I get the current D implementation of delegates. This is legal, but this is also undefined behavior. I believe that langage delegate work that way, that they are thus undefined behavior, and this should be corrected. As long as you assume the context pointer is part of the state of the aggregate, then yes. But I don't see it that way. The context pointer is part of the state of the delegate, and the delegate reference itself is the same as a function pointer -- it's not affected by immutable or const. Do you see the problem in the following code: class Foo { this(ref int _i) { i = () { return _i; } } ref int delegate() dg; ref int i() const { return dg(); } } int globalInt; immutable Foo globalFoo = Foo(globalInt); int bar(const Foo foo) { return foo.i++; } int globalBar() { return bar(globalFoo); } Answer: In multithreaded application, globalFoo, which is immutable, is automatically shared. However, globalInt is not. globalBar allows you to access to (and modify) globalInt. But globalInt is not protected for concurrent access. And globalInt from which thread is accessed ? Yes, I see the problem. It comes from immutable being shared implicitly. To reiterate, the fact that the data is not immutable or const is *not* an issue, globalInt is not immutable. The issue is purely the sharing aspect of immutable. Possible solutions: - do nothing, and let the programmer introducing multi-threading in the application deal with this, even if the programmers of Foo, bar and buggy did not cared to document the impact of their code for multithreaded applications. - forbid to put/call any delegate into an immutable object: that almost means forbiding to put/call a delegate into a const object. - what I propose: implement the separate kind of const delegates, that allows to protect their context pointers, and that you can safely call from const/immutable data. This also doesn't work: class Foo { this(ref int _i) { i = () { return _i; } } ref const(int) delegate() const dg; ref const(int) i() const { return dg(); } } int globalInt; immutable Foo globalFoo = Foo(globalInt); void thread1() { writeln(globalFoo.i); } int globalBar() { spawn(thread1); globalInt = 5; // does thread1 see this change or not? } It looks like any time you call a delegate member of an immutable object, it could access a context pointer that is not implicitly shared. Not to mention, what the hell does a const function mean for a delegate literal? The context pointer is the context of the function, not an object. How does that even work? What is probably the right solution is to disallow implicit immutable objects which have a delegate. This means: a) you cannot initialize a shared or immutable such object without a cast. This means the line immutable Foo globalFoo = new Foo(globalInt) is an error without a cast. b) Any time you have a const object that contains a delegate, it can be assumed that the object is not shared. And then we avoid dealing with the const delegate issue altogether. But in the case of the bug I filed, we are talking about a compiler-sanctioned implicit transformation to immutable. We *must* guarantee that when we allow an implicit transformation, that there are no existing mutable
Re: logical const without casts!
On Fri, 30 Sep 2011 13:38:31 -0400, Michel Fortin michel.for...@michelf.com wrote: On 2011-09-29 14:54:24 +, Steven Schveighoffer schvei...@yahoo.com said: I just thought of an interesting way to make a logical const object without casts. It requires a little extra storage, but works without changes to the current compiler (and requires no casts). Here's the code, then I'll talk about the implications: import std.stdio; class D { D getme() { return this;} void foo() {writeln(mutable!);} } class C { D delegate() d; this() { auto dinst = new D; this.d = dinst.getme; } void bar() const { d().foo();} } void main() { auto c = new C; c.bar(); } outputs: mutable! So how does it work? It works because delegates and especially the delegate data is *not* affected by const. So even when C is temporarily cast to const, the delegate is not affected (i.e. it's context pointer is not temporarily cast to const). Doesn't this poke holes in const? Of course it does, but no more holes than are present via another logical const scheme (i.e. using a globally stored AA to retrieve the data). This is a hole in the transitive const, because the delegate contains a pointer to mutable data. It also is a potential source of of low level races since returning that type from a pure function could make it immutable, which can then make this mutable data accessible to multiple threads with no synchronization or atomics to protect the data's integrity. Simen Kjaeraas and Christophe brought up the same points. I filed a bug on it: http://d.puremagic.com/issues/show_bug.cgi?id=6741 -Steve
Re: logical const without casts!
On Fri, 30 Sep 2011 13:46:13 -0400, Steven Schveighoffer schvei...@yahoo.com wrote: Simen Kjaeraas and Christophe brought up the same points. I filed a bug on it: http://d.puremagic.com/issues/show_bug.cgi?id=6741 Also now: http://d.puremagic.com/issues/show_bug.cgi?id=6747 -Steve
Re: logical const without casts!
On 2011-09-30 17:46:13 +, Steven Schveighoffer schvei...@yahoo.com said: On Fri, 30 Sep 2011 13:38:31 -0400, Michel Fortin michel.for...@michelf.com wrote: On 2011-09-29 14:54:24 +, Steven Schveighoffer schvei...@yahoo.com said: I just thought of an interesting way to make a logical const object without casts. It requires a little extra storage, but works without changes to the current compiler (and requires no casts). Here's the code, then I'll talk about the implications: import std.stdio; class D { D getme() { return this;} void foo() {writeln(mutable!);} } class C { D delegate() d; this() { auto dinst = new D; this.d = dinst.getme; } void bar() const { d().foo();} } void main() { auto c = new C; c.bar(); } outputs: mutable! So how does it work? It works because delegates and especially the delegate data is *not* affected by const. So even when C is temporarily cast to const, the delegate is not affected (i.e. it's context pointer is not temporarily cast to const). Doesn't this poke holes in const? Of course it does, but no more holes than are present via another logical const scheme (i.e. using a globally stored AA to retrieve the data). This is a hole in the transitive const, because the delegate contains a pointer to mutable data. It also is a potential source of of low level races since returning that type from a pure function could make it immutable, which can then make this mutable data accessible to multiple threads with no synchronization or atomics to protect the data's integrity. Simen Kjaeraas and Christophe brought up the same points. I filed a bug on it: http://d.puremagic.com/issues/show_bug.cgi?id=6741 Interesting proposal. You realize if we had a true 'mutable' storage class for class members it could obey the same rule as you propose in that bug report: it should be illegal to cast a mutable-containing object or struct to immutable implicitly. An explicit cast should be required. Also, if we had shared delegates -- delegates which are guarantied to only manipulate shared data -- we could allow the class that contains a shared delegate to be implicitly converted to immutable. -- Michel Fortin michel.for...@michelf.com http://michelf.com/
Re: logical const without casts!
On Fri, 30 Sep 2011 14:00:36 -0400, Michel Fortin michel.for...@michelf.com wrote: On 2011-09-30 17:46:13 +, Steven Schveighoffer schvei...@yahoo.com said: On Fri, 30 Sep 2011 13:38:31 -0400, Michel Fortin michel.for...@michelf.com wrote: On 2011-09-29 14:54:24 +, Steven Schveighoffer schvei...@yahoo.com said: I just thought of an interesting way to make a logical const object without casts. It requires a little extra storage, but works without changes to the current compiler (and requires no casts). Here's the code, then I'll talk about the implications: import std.stdio; class D { D getme() { return this;} void foo() {writeln(mutable!);} } class C { D delegate() d; this() { auto dinst = new D; this.d = dinst.getme; } void bar() const { d().foo();} } void main() { auto c = new C; c.bar(); } outputs: mutable! So how does it work? It works because delegates and especially the delegate data is *not* affected by const. So even when C is temporarily cast to const, the delegate is not affected (i.e. it's context pointer is not temporarily cast to const). Doesn't this poke holes in const? Of course it does, but no more holes than are present via another logical const scheme (i.e. using a globally stored AA to retrieve the data). This is a hole in the transitive const, because the delegate contains a pointer to mutable data. It also is a potential source of of low level races since returning that type from a pure function could make it immutable, which can then make this mutable data accessible to multiple threads with no synchronization or atomics to protect the data's integrity. Simen Kjaeraas and Christophe brought up the same points. I filed a bug on it: http://d.puremagic.com/issues/show_bug.cgi?id=6741 Interesting proposal. You realize if we had a true 'mutable' storage class for class members it could obey the same rule as you propose in that bug report: it should be illegal to cast a mutable-containing object or struct to immutable implicitly. An explicit cast should be required. Yes, but the benefit of the proposal is -- it already works in the current compiler :) You have a monstrous hill to climb to convince Walter to *add* mutable storage class, but we don't even have to ask for this one, it works today. Also, if we had shared delegates -- delegates which are guarantied to only manipulate shared data -- we could allow the class that contains a shared delegate to be implicitly converted to immutable. I'd like to avoid the complication of having any attributes that apply to the context pointer. I like how delegates just fit in anywhere, no matter what the context pointer type is (or its constancy). -Steve
Re: logical const without casts!
On 2011-09-30 18:07:54 +, Steven Schveighoffer schvei...@yahoo.com said: On Fri, 30 Sep 2011 14:00:36 -0400, Michel Fortin michel.for...@michelf.com wrote: Interesting proposal. You realize if we had a true 'mutable' storage class for class members it could obey the same rule as you propose in that bug report: it should be illegal to cast a mutable-containing object or struct to immutable implicitly. An explicit cast should be required. Yes, but the benefit of the proposal is -- it already works in the current compiler :) You have a monstrous hill to climb to convince Walter to *add* mutable storage class, but we don't even have to ask for this one, it works today. It works today but is unsafe due to the implicit cast. But yeah, lets use delegates for now. After the implicit cast is fixed it might be the right time to point out to Walter that this exception for delegate is no worse than having a direct mutable keyword. Especially if everyone is using this delegate detour to achieve what would be simpler and more efficient with 'mutable'. Also, if we had shared delegates -- delegates which are guarantied to only manipulate shared data -- we could allow the class that contains a shared delegate to be implicitly converted to immutable. I'd like to avoid the complication of having any attributes that apply to the context pointer. I like how delegates just fit in anywhere, no matter what the context pointer type is (or its constancy). But that's no longer true. Delegates no longer fit anywhere with the fix you just proposed. Only 'shared' delegates would fit anywhere. I really means *anywhere* since implicitly casting a shared delegate to a non-shared delegates is a non-issue: it does not change the type of the variables the context is pointing to. So basically you'd need to require a shared delegate only where a normal delegate cannot do the job (like in a class you want to make immutable, or wanting to pass the delegate across threads). -- Michel Fortin michel.for...@michelf.com http://michelf.com/
Re: logical const without casts!
Steven Schveighoffer , dans le message (digitalmars.D:145850), a As long as you assume the context pointer is part of the state of the aggregate, then yes. But I don't see it that way. The context pointer is part of the state of the delegate, and the delegate reference itself is the same as a function pointer -- it's not affected by immutable or const. That is actually where our opinions differ. I wouldn't blame the langage for choosing you proposal rather than mine. I just personnaly think my proposal in more in the spirit of the langage, and offer more guarantees when dealing with immutables. This also doesn't work: Indeed. The compiler should not allow the implicit conversion of globalFoo to immutable. class Foo { this(ref int _i) { dg = () { return _i; } } ref const(int) delegate() const dg; ref const(int) i() const { return dg(); } } int globalInt; immutable Foo globalFoo = Foo(globalInt); ^ error: can't cast ref int _i to immutable ref int in immutable Foo.this()._delegate_1. void thread1() { writeln(globalFoo.i); } int globalBar() { spawn(thread1); globalInt = 5; // does thread1 see this change or not? } It looks like any time you call a delegate member of an immutable object, it could access a context pointer that is not implicitly shared. Not if the compiler cares to check the delegate context is indeed immutable when the immutable delegate is created. It is not much more complicated than if Foo contained an explicit pointer to i instead of a delegate Not to mention, what the hell does a const function mean for a delegate literal? The context pointer is the context of the function, not an object. How does that even work? Internally, it is a pointer to a structure containing the delegate context that is generated by the compiler. If the delegate is const, the pointer should be a const pointer. If the delegate is immutable, it should be an immutable pointer, and the compiler as to check there is no mutable version of that pointer before making an implicit cast. What is probably the right solution is to disallow implicit immutable objects which have a delegate. This means: a) you cannot initialize a shared or immutable such object without a cast. This means the line immutable Foo globalFoo = new Foo(globalInt) is an error without a cast. Agreed, but with my proposal, you can have an immutable Foo, if the delegate context pointer can be cast to immutable. b) Any time you have a const object that contains a delegate, it can be assumed that the object is not shared. If the object is const, you have no guarantee that the objet is not shared, making an exception for object containing a delegate is quite wierd. And then we avoid dealing with the const delegate issue altogether. Dealing with this issue could be very useful for multithreaded applications. With my proposal, you can very easily keep an immutable reference in a const delegate. The delegate will just not be callable if its function pointer is not const with regard to the context pointer. You can also very easily keep a mutable reference in a const delegate inside an immutable object. It's not mutable through the delegate, but it's also not immutable. No, the compiler should check the delegate context is immutable when making the cast to immutable. I admit you have found a very interesting corner-case for my proposal that I had not thought of that case. Thank you for that. I think the proposal passed the test (until now).
Re: logical const without casts!
On Fri, 30 Sep 2011 14:51:19 -0400, Christophe trav...@phare.normalesup.org wrote: Steven Schveighoffer , dans le message (digitalmars.D:145850), a This also doesn't work: Indeed. The compiler should not allow the implicit conversion of globalFoo to immutable. class Foo { this(ref int _i) { dg = () { return _i; } } ref const(int) delegate() const dg; ref const(int) i() const { return dg(); } } int globalInt; immutable Foo globalFoo = Foo(globalInt); ^ error: can't cast ref int _i to immutable ref int in immutable Foo.this()._delegate_1. But ref int _i is the parameter to the constructor. Here is where your proposal breaks down. It's entirely feasible for class Foo to be compiled *separately* from the module that contains globalFoo. And it's also possible that when compiling the above line, the language *does not have* access to the source code of the constructor. How does it know that _i gets put into a const delegate? Likewise, when compiling Foo, how does the compiler know that the constructor is being used to cast to immutable? I just don't think there is enough information for the compiler to make the correct decision. void thread1() { writeln(globalFoo.i); } int globalBar() { spawn(thread1); globalInt = 5; // does thread1 see this change or not? } It looks like any time you call a delegate member of an immutable object, it could access a context pointer that is not implicitly shared. Not if the compiler cares to check the delegate context is indeed immutable when the immutable delegate is created. It is not much more complicated than if Foo contained an explicit pointer to i instead of a delegate What if it has no access to the delegate creation code? How does it disallow it? Not to mention, what the hell does a const function mean for a delegate literal? The context pointer is the context of the function, not an object. How does that even work? Internally, it is a pointer to a structure containing the delegate context that is generated by the compiler. If the delegate is const, the pointer should be a const pointer. If the delegate is immutable, it should be an immutable pointer, and the compiler as to check there is no mutable version of that pointer before making an implicit cast. Hm.. I don't know if there's a way to create such a delegate. I'd have to check. What is probably the right solution is to disallow implicit immutable objects which have a delegate. This means: a) you cannot initialize a shared or immutable such object without a cast. This means the line immutable Foo globalFoo = new Foo(globalInt) is an error without a cast. Agreed, but with my proposal, you can have an immutable Foo, if the delegate context pointer can be cast to immutable. You can also have an immutable Foo if you cast it to immutable. For example, this line should compile: immutable foo = cast(immutable(Foo))new Foo(globalInt); But then the onus is on you to ensure you are doing the right thing. So in both solutions, it's possible. While I don't think your solution is feasible or solves the problem you set out to solve, if you did find a way to make the compiler make more guarantees, it would be better. I just don't know if it's worth the extra syntax and restrictions. b) Any time you have a const object that contains a delegate, it can be assumed that the object is not shared. If the object is const, you have no guarantee that the objet is not shared, making an exception for object containing a delegate is quite wierd. Well, since you cannot implicitly create such an immutable object, it could be an assumption you can make. However, I can see why it wouldn't be a good idea to make that assumption (especially if you created one via casting). We can probably drop this assumption, because to write code like the above, even if explicitly casting, you are asking for race conditions. Probably safe to assume that it works just like any other const object. And then we avoid dealing with the const delegate issue altogether. Dealing with this issue could be very useful for multithreaded applications. With my proposal, you can very easily keep an immutable reference in a const delegate. The delegate will just not be callable if its function pointer is not const with regard to the context pointer. You can also very easily keep a mutable reference in a const delegate inside an immutable object. It's not mutable through the delegate, but it's also not immutable. No, the compiler should check the delegate context is immutable when making the cast to immutable. As I said above, it's not always possible to check the context. -Steve
Re: logical const without casts!
Steven Schveighoffer , dans le message (digitalmars.D:145867), a écrit : But ref int _i is the parameter to the constructor. Here is where your proposal breaks down. It's entirely feasible for class Foo to be compiled *separately* from the module that contains globalFoo. And it's also possible that when compiling the above line, the language *does not have* access to the source code of the constructor. How does it know that _i gets put into a const delegate? Likewise, when compiling Foo, how does the compiler know that the constructor is being used to cast to immutable? I just don't think there is enough information for the compiler to make the correct decision. You say it is impossible for the compiler to check the constness of the arguments passed to the constructor ? Well, allow me to doubt your statement, because my compiler can (it is gdc 4.4.5 using dmd 2.052). Easy test, with a pointer instead of a delegate: struct Foo { int* _i; ref int i() { return *_i; } this(int* m_i) { _i = m_i; } this(const int* m_i) const { _i = m_i; } this(immutable(int)* m_i) immutable // change immutable attribute of // the constructor and main won't compile. { _i = m_i; } } void main() { int ma; Foo mfoo = Foo(ma); const int ca; const Foo cfoo = Foo(ca); immutable int ia; immutable Foo ifoo = Foo(ia); } Now try to remove the const or the immutable attributes in the different this(int*) overload. You can play and try many changes, if you find a way to put an immutable int inside a mutable Foo, you get a banana for having found a great hole in the langage (or at least in the compiler). Protect can (and has to!) be assured for a simple pointer. Why would it not be the case with a delegate context pointer ? Now the full test with delegate: module a; struct B { int* _b; this(int* m_b) { _b = m_b; } this(const(int)* m_b) const { _b = m_b; } this(immutable(int)* m_b) immutable { _b = m_b; } int* b() { return _b; } const(int)* b() const { return _b; } immutable(int)* b() immutable { return _b; } } struct Foo { int* delegate() _i; ref int i() { return *_i(); } this(int* m_i) { B b = B(m_i); _i = b.b; } this(const int* m_i) const { const B b = B(m_i); _i = b.b; } this(immutable int* m_i) immutable { immutable B b = B(m_i); _i = b.b; // try to uncomment the following line: //_i = () { return m_i; }; } } module test; import a; void main() { int ma; Foo mfoo = Foo(ma); const int ca; const Foo cfoo = Foo(ca); immutable int ia; immutable Foo ifoo = Foo(ia); } To know why I created the struct B, try to uncomment the line I indicated in a.d and compile. What do you discover ? a.d:34: Error: cannot implicitly convert expression (__dgliteral1) of type immutable(int*) delegate() to immutable(int* delegate()) So the type I thought I was proposing, the const or immutable delegate that protects its context pointer already exists in my compiler. The only problem is that at the moment, you have to use an helper structure to create this type of delegate... What if it has no access to the delegate creation code? How does it disallow it? The compiler cannot cast a newly constructed structure to immutable if it cannot access the creation code, unless the creation code is marked immutable (and then there is actually no cast to do). Me thinking I could improve the langage... Well, the conversation was interesting and I learned a lot about d today. -- Christophe
Re: logical const without casts!
On Thu, 29 Sep 2011 16:54:24 +0200, Steven Schveighoffer schvei...@yahoo.com wrote: I just thought of an interesting way to make a logical const object without casts. It requires a little extra storage, but works without changes to the current compiler (and requires no casts). [snip] What do people think about this? This is what I think about it: class A { int n; void delegate( ) dg; } pure A createAnA( int n ) { A result = new A; result.n = n; result.dg = (){ result.n++; }; return result; } void main( ) { immutable A tmp = createAnA( 3 ); assert( tmp.n == 3 ); tmp.dg(); assert( tmp.n == 3 ); } -- Simen
Re: Logical const
On 03/12/2010 18:23, Steven Schveighoffer wrote: On Fri, 03 Dec 2010 11:23:48 -0500, Bruno Medeiros brunodomedeiros+s...@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 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
Re: Logical const
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+s...@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
Re: Logical const
On Thu, 02 Dec 2010 20:38:01 -0500, Walter Bright newshou...@digitalmars.com wrote: Steven Schveighoffer wrote: On Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros brunodomedeiros+s...@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.Darticle_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
Re: Logical const
On 02/12/2010 21:04, Steven Schveighoffer wrote: On Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros brunodomedeiros+s...@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.Darticle_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
Re: Logical const
On 03/12/2010 01:38, Walter Bright wrote: Steven Schveighoffer wrote: On Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros brunodomedeiros+s...@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.Darticle_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
Re: Logical const
On Fri, 03 Dec 2010 08:00:43 -0500, Bruno Medeiros brunodomedeiros+s...@com.gmail wrote: On 02/12/2010 21:04, Steven Schveighoffer wrote: On Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros brunodomedeiros+s...@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.Darticle_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
Re: Logical const
On Fri, 03 Dec 2010 08:22:01 -0500, Steven Schveighoffer schvei...@yahoo.com wrote: On Fri, 03 Dec 2010 08:00:43 -0500, Bruno Medeiros brunodomedeiros+s...@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
Re: Logical const
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
Re: Logical const
On 03/12/2010 13:22, Steven Schveighoffer wrote: On Fri, 03 Dec 2010 08:00:43 -0500, Bruno Medeiros brunodomedeiros+s...@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
Re: Logical const
On 03/12/2010 14:03, Steven Schveighoffer wrote: On Fri, 03 Dec 2010 08:22:01 -0500, Steven Schveighoffer schvei...@yahoo.com wrote: On Fri, 03 Dec 2010 08:00:43 -0500, Bruno Medeiros brunodomedeiros+s...@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
On const and inout (was Re: Logical const)
On 02/12/2010 09:18, 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. Yes, that is more or less it, although it may not the best way to think about it. I've come to realize an interesting parallel between inout and Java wildcards. In fact, that might give inspiration for another way to explain inout and const. Let's go back a little, to think about const itself. Ask yourselves this, does it makes sense to *instantiate* data typed as const, such as this: auto blah = new const(int[4]); ? The answer is no (even though the compiler allows it). The resulting data would effectively be the same as immutable, so you might as well instantiate it as immutable. The key thing here is that const(int), and any other const(T), is actually kinda like an abstract type. It tells you some of the things you can do with the data, but the data itself must be of a concrete type, which is mutable or immutable, but not const. It is entirely accurate to think of const(T) as some T that can be mutable or immutable, but one don't know which, so one can only work on the lowest common assumptions. So const is like a wildcard. Why is this interesting? Because it helps us understand what inout does. (if some people have trouble understanding and/or explaining const, then inout will be much worse). inout is based on const, it also says I don't know if this data is mutable or not. However, it adds the guarantee/restriction that all data typed as inout in the function parameters and return type actually has the same *concrete* type, with regards to immutability. So if you have this function: inout(int)[] func(inout(int)[] arr, int x, int y) { return arr[x .. y]; } You are specifying: I don't know what arr's actual/real/runtime immutability is (so I'll treat is an unknown), but I guarantee that the actual/real/runtime immutability of the return type will be that same as that of arr's. This pretty much the same conceptually as creating a binding of a wildcard type in Java (I'm not sure these are the correct terms). Java's wildcard support is much more... err.. of a generic solution (I mean generic as in http://www.answers.com/generic, not as in related to generics). For example, in Java you can specify a function signature such as this: public static T T get(T obj) { //... Which guarantees that the static return type is the same as the static type of the argument. So for example this will compile: String foo = get(new String()); Number foo = get(new Integer()); but this will not: Number foo = get(new String()); Conceptually this is pretty much the same as with inout in D, where inout is only able to bind to one type modifier, and it does so anonymously. Also D's support for this is only with regards to immutability, otherwise D is not able to define _one_ function with a signature/contract like the above. -- Bruno Medeiros - Software Engineer
Re: Logical const
On Fri, 03 Dec 2010 11:23:48 -0500, Bruno Medeiros brunodomedeiros+s...@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
Re: Logical const
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+s...@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
Re: Logical const
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.
Re: Logical const
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.
Re: Logical const
On 1-dic-10, at 22:18, Steven Schveighoffer wrote: On Wed, 01 Dec 2010 11:49:36 -0500, so s...@so.do wrote: On Wed, 01 Dec 2010 18:38:23 +0200, so s...@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
Re: Logical const
On 01/12/2010 21:09, Steven Schveighoffer wrote: On Tue, 30 Nov 2010 16:53:14 -0500, Walter Bright newshou...@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
Re: Logical const
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
Re: Logical const
On 29/11/2010 23:04, Walter Bright wrote: Steven Schveighoffer wrote: On Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright newshou...@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
Re: Logical const
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
Re: Logical const
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
Re: Logical const
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.
Re: Logical const
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)? 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, 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
Re: Logical const
On Thu, 02 Dec 2010 15:25:49 -0500, Don nos...@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)? 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 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
Re: Logical const
On Wed, 01 Dec 2010 18:34:34 -0500, Walter Bright newshou...@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
Re: Logical const
On Thu, 02 Dec 2010 12:59:22 -0500, Bruno Medeiros brunodomedeiros+s...@com.gmail wrote: On 01/12/2010 21:09, Steven Schveighoffer wrote: On Tue, 30 Nov 2010 16:53:14 -0500, Walter Bright newshou...@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
Re: Logical const
On Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros brunodomedeiros+s...@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.Darticle_id=58927 -Steve
Re: Logical const
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)? 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, 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.
Re: Logical const
Steven Schveighoffer wrote: On Thu, 02 Dec 2010 13:57:04 -0500, Bruno Medeiros brunodomedeiros+s...@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.Darticle_id=58927 What you're doing is keeping an alternate, mutable reference to each object. This does not mean that logical const == const.
Re: Logical const
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.
Re: Logical const
On Tue, 30 Nov 2010 15:03:43 -0800 Walter Bright newshou...@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)? Denis -- -- -- -- -- -- -- vit esse estrany ☣ spir.wikidot.com
Re: Logical const
On Wednesday 01 December 2010 03:13:08 spir wrote: On Tue, 30 Nov 2010 15:03:43 -0800 Walter Bright newshou...@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
Re: Logical const
On Wed, 1 Dec 2010 03:22:39 -0800 Jonathan M Davis jmdavisp...@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?). 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. denis -- -- -- -- -- -- -- vit esse estrany ☣ spir.wikidot.com
Re: Logical const
On 11/29/2010 11:56 PM, Steven Schveighoffer wrote: On Sat, 20 Nov 2010 09:21:04 -0500, Peter Alexander peter.alexander...@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
Re: Logical const
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/
Re: Logical const
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/
Re: Logical const
On Wed, 01 Dec 2010 18:38:23 +0200, so s...@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/
Re: Logical const
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?
Re: Logical const
On Wednesday, December 01, 2010 06:17:56 spir wrote: On Wed, 1 Dec 2010 03:22:39 -0800 Jonathan M Davis jmdavisp...@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
Re: Logical const
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.
Re: Logical const
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.
Re: Logical const
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.
Re: Logical const
On Tue, 30 Nov 2010 16:53:14 -0500, Walter Bright newshou...@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
Re: Logical const
On Wed, 01 Dec 2010 11:09:08 -0500, so s...@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
Re: Logical const
On Wed, 01 Dec 2010 11:49:36 -0500, so s...@so.do wrote: On Wed, 01 Dec 2010 18:38:23 +0200, so s...@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
Re: Logical const
On Wed, 01 Dec 2010 12:43:38 -0500, Jesse Phillips jessekphillip...@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
Re: Logical const
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!
Re: Logical const
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.
Re: Logical const
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.
Re: Logical const
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.
Re: Logical const
Walter Bright newshou...@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
Re: Logical const
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.
Re: Logical const
On Mon, 29 Nov 2010 17:21:27 -0500, Simen kjaeraas simen.kja...@gmail.com wrote: Steven Schveighoffer schvei...@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
Re: Logical const
On Mon, 29 Nov 2010 18:04:31 -0500, Walter Bright newshou...@digitalmars.com wrote: Steven Schveighoffer wrote: On Mon, 29 Nov 2010 15:58:10 -0500, Walter Bright newshou...@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. 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
Re: Logical const
On Tue, 30 Nov 2010 08:04:57 -0500, Steven Schveighoffer schvei...@yahoo.com wrote: On Mon, 29 Nov 2010 17:21:27 -0500, Simen kjaeraas simen.kja...@gmail.com wrote: Steven Schveighoffer schvei...@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
Re: Logical Const using a Mutable template
On 11/30/2010 09:30 AM, Jesse Phillips wrote: This came up in discussion and I think the behavior is safe and usable when wanting to change class data in a const function. http://article.gmane.org/gmane.comp.lang.d.general/43476 One limitation is that value types declared as Mutable (Mutable!(int)) can not be changed if the encapsulating class is declared immutable. For this reason a Mutable value can not be changed inside a const function. I think this is acceptable. Another limitation appears to be an issue with alias this. It is commented as a //FIXME in the code. https://gist.github.com/721066 An example usage looks like: class Inner { int n; } class A { Mutable!(int*) n; Mutable!(int) n2; Mutable!(Inner) innerClass; this() { n = new int; innerClass = new Inner; } void foo( int num ) { n2 = num; } void bar( int num ) const { innerClass.n = num; } } auto aImmu = new immutable(A); auto aMu = new A; int i = 8; *aImmu.n = i, aImmu.bar(6), *aMu.n = i, aMu.n =i, aMu.n2 = i, aMu.bar(6), aMu.foo(6), It is not enough to forward opAssign and opEquals. Any operator should be handled. For example, change the implementation of A.foo to n2 += 1; and you will get a segfault during compilation. You could add more kludge with operator overloads but I don't think it is worth the effort. It makes more sense to wait until alias this bugs are fixed. On the other hand, we may wait forever since the way alias this should handle operators has never been specified.
Re: Logical const
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?
Re: Logical const
On Tue, 30 Nov 2010 10:06:40 -0500, Don nos...@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
Re: Logical const
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.
Re: Logical const
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
Re: Logical Const using a Mutable template
Max Samukha Wrote: On 11/30/2010 09:30 AM, Jesse Phillips wrote: This came up in discussion and I think the behavior is safe and usable when wanting to change class data in a const function. http://article.gmane.org/gmane.comp.lang.d.general/43476 One limitation is that value types declared as Mutable (Mutable!(int)) can not be changed if the encapsulating class is declared immutable. For this reason a Mutable value can not be changed inside a const function. I think this is acceptable. Another limitation appears to be an issue with alias this. It is commented as a //FIXME in the code. https://gist.github.com/721066 It is not enough to forward opAssign and opEquals. Any operator should be handled. For example, change the implementation of A.foo to n2 += 1; and you will get a segfault during compilation. You could add more kludge with operator overloads but I don't think it is worth the effort. It makes more sense to wait until alias this bugs are fixed. On the other hand, we may wait forever since the way alias this should handle operators has never been specified. The intent isn't to work around alias this bugs. It is to provide mutation in a const function, but with the guarantee that it will not result in undefined behavior. (I should have stated that first, but it was getting late). Concurrency shouldn't be any different from having a reference in a class you are sharing. Sharing immutable objects should be fine as they still can not have their state changed. The rules for this are: * Only mutable data can be assigned to a Mutable * Modification of referenced fields can be modified (inner class fields, pointer targets) I'll be back to finish this in a bit.
Re: Logical const
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.
Re: Logical const
Steven Schveighoffer Wrote: On Tue, 30 Nov 2010 10:06:40 -0500, Don nos...@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?
Re: Logical const
On Tuesday, November 30, 2010 09:10:03 Sean Kelly wrote: Steven Schveighoffer Wrote: On Tue, 30 Nov 2010 10:06:40 -0500, Don nos...@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
Re: Logical Const using a Mutable template
Jesse Phillips Wrote: The rules for this are: * Only mutable data can be assigned to a Mutable * Modification of referenced fields can be modified (inner class fields, pointer targets) I'll be back to finish this in a bit. * Value types can be modified if the encapsulating class is not declared const/immutable And I have come up with 2 major concerns related to immutable classes. Would an inner class be placed in read-only memory in instantiating an immutable class: class A { Mutable!(Inner) innerclass; this() { innerclass = new Inner; } // Is innerclass placed in read-only memory } new immutable(A); And what about when an immutable instance is passed to a thread or network. Would the innerclass be placed in read-only memory for the new thread/machine. And looking over it again, I found that I wasn't using the opAssign const functions, they didn't meet the requirements. So I think a working alias this would allow the template to be: (The important check being that Mutables are unqualified) struct Mutable( T ) if ( is( T : Unqual!T ) ) { private T _payload; this( T t ) { _payload = t; } @trusted @property ref T get( )( ) const { T* p = cast( T* )_payload; return *p; } alias get this; } https://gist.github.com/721066
Re: Logical const
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.)
Re: Logical const
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.
Re: Logical const
Simen kjaeraas wrote: Walter Bright newshou...@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.
Re: Logical const
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.
Re: Logical const
Sean Kelly wrote: Hm... can a const object mutate globals? Yes.
Re: Logical const
On Tue, 30 Nov 2010 14:07:51 -0500, Walter Bright newshou...@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
Re: Logical const
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.
Re: Logical const
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.
Re: Logical const
On Tue, 30 Nov 2010 15:16:04 -0500, Walter Bright newshou...@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
Re: Logical const
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 = C.theCommonOne; auto old = c.x; c.foo(); assert (old == c.x); // Fails and this does not surprise you? Jerome -- mailto:jeber...@free.fr http://jeberger.free.fr Jabber: jeber...@jabber.fr signature.asc Description: OpenPGP digital signature
Re: Logical const
On 30/11/10 6:21 AM, so wrote: I can understand Peter using Matrix just for example but still i have to say. A small vector/matrix with an extra field to memoize things perform likely worse than their usual implementations. That small extra field might just destroy any alignments, optimizations, which are scarier than the computation itself. This may be true for this particular case, but has no bearing on the discussion in general.
Re: Logical const
Steven Schveighoffer wrote: On Tue, 30 Nov 2010 15:16:04 -0500, Walter Bright newshou...@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.