digitalmars.D.learn - mutable, const, immutable guidelines
- Daniel Davidson (39/39) Oct 02 2013 I'm reviewing Ali's insightful presentation from 2013 DConf. I
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (33/57) Oct 02 2013 For one, immutable is thread-safe. Also, an object can safely hold on to...
- Daniel Davidson (79/139) Oct 02 2013 Ok - then new guidelines should be added to the effect that, "if
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (31/40) Oct 07 2013 I have to report that I am not finding much time to go deeper in these
- qznc (12/24) Oct 08 2013 I do not agree with you, that the user has the responsibility.
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (42/69) Oct 08 2013 I have difficulty agreeing with myself as well. :) However, the power of...
- Daniel Davidson (43/118) Oct 09 2013 The example is great. I would say it like "Apparently the library
- qznc (26/87) Oct 10 2013 Maybe the fact that D allows this implicit copy to immutable is
- Daniel Davidson (8/34) Oct 10 2013 Yes - the issue arises because the language has different
- qznc (21/25) Oct 08 2013 I think guideline 1 should be about "arguments" not "variables".
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (36/59) Oct 08 2013 As demonstrated in the presentation, const erases the actual type. So,
- qznc (21/41) Oct 09 2013 For this case, there are basically two approaches: a) prevent the
- Daniel Davidson (81/137) Oct 09 2013 True. But what does it mean to be "used in an immutable context".
- qznc (13/35) Oct 09 2013 Why "the first one still probably requires the same guarantee"?
- Daniel Davidson (9/47) Oct 10 2013 I don't think that is how people interpret and use const. The
- =?UTF-8?B?Q2hyaXN0aWFuIEvDtnN0bGlu?= (10/12) Oct 10 2013 i think what the usual understanding of const for an argument to callee
- qznc (9/23) Oct 10 2013 The linked page clearly says "It may, however, be changed by
- Daniel Davidson (23/36) Oct 10 2013 That is probably a reasonable interpretation... but I think it
- Joseph Rushton Wakeling (18/38) Oct 10 2013 If you are insisting that your function receive immutable inputs, you ar...
- qznc (20/58) Oct 09 2013 The benefit of immutable is that nobody else can change it.
- Joseph Rushton Wakeling (11/14) Oct 10 2013 Among other things, because no LinearCongruentialEngine will ever, ever ...
- Daniel Davidson (16/57) Oct 16 2013 After trying for several days to use immutable with types
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (4/19) Oct 16 2013 I think this topic should be carried to the main newsgroup already. I am...
- H. S. Teoh (55/70) Oct 16 2013 I think it helps to realize that D's const system is different from
- Daniel Davidson (32/143) Oct 16 2013 I think the term "mutable aliasing" and "what you want" don't
- H. S. Teoh (13/43) Oct 16 2013 [...]
- Daniel Davidson (9/75) Oct 16 2013 Thanks. I appreciate any help I can get. Here is a small sample:
- qznc (22/27) Oct 16 2013 I think people in this thread know how const works, but some
- Daniel Davidson (12/41) Oct 16 2013 I don't understand how it could be fine. As code grows it would
- Dicebot (7/17) Oct 16 2013 I think any usage of immutable with types/entities not initially
- Daniel Davidson (25/46) Oct 16 2013 I don't disagree. Now, what does it mean to "initially design for
- H. S. Teoh (45/69) Oct 16 2013 I'd say that user code should use const, not immutable (unless the
- qznc (11/22) Oct 16 2013 Sounds reasonable. Which one of the following would you prefer?
- H. S. Teoh (8/30) Oct 17 2013 Well, that's a bit too generic to really decide one way or the other.
- qznc (12/23) Oct 17 2013 What about functions, which are not thread-safe?
- H. S. Teoh (13/35) Oct 17 2013 In D, variables are thread-local by default, and only shared if
- Daniel Davidson (47/162) Oct 17 2013 The distinction between user code and the rest (non-user code?)
- H. S. Teoh (80/119) Oct 16 2013 The root of the problem is reliance on assignment between mutable /
- H. S. Teoh (38/73) Oct 16 2013 Well, I think here the type system is working as advertised. Since the
I'm reviewing Ali's insightful presentation from 2013 DConf. I wonder has he or anyone else followed up on the concepts or formalized some guidelines that could achieve consensus. I definitely agree it would be helpful to have a 50 Ways To Improve Your D. The first thing I'd like to see is a set of guidelines on mutability along the lines he discussed in his talk. But as it stands, I don't know if there was any finalization/consensus. For instance, from the first two guidelines at the end (after several iterative reconstructions of those guidelines): 1. If a variable is never mutated, make it const, not immutable. 2. Make the parameter reference to immutable if that is how you will use it anyway. It is fine to ask a favor from the caller. ... If you follow (1) exclusively, why the need for immutable in the language at all? Maybe it is a philosophical question, but where does immutability really come from? Is it an aspect of some piece of data or is it a promise that function will not change it? Or is it a requirement by a function that data passed not be changed by anyone else? The two keywords cover all in some sense. I found the end of the video amusing, when one gentleman looking at a rather sophisticated "canonical" struct with three overloads for 'this(...)' and two overloads for opAssign, asked if all those methods were required. I think there was back and forth and head-scratching. Another asked if the language designers were happy with the resultant complexity. Naturally the answer was yes - it is a good mix. If that is the case I wonder if the reason is they don't write software in D like Ali was looking to develop. I imagine standard library code is much more functional than OO by its very nature. My money says you will not find a struct S constructed like Ali's in the wild - it is just too much boilerplate. But surely they have their own guidelines/approaches. By posting this I am not looking for a single answer to the simple question above as it is just one of many questions in the set of "how do you choose among the options in general". If you have such guidelines - please post them. Thanks Dan
Oct 02 2013
On 10/02/2013 06:09 AM, Daniel Davidson wrote:I'm reviewing Ali's insightful presentation from 2013 DConf. I wonder has he or anyone else followed up on the concepts or formalized some guidelines that could achieve consensus.I have not followed up on those concepts.1. If a variable is never mutated, make it const, not immutable. 2. Make the parameter reference to immutable if that is how you will use it anyway. It is fine to ask a favor from the caller. ... If you follow (1) exclusively, why the need for immutable in the language at all?For one, immutable is thread-safe. Also, an object can safely hold on to a piece of data and trust that it will never change. A file name is a good example: An object need not copy a string that is given to it as a constructor parameter. That string will be immutable as long as that reference is valid.Maybe it is a philosophical question, but where does immutability really come from? Is it an aspect of some piece of data or is it a promise that function will not change it? Or is it a requirement by a function that data passed not be changed by anyone else?The two concepts are necessarily conflated in languages like C++ because for those languages there is only the 'const' keyword. D's immutable is the last point you made: a requirement by a function that data passed not be changed by anyone else?I found the end of the video amusing, when one gentleman looking at a rather sophisticated "canonical" struct with three overloads for 'this(...)' and two overloads for opAssign, asked if all those methods were required.I think you are referring to Ben Gertzfield's question. He later told me that he was sorry that he asked the wrong question at the wrong time. :D To be honest, that slide was added more as an after thought. I suspect that that canonical struct is simpler today after changes made to dmd since the conference. (I will look at it later.) For example, there are no 'pure' keywords around in that code. I think the (ability to) use of that keyword will simplify matters. However, I should have answered Ben's question by the following: * If the struct is simply a value type or has no mutable indirections, no member function is really necessary. D takes care of it automatically. * Sometimes post-blit will be necessary for correctness. For example, we may not want two objects share the same internal buffer, File, etc. * As noted in the presentation, the default behavior of struct assignment in D is exception-safe: first copy then swap. In some cases that automatic behavior is less than optimal e.g. when an object's existing buffer can be reused; no need to "copy then swap (which implicitly destroys)" in that case. (The spec or Andrei's book has that exact case as an example.) So, opAssign should be for optimization reasons only. (I will look at these again later.)By posting this I am not looking for a single answer to the simple question above as it is just one of many questions in the set of "how do you choose among the options in general". If you have such guidelines - please post them. Thanks DanThanks for asking these questions. Ali
Oct 02 2013
On Wednesday, 2 October 2013 at 17:07:55 UTC, Ali Çehreli wrote:On 10/02/2013 06:09 AM, Daniel Davidson wrote:Ok - then new guidelines should be added to the effect that, "if you want a guarantee of read-only that can be counted on across threads, use immutable". But, I'm not sure that is a sensible guideline or when you know up-front to deal with it. Often threading is an afterthought. My point is not that immutable is not useful, but that if you never make an lvalue immutable in the guidelines (via guideline 1), it is hard to see them ever being used at all. The case in the slides where it was used was to when a method required it on a parameter type with mutable aliasing. In this case the client was required to copy beforehand. In other words, I think even the summary simple guidelines are not so simple, maybe because they are incomplete. I would love to help complete them.1. If a variable is never mutated, make it const, notimmutable.2. Make the parameter reference to immutable if that is howyou will useit anyway. It is fine to ask a favor from the caller. ... If you follow (1) exclusively, why the need for immutable inthelanguage at all?For one, immutable is thread-safe.Also, an object can safely hold on to a piece of data and trust that it will never change.I think for this to be true in general, the piece of data must be immutable, not just tail-immutable. If the class holds onto a string (i.e. only tail-immutable), the class can add to the string - just not change the existing characters.A file name is a good example: An object need not copy a string that is given to it as a constructor parameter. That string will be immutable as long as that reference is valid.IMHO file name and ultimately string are the *worst* example :-) The reason is they are a special case because they provide *safe sharing*. String encapsulates its contiguous data and does not allow mutation of elements. But it does allow concatenation that by design detail can not be seen by other references to the same data because of copy-on-write. Granted, if you have a handle to immutable(T)[] you know *it* (i.e. the complete string) will not change (unless you change it). The problem is it is just a byproduct of the implementation details of immutable(T)[]. From the referrer's point of view it is immutable. For example, the following comparable associative array does not have the same benefit as string since two references to the same underlying data *do see changes*: import std.stdio; struct V { string s = "I'm a value"; } alias immutable(V)[int] Map; void main() { Map m = [ 1:V(), 2:V("bar") ]; Map m2 = m; m2[3] = V("moo"); writeln(m); writeln(m2); } The reason I think string is a bad example is one might incorrectly assume tail-immutable is good enough for true read/only thread-safe data. And it is for string or any immutable(T)[], but not in general.I can understand - I would not want to put you on the spot on stage either. But, I have no qualms doing it in the news groups :-)Maybe it is a philosophical question, but where does immutability really come from? Is it an aspect of some pieceofdata or is it a promise that function will not change it? Orisit a requirement by a function that data passed not be changed by anyone else?The two concepts are necessarily conflated in languages like C++ because for those languages there is only the 'const' keyword. D's immutable is the last point you made: a requirement by a function that data passed not be changed by anyone else?I found the end of the video amusing, when one gentlemanlooking at arather sophisticated "canonical" struct with three overloadsfor'this(...)' and two overloads for opAssign, asked if allthose methodswere required.I think you are referring to Ben Gertzfield's question. He later told me that he was sorry that he asked the wrong question at the wrong time. :DTo be honest, that slide was added more as an after thought. I suspect that that canonical struct is simpler today after changes made to dmd since the conference. (I will look at it later.) For example, there are no 'pure' keywords around in that code. I think the (ability to) use of that keyword will simplify matters. However, I should have answered Ben's question by the following: * If the struct is simply a value type or has no mutable indirections, no member function is really necessary. D takes care of it automatically.Which I wonder if is a good idea. Immediately from that comes the problem that "D takes care of it" breaks when you go from no mutable aliasing to some mutable aliasing. That mutable aliasing could be deep down in the composition chain. I know you can feel this issue since your slides point out cases where code may break when well encapsulated structs make changes that should not impact client code but will break them.* Sometimes post-blit will be necessary for correctness. For example, we may not want two objects share the same internal buffer, File, etc.Agreed - but post-blits are easier said than done with nested data. For instance, if you have a member that is a struct that does not provide a post-blit (i.e. the original author did not concern himself with sharing) then you have to effectively write his post-blit for him in your post-blit. This is why I lobby for language supported generalized dup.* As noted in the presentation, the default behavior of struct assignment in D is exception-safe: first copy then swap. In some cases that automatic behavior is less than optimal e.g. when an object's existing buffer can be reused; no need to "copy then swap (which implicitly destroys)" in that case. (The spec or Andrei's book has that exact case as an example.) So, opAssign should be for optimization reasons only. (I will look at these again later.)I look forward to it and would be glad to help get to a good set of guidelines. For instance filling out this, which only covers variable declarations - not parameter passing, with a robust "when to use". | context | T | C(T) | I(T) | I(T)[] | |-----------------+---+------+------+--------| | local stack | | | | | | local heap | | | | | | global | | | | | | instance member | | | | | | static member | | | | | You could actually have two tables - one for T1 with no mutable aliasing and one for T2 with mutable aliasing. But relying on that in the decision matrix may lead to issues. Thanks, Dan
Oct 02 2013
On 10/02/2013 10:07 AM, Ali Çehreli wrote:On 10/02/2013 06:09 AM, Daniel Davidson wrote: > I'm reviewing Ali's insightful presentation from 2013 DConf. I > wonder has he or anyone else followed up on the concepts or > formalized some guidelines that could achieve consensus. I have not followed up on those concepts. > 1. If a variable is never mutated, make it const, not immutable. > 2. Make the parameter reference to immutable if that is how youwill use> it anyway. It is fine to ask a favor from the caller. > ...I have to report that I am not finding much time to go deeper in these issues. However, I spent some time this weekend, gaining a hint of enlightenment. immutable is a requirement on two concepts, second of which is new to me: 1) As it is commonly known, it is a requirement that the data does not mutate. 2) It is also a requirement on the type that the variables of that type will always be usable as immutable. I have not formalized "be usable" yet but what I mean is that the type cannot be modified in the future in a way that inhibits its use in existing code. To look at just one usage example, the following line carries two requirements: auto a = T(); immutable b = a; 1) b will be an immutable copy of a. 2) T will always be usable as in that fashion. If T appears on an API, it is the responibility of the user to ensure whether they are allowed to treat T in that way. Otherwise, they risk maintainability if the module decides to change T in any way that fits the module's needs. If they have not yet advertised that T can be used as immutable, it should not be. So, the question is how can a module expose a type that can always be used as immutable? Can the module guarantee that its future needs will never disallow T's use as immutable (e.g. by inserting mutable references into the type)? Can the library always guarantee that T can be used as immutable by improving its definition, e.g. by adding necessary opCast, opEquals, 'alias this', constructors, etc? I don't know these answers... :/ (yet! :p) Ali
Oct 07 2013
On Monday, 7 October 2013 at 17:57:11 UTC, Ali Çehreli wrote:To look at just one usage example, the following line carries two requirements: auto a = T(); immutable b = a; 1) b will be an immutable copy of a. 2) T will always be usable as in that fashion. If T appears on an API, it is the responibility of the user to ensure whether they are allowed to treat T in that way. Otherwise, they risk maintainability if the module decides to change T in any way that fits the module's needs. If they have not yet advertised that T can be used as immutable, it should not be.I do not agree with you, that the user has the responsibility. Rather I think the provider of T has the responsibility to maintain backwards compatibility. My principle is "anything is allowed, unless explicitly forbidden". This includes immutable type constructing. It is great that we get a type error, if backwards compatibility is broken. Nevertheless, the question of responsibility seems to be subjective. Are there any clear technical reasons for either way? Unfortunately, there is no way for the provider to allow or disallow immutable(T). Maybe there should be a UDA or something for this?
Oct 08 2013
On 10/08/2013 03:12 PM, qznc wrote:On Monday, 7 October 2013 at 17:57:11 UTC, Ali Çehreli wrote:I have difficulty agreeing with myself as well. :) However, the power of immutable makes me think so. Interestingly, once a user creates an immutable variable of a type, that type must support that use.To look at just one usage example, the following line carries two requirements: auto a = T(); immutable b = a; 1) b will be an immutable copy of a. 2) T will always be usable as in that fashion. If T appears on an API, it is the responibility of the user to ensure whether they are allowed to treat T in that way. Otherwise, they risk maintainability if the module decides to change T in any way that fits the module's needs. If they have not yet advertised that T can be used as immutable, it should not be.I do not agree with you, that the user has the responsibility.Rather I think the provider of T has the responsibility to maintain backwards compatibility.Agreed but I don't know how. Here is challenge: Let's start with the following program: // Library type struct MyInt { int i; } void main() { // User code auto a = MyInt(1); immutable b = a; } Let's assume that the library adds a private dynamic array of ints to that type: // Library type struct MyInt { int i; private int[] history; // <-- Added } void main() { // User code auto a = MyInt(1); immutable b = a; // <-- Existing code breaks } Error: cannot implicitly convert expression (a) of type MyInt to immutable(MyInt) Apparently, the library made a change that made its type non-immutable-able. :p What is missing in MyInt? How should it be defined instead of simply adding 'history'?My principle is "anything is allowed, unless explicitly forbidden". This includes immutable type constructing. It is great that we get a type error, if backwards compatibility is broken.Agreed. What is the solution?Nevertheless, the question of responsibility seems to be subjective. Are there any clear technical reasons for either way?Technically, MyInt is in a breaking state because the user used it as immutable. One way to look at this situation is to observe that the user took some freedom of the library just by using its type as immutable.Unfortunately, there is no way for the provider to allow or disallow immutable(T). Maybe there should be a UDA or something for this?I think the language is lacking tools to support the use case above. Ali
Oct 08 2013
On Wednesday, 9 October 2013 at 04:31:55 UTC, Ali Çehreli wrote:On 10/08/2013 03:12 PM, qznc wrote:The example is great. I would say it like "Apparently the library made a change that added mutable aliasing and therefore broke the special logic the compiler applies to types without aliasing". I think the root cause is the language special casing structs with no mutable aliasing by allowing the "immutable b = a" to work in the first place. One step toward a solution would be the existence of a generalized dup. Then calls to "immutable b = a" with mutable aliasing could lower to a call to the generalized dup, causing a deep copy and therefore still allowing assignment from non-immutable to immutable. This has its own set of problems - like what if the library designer wants sharing and not deep copies (perhaps they employ copy on write semantics on their own). What are other solutions or ways to prevent this jump from no aliasing to mutable aliasing breaking code? Maybe assume mutable aliasing on all your structs from the start, then those statements like "immutable b = a" won't appear in the first place. struct T { ... version(unittest) int[] _justToAddMutableAliasing; } Now you know that assignment of "immutable b = a" will fail in unittests. But this is crazy and it would only help your users if they run unittests.On Monday, 7 October 2013 at 17:57:11 UTC, Ali Çehreli wrote:carries twoTo look at just one usage example, the following lineto ensurerequirements: auto a = T(); immutable b = a; 1) b will be an immutable copy of a. 2) T will always be usable as in that fashion. If T appears on an API, it is the responibility of the userthey riskwhether they are allowed to treat T in that way. Otherwise,that fitsmaintainability if the module decides to change T in any waycan be usedthe module's needs. If they have not yet advertised that TI have difficulty agreeing with myself as well. :) However, the power of immutable makes me think so. Interestingly, once a user creates an immutable variable of a type, that type must support that use.as immutable, it should not be.I do not agree with you, that the user has the responsibility.Rather I think the provider of T has the responsibility to maintainbackwardscompatibility.Agreed but I don't know how. Here is challenge: Let's start with the following program: // Library type struct MyInt { int i; } void main() { // User code auto a = MyInt(1); immutable b = a; } Let's assume that the library adds a private dynamic array of ints to that type: // Library type struct MyInt { int i; private int[] history; // <-- Added } void main() { // User code auto a = MyInt(1); immutable b = a; // <-- Existing code breaks } Error: cannot implicitly convert expression (a) of type MyInt to immutable(MyInt) Apparently, the library made a change that made its type non-immutable-able. :p What is missing in MyInt? How should it be defined instead of simply adding 'history'?Technically, MyInt is in a breaking state because the user used it as immutable. One way to look at this situation is to observe that the user took some freedom of the library just by using its type as immutable.I don't quite understand the terminology "using it's type as immutable". I think the breaking state comes from copying in general having two flavors - shallow and deep. Transitive deep copy always offers the option of immutable source and target. Shallow copy also offers the option of immutable source and target, as long as the type has no mutable aliasing, simply because a shallow copy is also a deep copy if no aliasing. And, shallow copy does not offer the option of immutable in the face of types with mutable aliasing. It is when types go from having no mutable aliasing to having mutable aliasing (and the reverse) that the rules of the compiler change the game.One way might be to disallow copy and assignment (is this a D capability?) and provide pure factory that only returns immutable(T).Unfortunately, there is no way for the provider to allow ordisallowimmutable(T). Maybe there should be a UDA or something forthis?I think the language is lacking tools to support the use case above.I think if the default assignment and copy constructor for structs with aliasing were automatic transitive deep copy this issue would disappear. Other issues would come up. Also, I'm pretty sure Walter is not a fan of this concept because he does not advocate copying any data in postblits.Ali
Oct 09 2013
On Wednesday, 9 October 2013 at 04:31:55 UTC, Ali Çehreli wrote:On 10/08/2013 03:12 PM, qznc wrote:Maybe the fact that D allows this implicit copy to immutable is the problem? If one could require the use of a specific function, this function could be overridden with working behavior. The following code works. import std.exception: assumeUnique; struct MyInt { int i; private int[] history; // <-- Added } // idup for creating immutable MyInts immutable(MyInt) idup(const MyInt mi) pure nothrow trusted { MyInt cpy = MyInt(mi.i); return cast(immutable) cpy; } // special version for performance immutable(MyInt) idup(immutable MyInt mi) pure nothrow trusted { return mi; } unittest { auto a = MyInt(1); immutable b = a.idup; // <-- Code does not break } D could either remove the implicit-copy-to-immutable or provide a special copy-constructor for immutable structs.On Monday, 7 October 2013 at 17:57:11 UTC, Ali Çehreli wrote:carries twoTo look at just one usage example, the following lineto ensurerequirements: auto a = T(); immutable b = a; 1) b will be an immutable copy of a. 2) T will always be usable as in that fashion. If T appears on an API, it is the responibility of the userthey riskwhether they are allowed to treat T in that way. Otherwise,that fitsmaintainability if the module decides to change T in any waycan be usedthe module's needs. If they have not yet advertised that TI have difficulty agreeing with myself as well. :) However, the power of immutable makes me think so. Interestingly, once a user creates an immutable variable of a type, that type must support that use.as immutable, it should not be.I do not agree with you, that the user has the responsibility.Rather I think the provider of T has the responsibility to maintainbackwardscompatibility.Agreed but I don't know how. Here is challenge: Let's start with the following program: // Library type struct MyInt { int i; } void main() { // User code auto a = MyInt(1); immutable b = a; } Let's assume that the library adds a private dynamic array of ints to that type: // Library type struct MyInt { int i; private int[] history; // <-- Added } void main() { // User code auto a = MyInt(1); immutable b = a; // <-- Existing code breaks } Error: cannot implicitly convert expression (a) of type MyInt to immutable(MyInt)
Oct 10 2013
On Thursday, 10 October 2013 at 23:06:23 UTC, qznc wrote:Maybe the fact that D allows this implicit copy to immutable is the problem? If one could require the use of a specific function, this function could be overridden with working behavior. The following code works.Yes - the issue arises because the language has different behavior for structs with mutable aliasing and structs without. For structs without mutable aliasing a copy is safe so crossing bounds between any of (mutable, const, immutable) is easily achieved.import std.exception: assumeUnique; struct MyInt { int i; private int[] history; // <-- Added } // idup for creating immutable MyInts immutable(MyInt) idup(const MyInt mi) pure nothrow trusted { MyInt cpy = MyInt(mi.i); return cast(immutable) cpy; } // special version for performance immutable(MyInt) idup(immutable MyInt mi) pure nothrow trusted { return mi; } unittest { auto a = MyInt(1); immutable b = a.idup; // <-- Code does not break } D could either remove the implicit-copy-to-immutable or provide a special copy-constructor for immutable structs.See this discussion: http://forum.dlang.org/thread/fmhkvogowjlduqercsjn forum.dlang.org
Oct 10 2013
On Wednesday, 2 October 2013 at 13:09:34 UTC, Daniel Davidson wrote:1. If a variable is never mutated, make it const, not immutable. 2. Make the parameter reference to immutable if that is how you will use it anyway. It is fine to ask a favor from the caller. ...I think guideline 1 should be about "arguments" not "variables". Functions should take const arguments, so they can be called with mutable and immutable values. I would rephrase the second guideline as: "Never dup or idup an argument to make it mutable or immutable, but require the caller to do this (might be able to avoid it)". For more guidelines: 3. Return value, which are freshly created, should never be const. They should be mutable or immutable. Basically, I cannot come up with a case, where const would be preferable to mutable and immutable. Maybe someone is able to find a good example? Of course, this does not hold for returned values, which are retrieved from somewhere (array,container,etc). In this case, I have no general advice. 4. Data structures should not restrict themselves to be mutable, const, or immutable. This is a noble goal, which in reality is probably either trivial or impossible. So I am not sure, if it is worthy to be called a guideline.
Oct 08 2013
On 10/08/2013 03:03 PM, qznc wrote:On Wednesday, 2 October 2013 at 13:09:34 UTC, Daniel Davidson wrote:As demonstrated in the presentation, const erases the actual type. So, the function must take a copy if the argument is to be used in an immutable context. Instead, if the immutable appears on the interface, that copy will be avoided.1. If a variable is never mutated, make it const, not immutable. 2. Make the parameter reference to immutable if that is how you will use it anyway. It is fine to ask a favor from the caller. ...I think guideline 1 should be about "arguments" not "variables". Functions should take const arguments, so they can be called with mutable and immutable values.I would rephrase the second guideline as: "Never dup or idup an argument to make it mutable or immutable, but require the caller to do this (might be able to avoid it)".Agreed. But it means you agree with me that immutable should indeed appear on function signatures as needed.For more guidelines: 3. Return value, which are freshly created, should never be const. They should be mutable or immutable.Agreed. I say that it should be mutable if the function is pure, as such a return values is automatically convertible to immutable.Basically, I cannot come up with a case, where const would be preferable to mutable and immutable. Maybe someone is able to find a good example?You mean on the return type? Perhaps if the freshly created object carries references to existing data that you don't want mutated. Making the head const makes tail const; so the members would all be protected. However, it is always possible to return a mutable object of a type that has const members: struct Result { const(int)[] reference_to_precious_existing_data; } Result freshly_created_value() { // ... }Of course, this does not hold for returned values, which are retrieved from somewhere (array,container,etc). In this case, I have no general advice. 4. Data structures should not restrict themselves to be mutable, const, or immutable.What is the template of a struct that can be used as such? Providing simple values seems to be insufficient: struct MyInt { int i; private int[] history; } What else should the struct above have to make it usable as mutable, const, immutable?This is a noble goal, which in reality is probably either trivial or impossible. So I am not sure, if it is worthy to be called a guideline.We have to nail this down already. We have this great addition of immutable in the language but we either don't know how to use it or the language is lacking hopefully trivial things to make it work. Ali
Oct 08 2013
On Wednesday, 9 October 2013 at 04:41:35 UTC, Ali Çehreli wrote:For this case, there are basically two approaches: a) prevent the use as immutable in the first place or b) make it work as immutable by force. For version a) we could have a UDA like this: NeverImmutable struct MyInt { int i; } auto x = immutable(MyInt)(42); // compile error immutable y = MyInt(42); // compile error However, the library writer would have to anticipate the private dynamic array, which probably does not happen. Defensive coding like putting NeverImmutable on everything would defeat the purpose as well. For version b) we would relax the type system, which opens an ugly can of worms of unknown size. Basically, immutable data would be allowed to have mutable private data, if the author sufficiently asserts everything is correct. Since there are lots of interesting compiler optimizations with immutable things, I can imagine there will be lots of subtle errors until one can make something like this work. At this point, I cannot imagine that there is a solution which makes this case work, so I think we have to accept this breakage.4. Data structures should not restrict themselves to bemutable, const,or immutable.What is the template of a struct that can be used as such? Providing simple values seems to be insufficient: struct MyInt { int i; private int[] history; } What else should the struct above have to make it usable as mutable, const, immutable?This is a noble goal, which in reality is probably eithertrivial orimpossible. So I am not sure, if it is worthy to be called aguideline. We have to nail this down already. We have this great addition of immutable in the language but we either don't know how to use it or the language is lacking hopefully trivial things to make it work.
Oct 09 2013
On Wednesday, 9 October 2013 at 04:41:35 UTC, Ali Çehreli wrote:On 10/08/2013 03:03 PM, qznc wrote:True. But what does it mean to be "used in an immutable context". In your example you pass a variable to a function `void usefulFunction(string s)`. The problem with transitivity is once you start making rules regarding it, those rules must also be transitive (or turtles all the way down as you say). The point is, the desire in the example wants to use the guideline only at the top level and break it at the next level. If usefulFunction were following the guideline it would have a signature `void usefulFunction(const(char)[] s)` instead. If the guideline were: "if a function parameter is not mutated, make it const" and it were applied that way everywhere, the problem you suggest would not be hit. But we can't go changing any `standardFunction(string s)` to `standardFunction(const(char)[] s)`. Using const on parameters seems to be the prevalent guideline - even though passing strings breaks it. In regards to parameters, when, if ever, is there benefit to immutable over const (if you assume all called functions from that function have const parameters/signatures). The only reason I can think is if a *member* function wants to hold onto a reference or copy for future use and the designer wants to guarantee that member is truly immutable. An example would be a struct that initializes with an immutable(T) that the struct wants to then use throughout its life. struct S { this(ref immutable(T) t) { _t = t; } immutable(T) _t; } This is all very confusing and to me unsettled. I've brought up questions like this many times and have yet to hear Walter or Andrei weigh in. Either we (you and I) are missing something and making things much more difficult than necessary or it is just difficult. I think a third likely possibility is that composition in the face of structs with mutable aliasing is not heavily used by them.On Wednesday, 2 October 2013 at 13:09:34 UTC, Daniel Davidsonwrote:immutable.1. If a variable is never mutated, make it const, notyou will2. Make the parameter reference to immutable if that is how"variables".use it anyway. It is fine to ask a favor from the caller. ...I think guideline 1 should be about "arguments" notFunctions should take const arguments, so they can be calledwithmutable and immutable values.As demonstrated in the presentation, const erases the actual type. So, the function must take a copy if the argument is to be used in an immutable context. Instead, if the immutable appears on the interface, that copy will be avoided.I don't really agree. Naturally, if const were used all the way down there would be no need for copies. It is the introduction of immutable (via string in your example) that breaks the rule and causes the pain. But then if the rule were adopted globally - immutable is never used on parameters in a signature, only const is used, when would you start seeing any benefit to the immutable keyword?I would rephrase the second guideline as: "Never dup or idupan argumentto make it mutable or immutable, but require the caller to dothis(might be able to avoid it)".Agreed. But it means you agree with me that immutable should indeed appear on function signatures as needed.I don't know if these are good examples, but they are examples: From zip.d: const(void)[] compress(const(void)[] buf) From vibe: const(Json) opIndex(string key) const { [snip]For more guidelines: 3. Return value, which are freshly created, should never beconst. Theyshould be mutable or immutable.Agreed. I say that it should be mutable if the function is pure, as such a return values is automatically convertible to immutable.Basically, I cannot come up with a case, where const would bepreferableto mutable and immutable. Maybe someone is able to find agood example?I'm confused by the term "usable as mutable, const, immutable"? Isn't it true that types are not mutable, const, immutable in their definition but only in their context? [snip]4. Data structures should not restrict themselves to bemutable, const,or immutable.What is the template of a struct that can be used as such? Providing simple values seems to be insufficient: struct MyInt { int i; private int[] history; } What else should the struct above have to make it usable as mutable, const, immutable?We have to nail this down already. We have this great addition of immutable in the language but we either don't know how to use it or the language is lacking hopefully trivial things to make it work.Amen, brother! Either it is a great addition and usable, or it is not so great an addition. At times I find myself questioning even the basic benefit of immutable in the context of sharing. I've heard that immutable is great because it means the data can be shared across threads and you have confidence it will never change. This sounds good. But then you have to choose signatures: void foo(const(MutableType) mt); void foo(immutable(MutableType) mt); Naturally the inclination is to choose the second as it is a stronger guarantee that no threads are changing the data. Cool. But wait, the first one still probably requires the same guarantee, even if it does not state it. If in the const case another thread changes the state of mt during the function foo then it will fail. foo actually requires that mt not be change by other threads during its operation - that is the only sane way of using the data in the function. Take, for example, LinearCongruentialEngine from random.d. It has a function: bool opEquals(ref const LinearCongruentialEngine rhs) const Why is it using const here instead of immutable? Does it not care about other threads? No - it just is not smart enough to deal with it. It assumes other threads won't be changing it or if they are caveat developer. So when do you use immutable as a signal that not only will this function not change it, no thread will change it either? Probably when you know you have to deal with threading. But that then would be coupling two maybe orthogonal decisions - the threading of a program and the signature of functions. Where is Scott Myers when you need him :-)Ali
Oct 09 2013
On Wednesday, 9 October 2013 at 15:50:55 UTC, Daniel Davidson wrote:void foo(const(MutableType) mt); void foo(immutable(MutableType) mt); Naturally the inclination is to choose the second as it is a stronger guarantee that no threads are changing the data. Cool. But wait, the first one still probably requires the same guarantee, even if it does not state it. If in the const case another thread changes the state of mt during the function foo then it will fail. foo actually requires that mt not be change by other threads during its operation - that is the only sane way of using the data in the function.Why "the first one still probably requires the same guarantee"? Why would foo fail if "in the const case another thread changes the state of mt"? If foo is not thread-safe, then it should require immutable.Take, for example, LinearCongruentialEngine from random.d. It has a function: bool opEquals(ref const LinearCongruentialEngine rhs) const Why is it using const here instead of immutable? Does it not care about other threads? No - it just is not smart enough to deal with it. It assumes other threads won't be changing it or if they are caveat developer. So when do you use immutable as a signal that not only will this function not change it, no thread will change it either? Probably when you know you have to deal with threading. But that then would be coupling two maybe orthogonal decisions - the threading of a program and the signature of functions.Hm, that actually looks like a bug. If LinearCongruentialEngine is instantiated with a UIntType larger than size_t, it is not thread-safe, but this seems to be claimed in the section header. Why are those two decisions orthogonal? The signature of a function is a contract between caller and callee. If an argument is const, it means the callee says he can handle others changing the state concurrently.
Oct 09 2013
On Wednesday, 9 October 2013 at 23:05:27 UTC, qznc wrote:On Wednesday, 9 October 2013 at 15:50:55 UTC, Daniel Davidson wrote:I don't think that is how people interpret and use const. The general guideline is, for function parameters use const because it is most accepting - it takes mutable, immutable and const. If there are multiple threads developer beware. Threading is or can be somewhat orthogonal to function signatures simply because you can write a whole lot of code with the const guideline above. Then with proper serialized access use that thread unaware code in multi-threaded context.void foo(const(MutableType) mt); void foo(immutable(MutableType) mt); Naturally the inclination is to choose the second as it is a stronger guarantee that no threads are changing the data. Cool. But wait, the first one still probably requires the same guarantee, even if it does not state it. If in the const case another thread changes the state of mt during the function foo then it will fail. foo actually requires that mt not be change by other threads during its operation - that is the only sane way of using the data in the function.Why "the first one still probably requires the same guarantee"? Why would foo fail if "in the const case another thread changes the state of mt"? If foo is not thread-safe, then it should require immutable.Take, for example, LinearCongruentialEngine from random.d. It has a function: bool opEquals(ref const LinearCongruentialEngine rhs) const Why is it using const here instead of immutable? Does it not care about other threads? No - it just is not smart enough to deal with it. It assumes other threads won't be changing it or if they are caveat developer. So when do you use immutable as a signal that not only will this function not change it, no thread will change it either? Probably when you know you have to deal with threading. But that then would be coupling two maybe orthogonal decisions - the threading of a program and the signature of functions.Hm, that actually looks like a bug. If LinearCongruentialEngine is instantiated with a UIntType larger than size_t, it is not thread-safe, but this seems to be claimed in the section header. Why are those two decisions orthogonal? The signature of a function is a contract between caller and callee. If an argument is const, it means the callee says he can handle others changing the state concurrently.
Oct 10 2013
On 10/10/13 1:05 , qznc wrote: Very interesting discussion!contract between caller and callee. If an argument is const, it means the callee says he can handle others changing the state concurrently.i think what the usual understanding of const for an argument to callee is, what is written at http://dlang.org/const3.html. that means for me, that callee promises not to change the data itself. perhaps a bettr description would be readonly. usually you would think that no one else should change the data while callee runs. but at least with c++ i could imagine running callee in a thread with a reference to a const thing which changes underneath and while callee is running.
Oct 10 2013
On Thursday, 10 October 2013 at 18:39:32 UTC, Christian Köstlin wrote:On 10/10/13 1:05 , qznc wrote: Very interesting discussion!The linked page clearly says "It may, however, be changed by another reference to that same data."contract between caller and callee. If an argument is const,it meansthe callee says he can handle others changing the stateconcurrently. i think what the usual understanding of const for an argument to callee is, what is written at http://dlang.org/const3.html. that means for me, that callee promises not to change the data itself. perhaps a bettr description would be readonly.usually you would think that no one else should change the data while callee runs. but at least with c++ i could imagine running callee in a thread with a reference to a const thing which changes underneath and while callee is running.A const argument gives information to both sides. For the caller it means callee does not modify the data. For the callee it means somebody else (another thread) may modify the data at any time. An immutable argument means the same for the caller, but gives an additional guarantee to the callee that nobody modifies the data.
Oct 10 2013
On Thursday, 10 October 2013 at 23:12:24 UTC, qznc wrote:The linked page clearly says "It may, however, be changed by another reference to that same data."That is probably a reasonable interpretation... but I think it will only get you pain. The fact is, regardless of your interpretation of "const" arguments - the general guideline is "prefer const because immutables and mutables can be passed in". Think of it this way: you are writing a very generic function that requires inputs that you do not mutate. If you choose immutable you are making your life easier, I guess, since you know data is not being changed on you. But you are also preventing your function from being called by any arguments that are already mutable (assuming the type has mutable aliasing) because crossing the mutable to immutable divide is not part of the language. So, the only way then for mutable types to make it into your function is to copy them and this may or may not even be possible. And if you truly know no code is mutating your data it is a tougher sell. On the other hand if you go with const parameters, your data could in fact change on you. Unfortunately, it can cause terrible angst when the only options make you wish for other options. Have you actually written nested D data types with mutable aliasing (not just slices but assoc arrays) and used immutable in the signatures? I have tried and not succeeded. I guess this just highlights the need for guidelines.usually you would think that no one else should change the data while callee runs. but at least with c++ i could imagine running callee in a thread with a reference to a const thing which changes underneath and while callee is running.A const argument gives information to both sides. For the caller it means callee does not modify the data. For the callee it means somebody else (another thread) may modify the data at any time. An immutable argument means the same for the caller, but gives an additional guarantee to the callee that nobody modifies the data.
Oct 10 2013
On 11/10/13 01:43, Daniel Davidson wrote:That is probably a reasonable interpretation... but I think it will only get you pain. The fact is, regardless of your interpretation of "const" arguments - the general guideline is "prefer const because immutables and mutables can be passed in".Which makes much sense for code that is intended to be generically useful.Think of it this way: you are writing a very generic function that requires inputs that you do not mutate. If you choose immutable you are making your life easier, I guess, since you know data is not being changed on you. But you are also preventing your function from being called by any arguments that are already mutable (assuming the type has mutable aliasing) because crossing the mutable to immutable divide is not part of the language. So, the only way then for mutable types to make it into your function is to copy them and this may or may not even be possible. And if you truly know no code is mutating your data it is a tougher sell.If you are insisting that your function receive immutable inputs, you are in turn constraining the user of that code. If you really want your function to be "generic", isn't the appropriate thing to allow the user the choice of whether they want to pass it immutable data, or to take the risk of passing it mutable data? Surely there's a tradeoff -- you may well feel the resulting safety is worth it, and there may be other benefits, such as optimizations, that you can derive from having only immutable data. But for the standard library of a multiparadigm language, it's not appropriate to force users to use immutable data.On the other hand if you go with const parameters, your data could in fact change on you. Unfortunately, it can cause terrible angst when the only options make you wish for other options.Well, that depends. If you go with const parameters but you pass it immutable data, you know it won't change on you. You, the user, are in control.Have you actually written nested D data types with mutable aliasing (not just slices but assoc arrays) and used immutable in the signatures? I have tried and not succeeded.Yes, I tried it, and ran into this problem. But generally these days I use "in" to mark arguments that are not intended to be modified. I personally prefer that -- it has a semantic meaning, and how it translates into code is for the compiler.I guess this just highlights the need for guidelines.Guidelines are always nice. :-)
Oct 10 2013
On Wednesday, 9 October 2013 at 15:50:55 UTC, Daniel Davidson wrote:The benefit of immutable is that nobody else can change it. Functions that would be non-thread-safe with const arguments are thread-safe with immutable arguments. That relationship with thread-safety almost seems to be a good guideline. 5. If a function is not thread-safe due to const arguments, consider making it immutable. Otherwise you must explicitly document the lack of thread-safety.I don't really agree. Naturally, if const were used all the way down there would be no need for copies. It is the introduction of immutable (via string in your example) that breaks the rule and causes the pain. But then if the rule were adopted globally - immutable is never used on parameters in a signature, only const is used, when would you start seeing any benefit to the immutable keyword?I would rephrase the second guideline as: "Never dup or idupan argumentto make it mutable or immutable, but require the caller to dothis(might be able to avoid it)".Agreed. But it means you agree with me that immutable should indeed appear on function signatures as needed.Looking it up (in zlib.d actually) it could just as well return an immutable array, since it is freshly allocated.I don't know if these are good examples, but they are examples: From zip.d: const(void)[] compress(const(void)[] buf)For more guidelines: 3. Return value, which are freshly created, should never beconst. Theyshould be mutable or immutable.Agreed. I say that it should be mutable if the function is pure, as such a return values is automatically convertible to immutable.Basically, I cannot come up with a case, where const would bepreferableto mutable and immutable. Maybe someone is able to find agood example?From vibe: const(Json) opIndex(string key) const {Not sure where exactly this comes from. Seems to be some kind of map? In this case the value is not "freshly created", so the guideline does not apply.I'm confused by the term "usable as mutable, const, immutable"? Isn't it true that types are not mutable, const, immutable in their definition but only in their context?MyInt is a type, probably mutable. immutable(MyInt) is also a type, but immutable. const(MyInt) is also a type, but const. These are example of immutable and const being used as type constructors. They create a new type from an existing one (MyInt).
Oct 09 2013
On 09/10/13 17:50, Daniel Davidson wrote:Take, for example, LinearCongruentialEngine from random.d. It has a function: bool opEquals(ref const LinearCongruentialEngine rhs) const Why is it using const here instead of immutable?Among other things, because no LinearCongruentialEngine will ever, ever be immutable -- because it won't work if it's immutable. Whereas ref const I believe is simply saying, "You can't mutate it via this reference." I'm not sure if it ought technically to be auto ref const. In any case, I think the general reason is in order to be agnostic about type qualifications. Try: BigInt a = BigInt(5); immutable BigInt b = immutable(BigInt)(5); writeln(a.opEquals(b)); ... which works. BigInt.opEquals takes input by an auto ref const.
Oct 10 2013
On Wednesday, 2 October 2013 at 13:09:34 UTC, Daniel Davidson wrote:I'm reviewing Ali's insightful presentation from 2013 DConf. I wonder has he or anyone else followed up on the concepts or formalized some guidelines that could achieve consensus. I definitely agree it would be helpful to have a 50 Ways To Improve Your D. The first thing I'd like to see is a set of guidelines on mutability along the lines he discussed in his talk. But as it stands, I don't know if there was any finalization/consensus. For instance, from the first two guidelines at the end (after several iterative reconstructions of those guidelines): 1. If a variable is never mutated, make it const, not immutable. 2. Make the parameter reference to immutable if that is how you will use it anyway. It is fine to ask a favor from the caller. ... If you follow (1) exclusively, why the need for immutable in the language at all? Maybe it is a philosophical question, but where does immutability really come from? Is it an aspect of some piece of data or is it a promise that function will not change it? Or is it a requirement by a function that data passed not be changed by anyone else? The two keywords cover all in some sense. I found the end of the video amusing, when one gentleman looking at a rather sophisticated "canonical" struct with three overloads for 'this(...)' and two overloads for opAssign, asked if all those methods were required. I think there was back and forth and head-scratching. Another asked if the language designers were happy with the resultant complexity. Naturally the answer was yes - it is a good mix. If that is the case I wonder if the reason is they don't write software in D like Ali was looking to develop. I imagine standard library code is much more functional than OO by its very nature. My money says you will not find a struct S constructed like Ali's in the wild - it is just too much boilerplate. But surely they have their own guidelines/approaches. By posting this I am not looking for a single answer to the simple question above as it is just one of many questions in the set of "how do you choose among the options in general". If you have such guidelines - please post them. Thanks DanAfter trying for several days to use immutable with types containing some mutable aliasing I have come to the conclusion that maybe rule number one should be: If you have a type that has now or may ever have in the future any mutable aliasing (e.g. inclusion of T[] or T1[T1] where Ts are mutable) do not ever use the immutable keyword in any context as things just break down. If you have had more success with a immutable with types containing mutable aliasing and can share your success story that would be great. As D is growing are there any out there yet offering support services? Thanks, Dan
Oct 16 2013
On 10/16/2013 10:23 AM, Daniel Davidson wrote:On Wednesday, 2 October 2013 at 13:09:34 UTC, Daniel Davidson wrote:guidelines): 1. If a variable is never mutated, make it const, not immutable. 2. Make the parameter reference to immutable if that is how you will use it anyway. It is fine to ask a favor from the caller.After trying for several days to use immutable with types containing some mutable aliasing I have come to the conclusion that maybe rule number one should be: If you have a type that has now or may ever have in the future any mutable aliasing (e.g. inclusion of T[] or T1[T1] where Ts are mutable) do not ever use the immutable keyword in any context as things just break down.I think this topic should be carried to the main newsgroup already. I am convinced that this is a language issue. :-/Thanks, DanAli
Oct 16 2013
On Wed, Oct 16, 2013 at 07:23:24PM +0200, Daniel Davidson wrote:On Wednesday, 2 October 2013 at 13:09:34 UTC, Daniel Davidson wrote:[...]I think it helps to realize that D's const system is different from C++'s. Immutable means the data will never change, ever. It means you can put that data in read-only memory, maybe burned into a ROM chip or something like that. Const means *you* can't change the data, but somebody else may be able to. Therefore: [...]Maybe it is a philosophical question, but where does immutability really come from? Is it an aspect of some piece of data or is it a promise that function will not change it? Or is it a requirement by a function that data passed not be changed by anyone else?After trying for several days to use immutable with types containing some mutable aliasing I have come to the conclusion that maybe rule number one should be:If you have mutable aliasing, that means the data cannot be immutable. Use const.If you have a type that has now or may ever have in the future any mutable aliasing (e.g. inclusion of T[] or T1[T1] where Ts are mutable) do not ever use the immutable keyword in any context as things just break down.Yes, because immutable means nothing, no one, can change the data after it's constructed, ever. If you want mutable aliasing, what you want is const, not immutable.If you have had more success with a immutable with types containing mutable aliasing and can share your success story that would be great.[...] Maybe it's helpful to understand how D's const system works. The following diagram may help (please excuse the ASCII graphics): const / \ mutable immutable What this means is that const subsumes mutable and immutable. A mutable type can be implicitly converted to a const type (the receiver of the const can't modify the data, which is fine since the code holding the mutable reference can still mutate it), and so can immutable (immutable cannot be modified, ever, and const doesn't let you modify it either, so it's OK to make a const reference to immutable data). However, you cannot implicitly convert between mutable and immutable, unless you're copying the data by value. So if you have immutable data and want to make changes, you have to first make a copy of the data, then mutate it at will. What's the use of immutable, you ask? Immutable makes hard guarantees about the non-changeability of some piece of data. This makes it useful for implementing strings -- in fact, the 'string' type in D is just an alias for immutable(char)[]. You can take substrings (slices) of any given string freely, and be assured that your copy of the (sub)string will never unexpectedly change its value from somewhere else in the code. This saves the need for a lot of copying, which can be costly. One interesting subtlety here is that 'string' is immutable(char)[], but not immutable(char[]). The latter would mean that the string itself can never be changed -- you couldn't assign to it, you couldn't append to it, etc., which would make strings a lot less useful than they are. But by making strings a *mutable* array of *immutable* chars, you allow the string to be appended to, substring'd, etc., all while guaranteeing that the underlying bytes themselves will never change. So you can have the best of both worlds: you can append to strings, take substrings, assign strings to each other, etc., yet at the same time be assured that the list of intermediate substrings you stored somewhere during the process will continue to retain the values you assigned to them, because the underlying bytes they point to are immutable, and therefore guaranteed never to change. T -- Just because you survived after you did it, doesn't mean it wasn't stupid!
Oct 16 2013
On Wednesday, 16 October 2013 at 17:55:14 UTC, H. S. Teoh wrote:On Wed, Oct 16, 2013 at 07:23:24PM +0200, Daniel Davidson wrote:I think the term "mutable aliasing" and "what you want" don't work together. "mutable aliasing" is not about context of usage or what you want, it is a compile time attribute of a struct. I want to use associative arrays in composition because I think they are the way I view and use my data. `struct T { string[string] i; }` has mutable aliasing, like it or not. So, it is not so much that I want mutable aliasing - in fact I fear it. But once you use AAs it is a fact of life.On Wednesday, 2 October 2013 at 13:09:34 UTC, Daniel Davidson wrote:[...]I think it helps to realize that D's const system is different from C++'s. Immutable means the data will never change, ever. It means you can put that data in read-only memory, maybe burned into a ROM chip or something like that. Const means *you* can't change the data, but somebody else may be able to. Therefore: [...]Maybe it is a philosophical question, but where does immutability really come from? Is it an aspect of some piece of data or is it a promise that function will not change it? Or is it a requirement by a function that data passed not be changed by anyone else?After trying for several days to use immutable with types containing some mutable aliasing I have come to the conclusion that maybe rule number one should be:If you have mutable aliasing, that means the data cannot be immutable. Use const.If you have a type that has now or may ever have in the future any mutable aliasing (e.g. inclusion of T[] or T1[T1] where Ts are mutable) do not ever use the immutable keyword in any context as things just break down.Yes, because immutable means nothing, no one, can change the data after it's constructed, ever. If you want mutable aliasing, what you want is const, not immutable.yes - it requires transitive deep copy.If you have had more success with a immutable with types containing mutable aliasing and can share your success story that would be great.[...] Maybe it's helpful to understand how D's const system works. The following diagram may help (please excuse the ASCII graphics): const / \ mutable immutable What this means is that const subsumes mutable and immutable. A mutable type can be implicitly converted to a const type (the receiver of the const can't modify the data, which is fine since the code holding the mutable reference can still mutate it), and so can immutable (immutable cannot be modified, ever, and const doesn't let you modify it either, so it's OK to make a const reference to immutable data). However, you cannot implicitly convert between mutable and immutable, unless you're copying the data by value.So if you have immutable data and want to make changes, you have to first make a copy of the data, then mutate it at will. What's the use of immutable, you ask? Immutable makes hard guarantees about the non-changeability of some piece of data. This makes it useful for implementing strings -- in fact, the 'string' type in D is just an alias for immutable(char)[]. You can take substrings (slices) of any given string freely, and be assured that your copy of the (sub)string will never unexpectedly change its value from somewhere else in the code. This saves the need for a lot of copying, which can be costly.I agree that string behaves as you say. I don't quite agree that immutable alone is the reason it does. I think it is an byproduct of the way immutable(T)[] is implemented. It is an implementation detail and relying on that could lead to bad deduction. We covered that here in this thread: http://forum.dlang.org/post/jfjudswamyxlttgsdwva forum.dlang.orgOne interesting subtlety here is that 'string' is immutable(char)[], but not immutable(char[]). The latter would mean that the string itself can never be changed -- you couldn't assign to it, you couldn't append to it, etc., which would make strings a lot less useful than they are. But by making strings a *mutable* array of *immutable* chars, you allow the string to be appended to, substring'd, etc., all while guaranteeing that the underlying bytes themselves will never change. So you can have the best of both worlds: you can append to strings, take substrings, assign strings to each other, etc., yet at the same time be assured that the list of intermediate substrings you stored somewhere during the process will continue to retain the values you assigned to them, because the underlying bytes they point to are immutable, and therefore guaranteed never to change. TAgreed with the description of the behavior. But disagree on why. It works that way because T[] is modeled as contiguous memory and the api associated with slice of type immutable(T)[] means there is no unsafe sharing. So, `struct T { string[string] i; }` and `struct T { immutable(S)[string] }` do not have the same properties because they have a different layout model. Is the suggestion here: use immutable in any composition context where you have slices and you want to not "worry about mutable aliasing". So, do like string does: any case where you have T[] as a member, prefer immutable(T)[] since then you don't have to worry about sharing. Well, not sure that works in general because the T in string is char and has no mutable aliasing itself. Suppose T itself has mutable aliasing, then what? Or, suppose it is a struct with no mutable aliasing *now*. Who's to say it won't change. So, where does all this leave us w.r.t. a good set of guidelines?
Oct 16 2013
On Wed, Oct 16, 2013 at 08:49:51PM +0200, Daniel Davidson wrote:On Wednesday, 16 October 2013 at 17:55:14 UTC, H. S. Teoh wrote:[...]On Wed, Oct 16, 2013 at 07:23:24PM +0200, Daniel Davidson wrote:[...]I think the term "mutable aliasing" and "what you want" don't work together. "mutable aliasing" is not about context of usage or what you want, it is a compile time attribute of a struct. I want to use associative arrays in composition because I think they are the way I view and use my data. `struct T { string[string] i; }` has mutable aliasing, like it or not. So, it is not so much that I want mutable aliasing - in fact I fear it. But once you use AAs it is a fact of life.If you have a type that has now or may ever have in the future any mutable aliasing (e.g. inclusion of T[] or T1[T1] where Ts are mutable) do not ever use the immutable keyword in any context as things just break down.Yes, because immutable means nothing, no one, can change the data after it's constructed, ever. If you want mutable aliasing, what you want is const, not immutable.Is the suggestion here: use immutable in any composition context where you have slices and you want to not "worry about mutable aliasing". So, do like string does: any case where you have T[] as a member, prefer immutable(T)[] since then you don't have to worry about sharing. Well, not sure that works in general because the T in string is char and has no mutable aliasing itself. Suppose T itself has mutable aliasing, then what? Or, suppose it is a struct with no mutable aliasing *now*. Who's to say it won't change. So, where does all this leave us w.r.t. a good set of guidelines?Sorry, I kinda barged into this conversation, so I'm not sure what exactly you're trying to achieve here. What kind of composition contexts do you have in mind? Maybe you could help me understand what you're trying to do? Keeping in mind that AA's, as they are currently implemented, leaves a lot of room for improvement, to say the least. So you might be running into some AA-related issues, not the type system proper. T -- Creativity is not an excuse for sloppiness.
Oct 16 2013
On Wednesday, 16 October 2013 at 19:01:59 UTC, H. S. Teoh wrote:On Wed, Oct 16, 2013 at 08:49:51PM +0200, Daniel Davidson wrote:Thanks. I appreciate any help I can get. Here is a small sample: http://pastebin.com/TeiQ9DYa Other samples have at least 5 levels of composition. I'm generating the structure from json schema so I'm looking for something that scales and is immune to changes in deeply nested members.On Wednesday, 16 October 2013 at 17:55:14 UTC, H. S. Teoh wrote:[...]On Wed, Oct 16, 2013 at 07:23:24PM +0200, Daniel Davidson wrote:[...]I think the term "mutable aliasing" and "what you want" don't work together. "mutable aliasing" is not about context of usage or what you want, it is a compile time attribute of a struct. I want to use associative arrays in composition because I think they are the way I view and use my data. `struct T { string[string] i; }` has mutable aliasing, like it or not. So, it is not so much that I want mutable aliasing - in fact I fear it. But once you use AAs it is a fact of life.If you have a type that has now or may ever have in the future any mutable aliasing (e.g. inclusion of T[] or T1[T1] where Ts are mutable) do not ever use the immutable keyword in any context as things just break down.Yes, because immutable means nothing, no one, can change the data after it's constructed, ever. If you want mutable aliasing, what you want is const, not immutable.Is the suggestion here: use immutable in any composition context where you have slices and you want to not "worry about mutable aliasing". So, do like string does: any case where you have T[] as a member, prefer immutable(T)[] since then you don't have to worry about sharing. Well, not sure that works in general because the T in string is char and has no mutable aliasing itself. Suppose T itself has mutable aliasing, then what? Or, suppose it is a struct with no mutable aliasing *now*. Who's to say it won't change. So, where does all this leave us w.r.t. a good set of guidelines?Sorry, I kinda barged into this conversation, so I'm not sure what exactly you're trying to achieve here. What kind of composition contexts do you have in mind? Maybe you could help me understand what you're trying to do?Keeping in mind that AA's, as they are currently implemented, leaves a lot of room for improvement, to say the least. So you might be running into some AA-related issues, not the type system proper.I understand. It is a shame. If the data source is rich json data - I think AAs are a necessity.
Oct 16 2013
On Wednesday, 16 October 2013 at 17:55:14 UTC, H. S. Teoh wrote:Maybe it's helpful to understand how D's const system works. The following diagram may help (please excuse the ASCII graphics): const / \ mutable immutableI think people in this thread know how const works, but some think it is broken. Scenario is this: Library code: struct Foo { int x; } User code: Foo f; immutable f2 = f; This works, even though the library writer might not have anticipated that someone makes Foo immutable. However, now the library writer obliviously releases a new version of the library, which extends it like this: struct Foo { int x; private int[] history; } Unfortunately, now the user code is broken due to the freshly introduced mutable aliasing. Personally, I think is fine. Upon compilation the user code gives a error message and user developer can adapt to the code to the new library version. Some think the library writer should have a possibility to make this work.
Oct 16 2013
On Wednesday, 16 October 2013 at 18:52:23 UTC, qznc wrote:On Wednesday, 16 October 2013 at 17:55:14 UTC, H. S. Teoh wrote:Thanks.Maybe it's helpful to understand how D's const system works. The following diagram may help (please excuse the ASCII graphics): const / \ mutable immutableI think people in this thread know how const works, but some think it is broken. Scenario is this:Library code: struct Foo { int x; } User code: Foo f; immutable f2 = f; This works, even though the library writer might not have anticipated that someone makes Foo immutable. However, now the library writer obliviously releases a new version of the library, which extends it like this: struct Foo { int x; private int[] history; } Unfortunately, now the user code is broken due to the freshly introduced mutable aliasing. Personally, I think is fine. Upon compilation the user code gives a error message and user developer can adapt to the code to the new library version. Some think the library writer should have a possibility to make this work.I don't understand how it could be fine. As code grows it would lead to people not adding useful members like history just because of the huge repercussions. struct User { immutable(Foo) foos; } How can I as a user adapt to that change? Before the change assignment worked equally well among all of Mutable, Immutable, Const. After that change any `foos ~= createFoo(...)` would require change. And it is not clear what the change would be.
Oct 16 2013
On Wednesday, 16 October 2013 at 19:06:06 UTC, Daniel Davidson wrote:I don't understand how it could be fine. As code grows it would lead to people not adding useful members like history just because of the huge repercussions. struct User { immutable(Foo) foos; } How can I as a user adapt to that change? Before the change assignment worked equally well among all of Mutable, Immutable, Const. After that change any `foos ~= createFoo(...)` would require change. And it is not clear what the change would be.I think any usage of immutable with types/entities not initially designed for immutability is an potential mistake and in that sense it is good that change has broken the user code. Same goes for operating on immutable entity in generic code as if it is a value type without actually checking it via introspection.
Oct 16 2013
On Wednesday, 16 October 2013 at 19:12:48 UTC, Dicebot wrote:On Wednesday, 16 October 2013 at 19:06:06 UTC, Daniel Davidson wrote:I don't disagree. Now, what does it mean to "initially design for immutability" and what are the guidelines. I thought that was kind of what we were talking about here. How to effectively use const and immutable. If the rule is - don't use immutable unless you have immutability in mind for your design, ok. Maybe take it a step further... How about this - show a good use of immutable in any context where mutable aliasing (e.g. AAs) are in the mix. If the response is there are none, it is just too hairy of a proposition then something is wrong. One general idea that was presented by Ali is that an immutable parameter means that not only are you guaranteeing that you will not change your data, but also that no one else, even in another thread will. That sounds appealing. After all, who doesn't want the data they are using in a function to not change from underneath them? Suppose you have highly structured, deeply nested reference data you read from a nosql DB. Surely there is opportunity and some benefit to use immutable? The result of the query is just a read only data source. But then that data will be consumed - presumably by functions with either immutable or const parameters. If all signatures use const then when can you benefit from the fact that the data is really immutable (by the time it gets to a function with const parm the fact that it really was/is immutable is lost.I don't understand how it could be fine. As code grows it would lead to people not adding useful members like history just because of the huge repercussions. struct User { immutable(Foo) foos; } How can I as a user adapt to that change? Before the change assignment worked equally well among all of Mutable, Immutable, Const. After that change any `foos ~= createFoo(...)` would require change. And it is not clear what the change would be.I think any usage of immutable with types/entities not initially designed for immutability is an potential mistake and in that sense it is good that change has broken the user code. Same goes for operating on immutable entity in generic code as if it is a value type without actually checking it via introspection.
Oct 16 2013
On Wed, Oct 16, 2013 at 09:45:09PM +0200, Daniel Davidson wrote:On Wednesday, 16 October 2013 at 19:12:48 UTC, Dicebot wrote:[...]I'd say that user code should use const, not immutable (unless the library provides a way of constructing immutable instances of the type, of course), because immutable makes assumptions about implementation details, which should not be known unless you break encapsulation. As a user of a properly encapsulated type, I can't make any guarantees about its immutability; the best I can do is to promise I don't change it myself -- i.e., const. Immutable should be used in library code to provide strong guarantees to the user: since you're the one responsible for implementing the type, you're in the position to make guarantees about its uniqueness (and hence, immutability). [...]I think any usage of immutable with types/entities not initially designed for immutability is an potential mistake and in that sense it is good that change has broken the user code. Same goes for operating on immutable entity in generic code as if it is a value type without actually checking it via introspection.I don't disagree. Now, what does it mean to "initially design for immutability" and what are the guidelines. I thought that was kind of what we were talking about here. How to effectively use const and immutable.One general idea that was presented by Ali is that an immutable parameter means that not only are you guaranteeing that you will not change your data, but also that no one else, even in another thread will. That sounds appealing. After all, who doesn't want the data they are using in a function to not change from underneath them?I'm actually wary of this view, to be honest. An immutable parameter means you expect your *caller* to provide you with a value that cannot be changed, not by you, nor by anybody else, ever. Sure, it's nice to have, but that imposes a rather high bar on your callers. They have to be responsible to guarantee that whatever they hand to you cannot be changed by anything or anyone else, at any time. If they can do this, then great; if not, they won't be able to call your function.Suppose you have highly structured, deeply nested reference data you read from a nosql DB. Surely there is opportunity and some benefit to use immutable? The result of the query is just a read only data source. But then that data will be consumed - presumably by functions with either immutable or const parameters. If all signatures use const then when can you benefit from the fact that the data is really immutable (by the time it gets to a function with const parm the fact that it really was/is immutable is lost.I'm of the view that code should only require the minimum of assumptions it needs to actually work. If your code can work with mutable types, then let it take a mutable (unqualified) type. If your code works without modifying input data, then let it take const. Only if your code absolutely will not work correctly unless the data is guaranteed to never change, ever, by anyone, should it take immutable. I'm not sure what "benefits" you get from requiring immutable when the code doesn't really need to depend on immutability. It just makes the code harder to use (you have to make sure whatever you pass to it is immutable, and sometimes that's not easy to guarantee, and would require a lot of copying). Immutable only benefits you when the code *can't* work correctly unless the data is guaranteed never to change, ever. Hash table keys come to mind -- if you compute the hash value of the key and use that to determine which slot to put the data into, it would be very bad if somebody else mutated that key via a mutable reference after the fact -- now your AA is broken because the hash value no longer matches the key. By requiring an immutable key, you ensure that this never happens. Note that in D, everything is thread-local by default unless explicitly made shared, so using immutable to guarantee other threads won't mutate the data isn't really necessary. T -- What do you mean the Internet isn't filled with subliminal messages? What about all those buttons marked "submit"??
Oct 16 2013
On Wednesday, 16 October 2013 at 20:33:23 UTC, H. S. Teoh wrote:I'm of the view that code should only require the minimum of assumptions it needs to actually work. If your code can work with mutable types, then let it take a mutable (unqualified) type. If your code works without modifying input data, then let it take const. Only if your code absolutely will not work correctly unless the data is guaranteed to never change, ever, by anyone, should it take immutable.Sounds reasonable. Which one of the following would you prefer? foo1(const T t) { takes_immutable(t.idup); } foo2(immutable(T) t) { takes_immutable(t); } I'd say foo2, which puts the burden of the copy on the caller. However, if the caller already has an immutable value an unecessary copy is avoided.
Oct 16 2013
On Thu, Oct 17, 2013 at 08:56:08AM +0200, qznc wrote:On Wednesday, 16 October 2013 at 20:33:23 UTC, H. S. Teoh wrote:Well, that's a bit too generic to really decide one way or the other. What do foo1 and foo2 do? I don't think it's fair to say one or the other is superior without more context. It depends on what you're trying to achieve. T -- Too many people have open minds but closed eyes.I'm of the view that code should only require the minimum of assumptions it needs to actually work. If your code can work with mutable types, then let it take a mutable (unqualified) type. If your code works without modifying input data, then let it take const. Only if your code absolutely will not work correctly unless the data is guaranteed to never change, ever, by anyone, should it take immutable.Sounds reasonable. Which one of the following would you prefer? foo1(const T t) { takes_immutable(t.idup); } foo2(immutable(T) t) { takes_immutable(t); } I'd say foo2, which puts the burden of the copy on the caller. However, if the caller already has an immutable value an unecessary copy is avoided.
Oct 17 2013
On Wednesday, 16 October 2013 at 20:33:23 UTC, H. S. Teoh wrote:I'm of the view that code should only require the minimum of assumptions it needs to actually work. If your code can work with mutable types, then let it take a mutable (unqualified) type. If your code works without modifying input data, then let it take const. Only if your code absolutely will not work correctly unless the data is guaranteed to never change, ever, by anyone, should it take immutable.What about functions, which are not thread-safe? auto percentageDistribution(const int[] percentages) { [...] assert (sum == 100); [...] } Not a realistic example, but it should do. This function checks that the sum of all percentages equals 100, but a concurrent modification might break this. You could require an immutable array to avoid that. Alternatively, the absence of "shared" could be considered enough to express this.
Oct 17 2013
On Thu, Oct 17, 2013 at 09:08:16AM +0200, qznc wrote:On Wednesday, 16 October 2013 at 20:33:23 UTC, H. S. Teoh wrote:In D, variables are thread-local by default, and only shared if explicitly stated so. If this function took shared(const(int)[]), or you pass it something that's __gshared, then you might have a point. But in normal D code I'd recommend against doing that. Currently, D has some issues in the area of shared, requiring a cast to strip shared from the type. So basically the onus is on the caller to ensure that when shared is stripped from the type it's actually safe from concurrent modification. In which case the above code should be OK as-is. T -- Guns don't kill people. Bullets do.I'm of the view that code should only require the minimum of assumptions it needs to actually work. If your code can work with mutable types, then let it take a mutable (unqualified) type. If your code works without modifying input data, then let it take const. Only if your code absolutely will not work correctly unless the data is guaranteed to never change, ever, by anyone, should it take immutable.What about functions, which are not thread-safe? auto percentageDistribution(const int[] percentages) { [...] assert (sum == 100); [...] } Not a realistic example, but it should do. This function checks that the sum of all percentages equals 100, but a concurrent modification might break this. You could require an immutable array to avoid that. Alternatively, the absence of "shared" could be considered enough to express this.
Oct 17 2013
On Wednesday, 16 October 2013 at 20:33:23 UTC, H. S. Teoh wrote:On Wed, Oct 16, 2013 at 09:45:09PM +0200, Daniel Davidson wrote:Thanks - this is most helpful.On Wednesday, 16 October 2013 at 19:12:48 UTC, Dicebot wrote:[...]I think any usage of immutable with types/entities not initially designed for immutability is an potential mistake and in that sense it is good that change has broken the user code. Same goes for operating on immutable entity in generic code as if it is a value type without actually checking it via introspection.I don't disagree. Now, what does it mean to "initially design for immutability" and what are the guidelines. I thought that was kind of what we were talking about here. How to effectively use const and immutable.I'd say that user code should use const, not immutable (unless the library provides a way of constructing immutable instances of the type, of course), because immutable makes assumptions about implementation details, which should not be known unless you break encapsulation. As a user of a properly encapsulated type, I can't make any guarantees about its immutability; the best I can do is to promise I don't change it myself -- i.e., const.The distinction between user code and the rest (non-user code?) is not enough for me to get clear guidance. I am writing user code, the vast majority will be in libraries because I find that helpful. I would hate to have a separate set of rules for my code and the code I use. We should strive for guidelines that work in general - as in this is a good way to do D. But leaving "user code" aside, I like the argument and the vote for const over immutable.Immutable should be used in library code to provide strong guarantees to the user: since you're the one responsible for implementing the type, you're in the position to make guarantees about its uniqueness (and hence, immutability).I don't see the benefit of separating usage of types, usually via functions where mutability guarantees adorn the types, and implementation guarantees in a world with turtles all the way down. Maybe if those guarantees are totally hidden from user code via encapsulation then the impact of immutable and the difficulties of dealing with it are lessened. But then is the immutable guarantee for the user or just the developer of the encapsulated non-user code? How about this... here is a plea for ideas on a good use of immutable for a type with potentially mutable aliasing. String I think is a great use of immutable but char will likely never have aliasing introduced to it. At this stage I want the information for educational purposes, because based on this thread and my experimentation - Ali's presentation guidelines aside - I am about to abandon immutable altogether. The fight is too hard and my skills too weakened.[...]I now agree with you on this, especially since it goes with my new guideline of don't use immutable.One general idea that was presented by Ali is that an immutable parameter means that not only are you guaranteeing that you will not change your data, but also that no one else, even in another thread will. That sounds appealing. After all, who doesn't want the data they are using in a function to not change from underneath them?I'm actually wary of this view, to be honest. An immutable parameter means you expect your *caller* to provide you with a value that cannot be changed, not by you, nor by anybody else, ever. Sure, it's nice to have, but that imposes a rather high bar on your callers. They have to be responsible to guarantee that whatever they hand to you cannot be changed by anything or anyone else, at any time. If they can do this, then great; if not, they won't be able to call your function.I don't have the instincts yet to really buy this. Perhaps a specific example would be helpful. I think most of the time `foo(ref const(T) t)` is written such that it is assumed t is never changed during the span of foo and the compiler helps. While it is possible to, in a single thread call out to code that also has a handle to shared state so t could be accidentally or purposely modified elsewhere, it is probably a rare design goal. Take the query example. A big mongo db query result comes back and is deserialized into a large web of json like data - lists, dictionaries at many levels. Is that a good reason then for `foo(ref immutable(QueryResult) qr)`. Surely while you are working on query result you don't want it to change. I am ambivalent here because immutable sounds appealing. Any iteration over a hash in the data set must be immutable. I think this is the expectation most have with all const or immutable types taken in a function. Sticking with const just makes life easier.Suppose you have highly structured, deeply nested reference data you read from a nosql DB. Surely there is opportunity and some benefit to use immutable? The result of the query is just a read only data source. But then that data will be consumed - presumably by functions with either immutable or const parameters. If all signatures use const then when can you benefit from the fact that the data is really immutable (by the time it gets to a function with const parm the fact that it really was/is immutable is lost.I'm of the view that code should only require the minimum of assumptions it needs to actually work. If your code can work with mutable types, then let it take a mutable (unqualified) type. If your code works without modifying input data, then let it take const. Only if your code absolutely will not work correctly unless the data is guaranteed to never change, ever, by anyone, should it take immutable.I'm not sure what "benefits" you get from requiring immutable when the code doesn't really need to depend on immutability. It just makes the code harder to use (you have to make sure whatever you pass to it is immutable, and sometimes that's not easy to guarantee, and would require a lot of copying). Immutable only benefits you when the code *can't* work correctly unless the data is guaranteed never to change, ever. Hash table keys come to mind -- if you compute the hash value of the key and use that to determine which slot to put the data into, it would be very bad if somebody else mutated that key via a mutable reference after the fact -- now your AA is broken because the hash value no longer matches the key. By requiring an immutable key, you ensure that this never happens.I like the AA example - since it sounds like a good use for immutable.Note that in D, everything is thread-local by default unless explicitly made shared, so using immutable to guarantee other threads won't mutate the data isn't really necessary. T
Oct 17 2013
On Wed, Oct 16, 2013 at 09:06:05PM +0200, Daniel Davidson wrote:On Wednesday, 16 October 2013 at 18:52:23 UTC, qznc wrote:[...]The root of the problem is reliance on assignment between mutable / immutable / const. This reliance breaks encapsulation because you're making an assumption about the assignability of a presumedly opaque library type to immutable / const. In D, immutable is *physical* immutability, not logical immutability; by writing immutable(Foo) you're saying that you wish to have physically-immutable instances of Foo. However, whether this is possible depends on the implementation details of Foo, which, if Foo is supposed to be an opaque type, breaks encapsulation. Without knowing how Foo is implemented (and user code shouldn't know that), you can't reliably go around and claim Foo can be made immutable from a mutable instance. The fact that you're relying on Foo being implicitly convertible to immutable(Foo) means you're already depending on implementation details of Foo, and should be prepared to change code when Foo's implementation changes. If you want to say that User cannot modify the Foo's it contains, you should use const rather than immutable. It is safe to use const because anything is implicitly convertible to const, so it doesn't introduce any reliance upon implementational details of Foo. If you insist on being able to append to immutable(Foo)[], then you'll need a createFoo method that returns immutable instances of Foo: struct User { immutable(Foo)[] foos; } immutable(Foo) createFoo(...) { ... } User u; u.foos ~= createFoo(...); // now this works The problem with this, of course, is that it unnecessarily restricts createFoo(): if you want *mutable* instances of Foo, then you can't use this version of createFoo(), but have to create another function that probably does exactly the same thing. So an alternative solution is to use Phobos' assumeUnique template: struct User { immutable(Foo)[] foos; } Foo createFoo(...) { ... } User u; u.foos ~= assumeUnique(createFoo(...)); The assumeUnique template basically does a cast from mutable to immutable, but explicitly documents the purpose of this cast in the code. It places the onus on the user to ensure that the Foo returned by createFoo is actually unique. If not, you break the type system and the immutability guarantee may no longer hold. To illustrate why adding mutable aliases to Foo *should* break code, consider this: /* This is what Foo looked like before: struct OriginalFoo { int x; } */ /* This is what Foo looks like now */ struct Foo { int x; private int[] history; void changeHistory() { history[0]++; } } Foo createFoo(int x) { Foo f; f.x = x; f.history = [1]; } Foo f = createFoo(); immutable(Foo) g = f; // doesn't compile, but suppose it does f.changeHistory(); // oops, g.history has mutated, so it's // *not* immutable after all That's why assigning f to g must be made illegal, since it breaks immutability guarantees. OTOH, if you absolutely have to do it, you can document your intent thus: Foo f = createFoo(); immutable(Foo) g = assumeUnique(f); // Now if you use f to mutate g, it's your own problem: you // claimed that g was unique but actually it isn't. So it's your // own fault when your supposedly-immutable Foo mutates. // If you *don't* do stupid things, OTOH, this lets your code // continue to work when the library writer decides to change // Foo's implementation to contain mutable aliases. T -- Do not reason with the unreasonable; you lose by definition.Library code: struct Foo { int x; } User code: Foo f; immutable f2 = f; This works, even though the library writer might not have anticipated that someone makes Foo immutable. However, now the library writer obliviously releases a new version of the library, which extends it like this: struct Foo { int x; private int[] history; } Unfortunately, now the user code is broken due to the freshly introduced mutable aliasing. Personally, I think is fine. Upon compilation the user code gives a error message and user developer can adapt to the code to the new library version. Some think the library writer should have a possibility to make this work.I don't understand how it could be fine. As code grows it would lead to people not adding useful members like history just because of the huge repercussions. struct User { immutable(Foo) foos; } How can I as a user adapt to that change? Before the change assignment worked equally well among all of Mutable, Immutable, Const. After that change any `foos ~= createFoo(...)` would require change. And it is not clear what the change would be.
Oct 16 2013
On Wed, Oct 16, 2013 at 08:52:22PM +0200, qznc wrote:On Wednesday, 16 October 2013 at 17:55:14 UTC, H. S. Teoh wrote:Well, I think here the type system is working as advertised. Since the original version of Foo has no mutable aliasing, implicit conversion to immutable is OK (you're making a new binary copy of the data). But in the second case, it will obviously violate immutability guarantees, because in D, immutable is transitive, so immutable(Foo) in the second case is equivalent to: struct Foo { int x; private immutable(int[]) history; } You can't implicitly convert int[] to immutable(int[]) because whoever holds the original int[] reference can use it to change the data, which breaks the immutable guarantee of immutable(int[]). The only way this could work is if you made a copy of the int[]. So the library writer would have to provide a method for creating an immutable copy of the struct (like an .idup method or something). You may argue that this is bad because now user code is broken and you need to rewrite it to use .idup, but I'd argue that relying on implicit conversion to immutable already introduces a dependency on implementation details of Foo, which should be avoided in the first place if you want to have a clean encapsulation. I mean, given a Foo type exported by a library, if that type is meant to be an opaque type, then my code should make no assumptions about whether it can implicitly convert to immutable. I should rather require the library writer to provide an .idup method for creating an immutable instance of the struct than to blindly write the code to implicitly convert to immutable (thus introducing a hidden reliance on how Foo is implemented) and then have it broken later when the library writer changes its implementation. OTOH, if it's not an opaque type, then it's no surprise that changing its implementation should also require changing the code (that depends on its implementation details). So I don't see anything wrong with this particular scenario. What other scenarios were being considered? T -- He who does not appreciate the beauty of language is not worthy to bemoan its flaws.Maybe it's helpful to understand how D's const system works. The following diagram may help (please excuse the ASCII graphics): const / \ mutable immutableI think people in this thread know how const works, but some think it is broken. Scenario is this: Library code: struct Foo { int x; } User code: Foo f; immutable f2 = f; This works, even though the library writer might not have anticipated that someone makes Foo immutable. However, now the library writer obliviously releases a new version of the library, which extends it like this: struct Foo { int x; private int[] history; } Unfortunately, now the user code is broken due to the freshly introduced mutable aliasing. Personally, I think is fine. Upon compilation the user code gives a error message and user developer can adapt to the code to the new library version. Some think the library writer should have a possibility to make this work.
Oct 16 2013