www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - D safety! New Feature?

reply Mark Twain <Juckleberry twain.com> writes:
One of the biggest problems with memory is that it's inherently 
unsafe. We know the solution to that is immutability. Immutable 
memory is inherently safe. The problem is that Immutable memory 
is useless because it cannot be changed. To make it useful and 
safe we have to cheat by creating mutable things then turning 
them in to immutable things, which is safe, but generally not the 
other way around, which is unsafe.

So, Immutable memory sort of as a direction, that if we follow 
it, we are safe, and when we go in the opposite direction, we are 
unsafe.

Now, to real world stuff. I was building some container classes 
using mixin templates like:

// An Immutable Array Template
template _ImmutableArray(T, A)
{
     // Has functionality to create itself from a indexable object 
and supply accessor methods for indexing, etc. The typical array 
stuff.

}

template _MutableArray(T,A)
{
     mixin _ImmutableArray!(T,A);

     // Adds stuff so we can mutate the array(change it's length, 
remove elements, etc...
}

struct ImmutableArray(T)
{
     mixin _ImmutableArray!(T,A);
}


struct MutableArray(T)
{
     mixin _MutableArray!(T,A)
}


With such a pattern we can build up a hierarchy of templates that 
have corresponding run-time types. We can use introspection by 
having things like IsImmutable, etc. Very oop like, but with 
static constructs. On top of these we can build stacks(just a 
MutableArray), a queue(a MutableArray with Enqueue and Dequeue), 
a circular queue(a fixed array(length can't change)), and their 
immutable variants.

Now the cool thing about this, I think, is that we can do stuff 
like:

global ImmutableArray!int Data;


MutableArray!int DataCopy = Data.Copy; // Creates a mutable copy 
of Data.
... Do work with DataCopy ...
Data.Replace(DataCopy); // Makes a copy of DataCopy.


Note that Data is immutable and doesn't even have functionality 
that mutate the Data. So we know that we never mutate Data(this 
is checked at compile time). Immutable data always has it's own 
copy. The difference is that this is a weaker form of standard 
immutability. It allows us to logically separate code that 
mutates something with the code that doesn't. We can reduce 
overhead of allocating relatively simply:

void foo()
{
     MutableArray!int Data;
     scope(Exit) Data.Reuse();
}

Reuse can simply mark the memory reusable rather then freeing it. 
This memory can then be reused the next time foo is called(or 
possibly use the stack for memory). Utilities could find optimal 
uses for typical program behavior. e.g., foo uses on average 500 
bytes for Data. Hence allocate the capacity for Data to 500 bytes.

While compiler support would make this shine, I think the main 
benefit is that one doesn't keeps mutable functionality with 
mutable data and immutable functionality with immutable data. The 
more a program works with immutable the safer it gets. It's 
leaving the little dropping of mutable data around that create 
the hard to find bugs. It makes it easier to reason about 
code(compiler or tool support can make it even better).  
Basically all the benefits of immutability as that is all it is 
but with a bit more type logic than the keyword.

e.g., If you use intelligent and mark something immutable, You'll 
still have "access" visually to all the mutable functionality. If 
you don't use intelligent, you'll get a compile time error higher 
up the stream that is more meaningful(basically trying to access 
a member that doesn't exist in the type).
Aug 02 2016
next sibling parent Mark "J" Twain <Juckleberry Twain.com> writes:
On Tuesday, 2 August 2016 at 21:48:58 UTC, Mark Twain wrote:
 [...]
Another useful feature I forgot to mention is that we can cast between Immutable and Muttable types. class ImmutableTest { private MutableArray!int data; public ImmutableArray!int Data; this() { // Creates a view to data, but Data cannot modify data. Data.View(data); } public Add(int x) { data.Add(x); } } Data can then be exposed without worrying about it getting trampled on. Sense a MutableArray contains all the functionality of an ImmutableArray, there should be no problems with the interpretation.
Aug 02 2016
prev sibling next sibling parent reply ag0aep6g <anonymous example.com> writes:
On 08/02/2016 11:48 PM, Mark Twain wrote:
 global ImmutableArray!int Data;


 MutableArray!int DataCopy = Data.Copy; // Creates a mutable copy of Data.
 ... Do work with DataCopy ...
 Data.Replace(DataCopy); // Makes a copy of DataCopy.
What benefit do ImmutableArray and MutableArray have over built-in arrays? The thing above can be done with built-in arrays: ---- immutable(int)[] Data; int[] DataCopy = Data.dup; // Creates a mutable copy of Data. ... Do work with DataCopy ... Data = DataCopy.idup; // Makes a copy of DataCopy. ---- [...]
 void foo()
 {
     MutableArray!int Data;
     scope(Exit) Data.Reuse();
 }

 Reuse can simply mark the memory reusable rather then freeing it. This
 memory can then be reused the next time foo is called(or possibly use
 the stack for memory).
As far as I see, "marking for reuse" is practically the same as freeing here. If Data were static, there could be a difference. For built-in arrays, you can mark an array for reuse by setting the length to 0 and calling assumeSafeAppend, like so: ---- void foo(int x) { static int[] Data; scope(exit) { Data.length = 0; Data.assumeSafeAppend(); } Data ~= x; import std.stdio: writeln; writeln(Data, " ", Data.ptr); } void main() { foo(1); foo(2); } ----
Aug 02 2016
parent reply Mark "J" Twain <Juckleberry Twain.com> writes:
On Wednesday, 3 August 2016 at 05:44:42 UTC, ag0aep6g wrote:
 On 08/02/2016 11:48 PM, Mark Twain wrote:
 global ImmutableArray!int Data;


 MutableArray!int DataCopy = Data.Copy; // Creates a mutable 
 copy of Data.
 ... Do work with DataCopy ...
 Data.Replace(DataCopy); // Makes a copy of DataCopy.
What benefit do ImmutableArray and MutableArray have over built-in arrays? The thing above can be done with built-in arrays: ---- immutable(int)[] Data; int[] DataCopy = Data.dup; // Creates a mutable copy of Data. ... Do work with DataCopy ... Data = DataCopy.idup; // Makes a copy of DataCopy. ----
The built in array is mutable and exposes the same interface for the immutable copy. The only difference is that the immutable copy is marked immutable. My method changes the interface. An "immutable" type has an immutable interface while a mutable type has a mutable interface. For simple types, and value types, there is obviously no advantage... their interfaces are essentially empty/DNE.
 [...]
 void foo()
 {
     MutableArray!int Data;
     scope(Exit) Data.Reuse();
 }

 Reuse can simply mark the memory reusable rather then freeing 
 it. This
 memory can then be reused the next time foo is called(or 
 possibly use
 the stack for memory).
As far as I see, "marking for reuse" is practically the same as freeing here. If Data were static, there could be a difference. For built-in arrays, you can mark an array for reuse by setting the length to 0 and calling assumeSafeAppend, like so:
No again. Data in the example above is a local variable that allocates memory that would generally be free'ed at the end of the function call. Hence every call to foo results in a malloc/free pair for Data. By reusing the memory, if possible, one can potentially skip the malloc/free. Therefore instead of potentially thrashing the memory pool, after a few runs of foo, Data would generally not allocate. When one has hundreds of functions allocating and deallocating memory, this is a big deal. This is effectively caching or similar memory pooling. Your assumeSafeAppend works while the object is in existence. This thing works between the existence of the object.
Aug 03 2016
parent reply ag0aep6g <anonymous example.com> writes:
On 08/03/2016 09:33 PM, Mark J Twain wrote:
 The built in array is mutable and exposes the same interface for the
 immutable copy. The only difference is that the immutable copy is marked
 immutable.
I don't understand. An immutable array does not let you overwrite elements. A mutable array does. So they have different interfaces, don't they?
 My method changes the interface. An "immutable" type has an immutable
 interface while a mutable type has a mutable interface.

 For simple types, and value types, there is obviously no advantage...
 their interfaces are essentially empty/DNE.
Can you show some example code where there is an advantage over built-in arrays? [...]
 As far as I see, "marking for reuse" is practically the same as
 freeing here. If Data were static, there could be a difference.

 For built-in arrays, you can mark an array for reuse by setting the
 length to 0 and calling assumeSafeAppend, like so:
No again. Data in the example above is a local variable that allocates memory that would generally be free'ed at the end of the function call. Hence every call to foo results in a malloc/free pair for Data. By reusing the memory, if possible, one can potentially skip the malloc/free. Therefore instead of potentially thrashing the memory pool, after a few runs of foo, Data would generally not allocate.
[...]
 Your assumeSafeAppend works while the object is in existence. This thing
 works between the existence of the object.
The allocator (e.g. the GC) is free to reuse memory it obtained from the operating system. So repeatedly allocating and freeing the same amount of memory can already be faster than one might think. Of course, the compiler is also free to reuse stuff, if it's guaranteed that no reference escapes the function. I don't expect dmd to do stuff like this. ldc or gdc might. What do Mutable/ImmutableArray enable beyond this? What do they do that built-in arrays don't? Again, example code would help doofuses like me make sense of this.
Aug 04 2016
parent reply Mark "J" Twain <Juckleberry Twain.com> writes:
On Thursday, 4 August 2016 at 13:08:11 UTC, ag0aep6g wrote:
 On 08/03/2016 09:33 PM, Mark J Twain wrote:
 The built in array is mutable and exposes the same interface 
 for the
 immutable copy. The only difference is that the immutable copy 
 is marked
 immutable.
I don't understand. An immutable array does not let you overwrite elements. A mutable array does. So they have different interfaces, don't they?
 My method changes the interface. An "immutable" type has an 
 immutable
 interface while a mutable type has a mutable interface.

 For simple types, and value types, there is obviously no 
 advantage...
 their interfaces are essentially empty/DNE.
Can you show some example code where there is an advantage over built-in arrays? [...]
 As far as I see, "marking for reuse" is practically the same 
 as
 freeing here. If Data were static, there could be a 
 difference.

 For built-in arrays, you can mark an array for reuse by 
 setting the
 length to 0 and calling assumeSafeAppend, like so:
No again. Data in the example above is a local variable that allocates memory that would generally be free'ed at the end of the function call. Hence every call to foo results in a malloc/free pair for Data. By reusing the memory, if possible, one can potentially skip the malloc/free. Therefore instead of potentially thrashing the memory pool, after a few runs of foo, Data would generally not allocate.
[...]
 Your assumeSafeAppend works while the object is in existence. 
 This thing
 works between the existence of the object.
The allocator (e.g. the GC) is free to reuse memory it obtained from the operating system. So repeatedly allocating and freeing the same amount of memory can already be faster than one might think. Of course, the compiler is also free to reuse stuff, if it's guaranteed that no reference escapes the function. I don't expect dmd to do stuff like this. ldc or gdc might. What do Mutable/ImmutableArray enable beyond this? What do they do that built-in arrays don't? Again, example code would help doofuses like me make sense of this.
The problem is that you have fixated on the *array* and not the general principle. The Array was an example. Get Array out of your mind and think of them as general structures. It could be a queue. D has no built in queue, then what? What if it is a widget, then what? Immutable Widget vs Mutable Widget. Marking a widget immutable is not the same as having an ImmutableWidget. Can you see the difference? I assure you there is. The immutable keyword only prevents data manipulation, it does not change the interface. For simple primitives, there is not much difference, but for larger complex types, the immutable keyword doesn't cut it. immutable Queue!int q1; ImmutableQueue!int q2; q1.Enqueue(x); // Compile time error if no tricks, but the error is further up the line inside Enqueue, when it actually modifies the data. We can cast away immutability and end up defeating the purpose and end up with run-time problems. q2.Enqueue(x); // Compile time error, Enqueue doesn't exist in ImmutableQueue. cannot cast away immutable. At most we can convert q2 to a mutable class, which is a copy, then replace q2 with the copy. There are difference and the second case is better. The error reporting is more accurate and no casting can be done to bypass immutability. We essentially get all this stuff for free if we simply use templates to build the hierarchy and separate the template in to different parts(immutable, mutable, etc). Now, an ImmutableQueue might not be hugely useful if we have no way to access the data, but it could provide [] access. Again, don't get bogged down in the specifics, I'm talking about general application here. The more complex the type and hierarchy the more useful such a method is and the less useful immutable keyword is. The immutable keyword is a blind, it only does one thing. Building immutability in to the type system itself allows the programmer to make immutable smarter and control exactly what it does.
Aug 04 2016
next sibling parent reply ag0aep6g <anonymous example.com> writes:
On 08/04/2016 08:22 PM, Mark J Twain wrote:
 The problem is that you have fixated on the *array* and not the general
 principle. The Array was an example.
I'm having trouble understanding what you're getting at, so I'm trying to get it from the example you gave. If there's merit in your idea, then surely you can give a example where it provides benefit over the immutable keyword.
 Get Array out of your mind and
 think of them as general structures. It could be a queue.
Ok.
 D has no built
 in queue, then what?
You can still have an immutable queue, or a queue of immutable elements. Just like with arrays.
 What if it is a widget, then what? Immutable Widget
 vs Mutable Widget.

 Marking a widget immutable is not the same as having an ImmutableWidget.
 Can you see the difference?
No.
 I assure you there is.
Please show.
 The immutable keyword
 only prevents data manipulation, it does not change the interface.
I'm still not sure what that means. An immutable object does not have mutating operations in its interface. A mutable object does. So the interfaces are different.
 For
 simple primitives, there is not much difference, but for larger complex
 types, the immutable keyword doesn't cut it.

 immutable Queue!int q1;
 ImmutableQueue!int q2;

 q1.Enqueue(x);  // Compile time error if no tricks, but the error is
 further up the line inside Enqueue, when it actually modifies the data.
Not true. Since Enqueue isn't marked const or immutable, it can't be called on an immutable object. The compiler rejects the call itself. It doesn't reject the mutation that happens inside Enqueue, because that's perfectly fine in a non-const, non-immutable method. In code: ---- struct Queue { void Enqeue(int dummy) {} } void main() { Queue m; m.Enqeue(1); immutable Queue i; i.Enqeue(2); /* Error: mutable method test.Queue.Enqeue is not callable using a immutable object */ } ----
 We can cast away immutability and end up defeating the purpose and end
 up with run-time problems.
You can break everything with casts, yes.
 q2.Enqueue(x);  // Compile time error, Enqueue doesn't exist in
 ImmutableQueue.
It doesn't exist for an immutable Queue, either.
 cannot cast away immutable.
You can still cast from ImmutableQueue to MutableQueue.
 At most we can convert q2 to
 a mutable class, which is a copy, then replace q2 with the copy.

 There are difference and the second case is better. The error reporting
 is more accurate and no casting can be done to bypass immutability.

 We
 essentially get all this stuff for free if we simply use templates to
 build the hierarchy and separate the template in to different
 parts(immutable, mutable, etc).

 Now, an ImmutableQueue might not be hugely useful if we have no way to
 access the data, but it could provide [] access. Again, don't get bogged
 down in the specifics, I'm talking about general application here. The
 more complex the type and hierarchy the more useful such a method is and
 the less useful immutable keyword is.

 The immutable keyword is a blind, it only does one thing. Building
 immutability in to the type system itself allows the programmer to make
 immutable smarter and control exactly what it does.
Sorry, but I still don't see what ImmutableWhatever does that `immutable Whatever` can't do. As far as I see, your example about having better error locations is wrong.
Aug 04 2016
parent reply Mark "J" Twain <Juckleberry Twain.com> writes:
On Thursday, 4 August 2016 at 18:58:18 UTC, ag0aep6g wrote:
 On 08/04/2016 08:22 PM, Mark J Twain wrote:
 The problem is that you have fixated on the *array* and not 
 the general
 principle. The Array was an example.
I'm having trouble understanding what you're getting at, so I'm trying to get it from the example you gave. If there's merit in your idea, then surely you can give a example where it provides benefit over the immutable keyword.
 Get Array out of your mind and
 think of them as general structures. It could be a queue.
Ok.
 D has no built
 in queue, then what?
You can still have an immutable queue, or a queue of immutable elements. Just like with arrays.
 What if it is a widget, then what? Immutable Widget
 vs Mutable Widget.

 Marking a widget immutable is not the same as having an 
 ImmutableWidget.
 Can you see the difference?
No.
 I assure you there is.
Please show.
 The immutable keyword
 only prevents data manipulation, it does not change the 
 interface.
I'm still not sure what that means. An immutable object does not have mutating operations in its interface. A mutable object does. So the interfaces are different.
 For
 simple primitives, there is not much difference, but for 
 larger complex
 types, the immutable keyword doesn't cut it.

 immutable Queue!int q1;
 ImmutableQueue!int q2;

 q1.Enqueue(x);  // Compile time error if no tricks, but the 
 error is
 further up the line inside Enqueue, when it actually modifies 
 the data.
Not true. Since Enqueue isn't marked const or immutable, it can't be called on an immutable object. The compiler rejects the call itself. It doesn't reject the mutation that happens inside Enqueue, because that's perfectly fine in a non-const, non-immutable method. In code: ---- struct Queue { void Enqeue(int dummy) {} } void main() { Queue m; m.Enqeue(1); immutable Queue i; i.Enqeue(2); /* Error: mutable method test.Queue.Enqeue is not callable using a immutable object */ } ----
 We can cast away immutability and end up defeating the purpose 
 and end
 up with run-time problems.
You can break everything with casts, yes.
 q2.Enqueue(x);  // Compile time error, Enqueue doesn't exist in
 ImmutableQueue.
It doesn't exist for an immutable Queue, either.
 cannot cast away immutable.
You can still cast from ImmutableQueue to MutableQueue.
 At most we can convert q2 to
 a mutable class, which is a copy, then replace q2 with the 
 copy.

 There are difference and the second case is better. The error 
 reporting
 is more accurate and no casting can be done to bypass 
 immutability.

 We
 essentially get all this stuff for free if we simply use 
 templates to
 build the hierarchy and separate the template in to different
 parts(immutable, mutable, etc).

 Now, an ImmutableQueue might not be hugely useful if we have 
 no way to
 access the data, but it could provide [] access. Again, don't 
 get bogged
 down in the specifics, I'm talking about general application 
 here. The
 more complex the type and hierarchy the more useful such a 
 method is and
 the less useful immutable keyword is.

 The immutable keyword is a blind, it only does one thing. 
 Building
 immutability in to the type system itself allows the 
 programmer to make
 immutable smarter and control exactly what it does.
Sorry, but I still don't see what ImmutableWhatever does that `immutable Whatever` can't do. As far as I see, your example about having better error locations is wrong.
Ok, Simple: immutable does not remove the interface! Regardless of how you are thinking about it, your exmaple, i still have Enqueue. Only the compiler has stopped compiling.
 struct Queue
 {
     void Enqeue(int dummy) {}
 }

 void main()
 {
     Queue m;
     m.Enqeue(1);
     immutable Queue i;
     i.Enqeue(2); /* Error: mutable method test.Queue.Enqeue is 
 not callable using a immutable object */
 }
In the case of ImmutableQueue, There is no Enqueue! See, there is a difference between "not callable" and "does not exists". It seems maybe minor, and maybe it is, but immutability and "Immutability" are not exactly the same. I actually think they would work well together, and of course, a lot of overlap exist. `immutability` only logically makes something immutable, as it can easily be proved. One can cast out immutable and mutate very easily(take address, change values). `Immutable` cannot be cast because there is no type relationship. Any time a change has to be made to an Immutable object, a copy is created. Of course, one could call this a "long winded cast", but it's more safe and requires more verbosity, hence less ambiguity and therefore less problems. Again, there is a lot of overlap, I'm not claiming this replaces `immutable`. But it does things that immutable doesn't. I'm not even claiming it is perfect in and of itself. After all, it is somewhat arbitrary. One has to design the templates to be immutable, and if they are not then it means nothing and just represents different types(e.g., ImmutableQueue vs Queue means nothing unless ImmutableQueue is truly an immutable type of Queue, which can only be designed properly designed with intent). What I would say to you is, either try it on some examples, or don't. I'm not trying to take away your favorite blanket... just present another tool for solving some problems. If you don't like it, that's fine, don't use it. Don't get upset, it won't affect your world if you don't want it to. For those that see some use out of it, use it, else don't. Simple as that.
Aug 05 2016
parent reply ag0aep6g <anonymous example.com> writes:
On 08/05/2016 09:39 PM, Mark J Twain wrote:
 In the case of ImmutableQueue, There is no Enqueue!

 See, there is a difference between "not callable" and "does not exists".
Ok, but what cool stuff is enabled by "does not exist" that doesn't work (as nicely) with "not callable"? As far as I can tell, there is no difference in practice. [...]
 `immutability` only logically makes something immutable, as it can
 easily be proved. One can cast out immutable and mutate very easily(take
 address, change values).
Casting immutable away and then mutating is not allowed by the language. It has undefined behavior. After doing it, the program may see the old value, it may see the new value, it may crash, or it may behave completely crazy. Some example code that shows different outcomes of mutating immutable data: ---- void main() { import std.stdio; immutable int i = 1; * cast(int*) &i = 2; writeln(i); /* prints "1" */ writeln(*&i); /* prints "2" */ immutable int* p = new int(3); *(cast(int*) p) = 4; writeln(*p); /* prints "4" */ immutable(char)[] s = "a"; (cast(char[]) s)[0] = 'b'; /* segfault on Linux */ writeln(s); /* prints "b" on Windows */ } ---- That's not a valid D program at all, of course. All of the mutations are invalid.
 `Immutable` cannot be cast because there is no
 type relationship.
You can cast between completely unrelated types no problem: ---- struct MutableSomething { int value; void mutate(int newValue) { value = newValue; } } struct ImmutableSomething { int value; /* no mutate method here */ } void main() { auto i = ImmutableSomething(1); (cast(MutableSomething) i).mutate(2); import std.stdio; writeln(i.value); /* prints "2" */ } ---- [...]
 What I would say to you is, either try it on some examples, or don't.
I'm asking you for an example, because I don't see the point of it.
 I'm not trying to take away your favorite blanket... just present
 another tool for solving some problems. If you don't like it, that's
 fine, don't use it. Don't get upset, it won't affect your world if you
 don't want it to. For those that see some use out of it, use it, else
 don't. Simple as that.
I cannot see any benefit in using your tool rather than plain immutable, which is why I'm asking for an example that shows the benefit. By now, I think there just isn't any. I think you have (or had) some misconceptions about immutable and casting in D, so you came up with something that's supposed to be stronger. But D's immutable already seems to be as strong as your construct. D already rejects calling a mutating method on an immutable object, and it also disallows casting immutable away and then mutating. Breaking things with a cast is always possible, even with your unrelated types. Please don't take this personally. I don't mean to put you down. I just think your ImmutableFoo thingy doesn't buy you anything over immutable Foo.
Aug 05 2016
next sibling parent reply Chris Wright <dhasenan gmail.com> writes:
On Fri, 05 Aug 2016 23:12:06 +0200, ag0aep6g wrote:

 On 08/05/2016 09:39 PM, Mark J Twain wrote:
 In the case of ImmutableQueue, There is no Enqueue!

 See, there is a difference between "not callable" and "does not
 exists".
Ok, but what cool stuff is enabled by "does not exist" that doesn't work (as nicely) with "not callable"? As far as I can tell, there is no difference in practice.
Some reflection stuff is a bit inconvenient: class A { int foo() { return 1; } } void main() { auto a = new immutable(A); // This passes: static assert(is(typeof(a.foo))); // This doesn't: static assert(__traits(compiles, () { a.foo; })); } __traits(compiles) is mostly an evil hack, but things like this require its use.
Aug 05 2016
parent reply ag0aep6g <anonymous example.com> writes:
On 08/06/2016 03:38 AM, Chris Wright wrote:
 Some reflection stuff is a bit inconvenient:

 class A {
   int foo() { return 1; }
 }

 void main() {
   auto a = new immutable(A);
   // This passes:
   static assert(is(typeof(a.foo)));
   // This doesn't:
   static assert(__traits(compiles, () { a.foo; }));
 }

 __traits(compiles) is mostly an evil hack, but things like this require
 its use.
The two are not equivalent, though. The first one checks the type of the method. __traits(compiles, ...) also passes when used that way: static assert(__traits(compiles, a.foo)); /* passes */ Add parentheses to the typeof one and it fails as expected: static assert(is(typeof(a.foo()))); /* fails */ Can also do the function literal thing you did in the __traits one: static assert(is(typeof(() { a.foo; }))); /* fails */ If the method wasn't there at all, they would all fail, of course. So the programmer couldn't mess this up as easily. I don't think this outweighs the inconvenience of bending over backwards to avoid immutable, though.
Aug 05 2016
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06.08.2016 07:56, ag0aep6g wrote:
 Add parentheses to the typeof one and it fails as expected:

     static assert(is(typeof(a.foo()))); /* fails */

 Can also do the function literal thing you did in the __traits one:

     static assert(is(typeof(() { a.foo; }))); /* fails */
You can, but don't. class A { int foo() { return 1; } } void main() { auto a = new A; static void gotcha(){ // This passes: static assert(is(typeof((){a.foo;}))); // This doesn't: static assert(__traits(compiles,(){a.foo;})); } }
Aug 06 2016
prev sibling parent Chris Wright <dhasenan gmail.com> writes:
On Sat, 06 Aug 2016 07:56:29 +0200, ag0aep6g wrote:

 On 08/06/2016 03:38 AM, Chris Wright wrote:
 Some reflection stuff is a bit inconvenient:

 class A {
   int foo() { return 1; }
 }

 void main() {
   auto a = new immutable(A);
   // This passes:
   static assert(is(typeof(a.foo)));
   // This doesn't:
   static assert(__traits(compiles, () { a.foo; }));
 }

 __traits(compiles) is mostly an evil hack, but things like this require
 its use.
The two are not equivalent, though. The first one checks the type of the method.
Which is a bit awkward because in no other context is it possible to mention a raw method. You invoke it or you get a delegate to it. And if you try to get a delegate explicitly to avoid this, you run into https://issues.dlang.org/show_bug.cgi?id=1983 . It's also a bit awkward for a method to be reported to exist with no way to call it. While you can do the same with private methods, you get a warning: a.d(7): Deprecation: b.B.priv is not visible from module a Which implies that this will become illegal at some point and fail to compile.
Aug 06 2016
prev sibling parent reply Mark "J" Twain <Juckleberry Twain.com> writes:
On Friday, 5 August 2016 at 21:12:06 UTC, ag0aep6g wrote:
 On 08/05/2016 09:39 PM, Mark J Twain wrote:
 In the case of ImmutableQueue, There is no Enqueue!

 See, there is a difference between "not callable" and "does 
 not exists".
Ok, but what cool stuff is enabled by "does not exist" that doesn't work (as nicely) with "not callable"? As far as I can tell, there is no difference in practice. [...]
 `immutability` only logically makes something immutable, as it 
 can
 easily be proved. One can cast out immutable and mutate very 
 easily(take
 address, change values).
Casting immutable away and then mutating is not allowed by the language. It has undefined behavior. After doing it, the program may see the old value, it may see the new value, it may crash, or it may behave completely crazy. Some example code that shows different outcomes of mutating immutable data: ---- void main() { import std.stdio; immutable int i = 1; * cast(int*) &i = 2; writeln(i); /* prints "1" */ writeln(*&i); /* prints "2" */ immutable int* p = new int(3); *(cast(int*) p) = 4; writeln(*p); /* prints "4" */ immutable(char)[] s = "a"; (cast(char[]) s)[0] = 'b'; /* segfault on Linux */ writeln(s); /* prints "b" on Windows */ } ---- That's not a valid D program at all, of course. All of the mutations are invalid.
 `Immutable` cannot be cast because there is no
 type relationship.
You can cast between completely unrelated types no problem: ---- struct MutableSomething { int value; void mutate(int newValue) { value = newValue; } } struct ImmutableSomething { int value; /* no mutate method here */ } void main() { auto i = ImmutableSomething(1); (cast(MutableSomething) i).mutate(2); import std.stdio; writeln(i.value); /* prints "2" */ } ----
wow, that seems like a huge design issues. Change one value to double though and it won't work. It's not general and seems to be a design flaw. It is not proof of anything in this case though. Why? Because an Queue might not have the same size as an ImmutableQueue and your "proof" only works when they are the same size.
 [...]
 What I would say to you is, either try it on some examples, or 
 don't.
I'm asking you for an example, because I don't see the point of it.
What kind of example? I have already given examples and proved that there is a functional difference. I will not continue this conversation because you refuse accept that. First you haven't given your criteria for me to prove anything to you to satisfy whatever it is you want. Second, I'm not here to waste my time trying to prove the merits of the tool. Again, either use it if you feel like it does something or don't. The method is not flawed in and of itself. It works(if it didn't, someone would have probably already shown it not to work). All you can do is assert a negative, which you can't prove yourself. So we end up chasing each other tails, which I refuse to do. If you want more proof then it is up to you, not me. I have my proof and it is good enough for me.
Aug 05 2016
next sibling parent Chris Wright <dhasenan gmail.com> writes:
On Sat, 06 Aug 2016 01:57:50 +0000, Mark "J" Twain wrote:
 wow, that seems like a huge design issues.
In practice, it's not a problem. In a language supporting low-level operations, you do sometimes need a reinterpret_cast. And since there's no other useful cast you can do with a D struct... The casting rules *are* moderately complex, though, in order to do the most useful thing at all times.
 Change one value to double though and it won't work.
With your own example of MutableArray and ImmutableArray, the two structs need the same fields. You even started out with a mixin to define common fields.
 [...]
 What I would say to you is, either try it on some examples, or don't.
I'm asking you for an example, because I don't see the point of it.
What kind of example?
An example demonstrating clearly the difference between this design pattern immutability and the already defined language construct. This example should demonstrate that you know how immutability works in D today. You haven't demonstrated much knowledge of D so far in this thread. Specifically: * You think a function can trivially reuse memory across invocations, but only if manually released, and releasing memory allows it to be stack- allocated. This ignores garbage collection, recursion, memory safety, how the stack works, and how to make this feature practical to implement. * You use a 'global' keyword. * You are using a nonstandard code style. * You misspelled `scope(exit)`. * You believed that you can call on an immutable object methods that are not marked immutable, and that the compiler would produce an error at the site of mutation, which would be quite impossible. Consider the following code: extern(D) void foo(MyObject o); void main() { foo(new immutable(MyObject)); } With your idea of how immutability is enforced, the compiler wouldn't know whether the code is valid or not.
 I have my proof and it is good enough for me.
But you posted here to suggest that people use this pattern. You went so far as to hint "compiler support would make this nice". And then when people pointed out that there's already compiler support for this and it's better than what you suggested, you argued with them! If you had said that you had looked at the builtin const/immutable and still preferred your model, well, it's your code. But instead you tried to sell us on your idea.
Aug 05 2016
prev sibling parent ag0aep6g <anonymous example.com> writes:
On 08/06/2016 03:57 AM, Mark J Twain wrote:
 On Friday, 5 August 2016 at 21:12:06 UTC, ag0aep6g wrote:
[...]
 ----
 struct MutableSomething
 {
     int value;
     void mutate(int newValue) { value = newValue; }
 }

 struct ImmutableSomething
 {
     int value;
     /* no mutate method here */
 }

 void main()
 {
     auto i = ImmutableSomething(1);
     (cast(MutableSomething) i).mutate(2);
     import std.stdio;
     writeln(i.value); /* prints "2" */
 }
 ----
wow, that seems like a huge design issues. Change one value to double though and it won't work. It's not general and seems to be a design flaw. It is not proof of anything in this case though. Why? Because an Queue might not have the same size as an ImmutableQueue and your "proof" only works when they are the same size.
You can cast between structs of different sizes. Just need to add some magical symbols: ---- struct MutableSomething { float value; void mutate(int newValue) { value = newValue; } float somethingElse; } struct ImmutableSomething { int value; /* no mutate method here */ } void main() { auto i = ImmutableSomething(1); (* cast(MutableSomething*) &i).mutate(2); import std.stdio; writeln(i.value); /* prints "2" */ } ---- You can cast anything to anything this way. It's absolutely unsafe, of course, just like casting away immutable and then mutating. Also, MutableFoo and ImmutableFoo would usually have the same fields, wouldn't they? [...]
 What kind of example? I have already given examples and proved that
 there is a functional difference.
The first example you gave was ImmutableArray. You later said it was a bad example, and I should disregard it. You also said something about caching allocations. I'm still not sure how that's supposed to work differently than with an ordinary array/object/whatever. If you uphold that point, I would appreciate an example in code that shows how it works. Then you said that ImmutableQueue would have better errors than immutable Queue. That's wrong. Again, I'd appreciate a code example that shows the better errors, if you think you were correct there. The next point was that one can cast immutable away, but they can't cast ImmutableFoo to MutableFoo. That's wrong, too. Casting immutable away and then mutating has undefined behavior, i.e. it's not allowed. And you can cast between unrelated types just fine. One thing that's true is that an uncallable method is slightly different from one that's not there at all. Chris Wright gave an example where this would make it a tiny bit harder to mess up. But I think that's a rather minor point. And it has to be weighed against the downsides of ImmutableFoo, like the added complexity and it being more of an eyesore than immutable Foo. If there's more to this, a code example would do wonders. Those are the points/examples that I'm aware of. I don't see much in favor of ImmutableFoo. If I missed anything, please point it out. I'm repeating myself, but code examples would be great then.
 I will not continue this conversation
 because you refuse accept that. First you haven't given your criteria
 for me to prove anything to you to satisfy whatever it is you want.
I'm not asking for (strict mathematical) proof. Just some example that highlights the pros of ImmutableFoo over immutable Foo. Can be subjective, like "See? This looks much nicer with my version". So far I just don't see any reason to use it over the immutable keyword.
 Second, I'm not here to waste my time trying to prove the merits of the
 tool. Again, either use it if you feel like it does something or don't.
 The method is not flawed in and of itself. It works(if it didn't,
 someone would have probably already shown it not to work).

 All you can do is assert a negative, which you can't prove yourself.
I'm not disputing that it works. (Neither am I saying that it does work correctly.) Assuming that it works just fine, it apparently does almost exactly what immutable does, just in a more cumbersome way.
 So we end up chasing each other tails, which I refuse to do.
I don't think the discussion has been that way so far. You've made some points where you were mistaken about how immutable and casting work. So they fell flat. You can defend them (point out errors in my refusals), or you can bring up other points. You don't have to, of course, but I don't think we have to agree to disagree just yet. I don't know why you refuse to give code examples. If you're using this thing, you have something lying around, no? Some code that shows the benefits of your approach would shut me up. I'd try for sure to show that it works similarly well with plain immutable, though ;)
 If you want more proof then it is up to you, not me. I have my proof and
 it is good enough for me.
Sure, go ahead. But it seems to me that you're basing this on wrong impressions of immutable and casts.
Aug 05 2016
prev sibling next sibling parent H.Loom <loom grossnet.az> writes:
On Thursday, 4 August 2016 at 18:22:52 UTC, Mark "J" Twain wrote:
 Marking a widget immutable is not the same as having an 
 ImmutableWidget. Can you see the difference?
Yes, you're thinking here of logical vs physical immutability.
 I assure you there is. The immutable keyword only prevents data 
 manipulation, it does not change the interface. For simple 
 primitives, there is not much difference, but for larger 
 complex types, the immutable keyword doesn't cut it.

 immutable Queue!int q1;
 ImmutableQueue!int q2;
I don't get you well because D has actually the ability to use immutable as part of the type. This feature is called the type constructors. immutable Queue : attribute or storage class immutable(Queue): part of type
Aug 05 2016
prev sibling parent H.Loom <loom grossnet.az> writes:
On Thursday, 4 August 2016 at 18:22:52 UTC, Mark "J" Twain wrote:
 Building immutability in to the type system itself allows the 
 programmer to make immutable smarter and control exactly what 
 it does.
once again it is in the type system: °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°° struct Queue{} alias ImmutQueue = immutable(Queue); unittest { static assert(!is(Queue == ImmutQueue)); } °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
Aug 05 2016
prev sibling parent reply qznc <qznc web.de> writes:
On Tuesday, 2 August 2016 at 21:48:58 UTC, Mark Twain wrote:
 global ImmutableArray!int Data;


 MutableArray!int DataCopy = Data.Copy; // Creates a mutable 
 copy of Data.
 ... Do work with DataCopy ...
 Data.Replace(DataCopy); // Makes a copy of DataCopy.
I see the problem that you cannot compose this. Expand the example to two global arrays: global ImmutableArray!int Data1; global ImmutableArray!int Data2; MutableArray!int DataCopy1 = Data1.Copy; MutableArray!int DataCopy2 = Data2.Copy; ... Do work with DataCopy1 and DataCopy2 ... Data1.Replace(DataCopy1); // in between state is inconsistent => unsafe Data2.Replace(DataCopy2);
Aug 03 2016
parent Mark "J" Twain <Juckleberry Twain.com> writes:
On Wednesday, 3 August 2016 at 08:09:41 UTC, qznc wrote:
 On Tuesday, 2 August 2016 at 21:48:58 UTC, Mark Twain wrote:
 global ImmutableArray!int Data;


 MutableArray!int DataCopy = Data.Copy; // Creates a mutable 
 copy of Data.
 ... Do work with DataCopy ...
 Data.Replace(DataCopy); // Makes a copy of DataCopy.
I see the problem that you cannot compose this. Expand the example to two global arrays: global ImmutableArray!int Data1; global ImmutableArray!int Data2; MutableArray!int DataCopy1 = Data1.Copy; MutableArray!int DataCopy2 = Data2.Copy; ... Do work with DataCopy1 and DataCopy2 ... Data1.Replace(DataCopy1); // in between state is inconsistent => unsafe Data2.Replace(DataCopy2);
I don't see this at all. What in between state are you talking about? Each object has it's own copy so no inconsistency is possible. If you are saying that Data1 and Data2 are suppose to be consistent as a pair, then your doing it wrong. You should combine them in to a single structure. The same problem would exist with other techniques. You have acted like the order of declaration matters but your example, given the information, can be written as
 global ImmutableArray!int Data1;
 MutableArray!int DataCopy1 = Data1.Copy;
//....
 Data1.Replace(DataCopy1);
 global ImmutableArray!int Data2;
 MutableArray!int DataCopy2 = Data2.Copy;
//....
 Data2.Replace(DataCopy2);
Aug 03 2016