digitalmars.D - head const (again), but for free?
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (17/17) Jan 11 2021 So, I find that I increasingly miss head-const for immutable. And
- Q. Schroll (41/49) Jan 11 2021 I tried implementing std.experimental.Final properly for structs.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (25/39) Jan 11 2021 So, I disagree strongly with this. Head immutable is basically
- Imperatorn (4/10) Jan 13 2021 When I see readonly I think of this:
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (4/6) Jan 13 2021 Yes, it is a common term, Typescript also uses it:
- sighoya (8/11) Jan 13 2021 This is an idiomatic break to the meaning of immutability which
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/17) Jan 13 2021 No, readonly would be one-level immutable. Basically the same as
- sighoya (6/11) Jan 13 2021 Yep, head const is usually readonly or better final, readonly
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (8/13) Jan 13 2021 Why? "immutable" is transitive in D. readonly memory or registers
- Meta (7/20) Jan 13 2021 Is there even any value to having head-const in a language? As I
- sighoya (6/8) Jan 13 2021 I think they are both worthwhile and if we provide one of them we
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (7/12) Jan 13 2021 Well, yes, but in the case of C++ there are no guarantees, so it
- jmh530 (3/9) Jan 13 2021 I don't really understand the difference between head-const and
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (16/18) Jan 13 2021 Const allows the content to change through another reference.
- Q. Schroll (31/41) Jan 13 2021 Do you understand the difference between const and immutable (the
- jmh530 (4/9) Jan 13 2021 Should that last head_immutable be &q instead of &p?
- Q. Schroll (2/11) Jan 13 2021 Yes.
- sighoya (28/41) Jan 13 2021 The difference is readonly memory didn't know about the type of
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (17/35) Jan 13 2021 Not sure what you mean? In functional languages _everything_ is
So, I find that I increasingly miss head-const for immutable. And I understand that people don't want yet another way to modify the access patterns for a type, but maybe it is possible to avoid that, and do both. So here is the idea: Introduce "readonly" as head-immutable. That means that all values in an immutable array are immutable, including the address of reference, but not the objects being pointed to. Then let "immutable" types be redefined to be a recursion of "readonly applications! When you test a type for "immutable" the the checker would then not actually test for immutable, but test that the composite type is "readonly" at all levels. Then we have two variants for the price of one! What are the gotchas? (You could probably do the same with const, but I think immutable has some potential performance benefits.)
Jan 11 2021
On Monday, 11 January 2021 at 17:15:39 UTC, Ola Fosheim Grøstad wrote:So, I find that I increasingly miss head-const for immutable. And I understand that people don't want yet another way to modify the access patterns for a type, but maybe it is possible to avoid that, and do both. So here is the idea: Introduce "readonly" as head-immutable. That means that all values in an immutable array are immutable, including the address of reference, but not the objects being pointed to.I tried implementing std.experimental.Final properly for structs. (For arrays, pointers and classes, Final could work well: The semantics of the operations of pointers and arrays are known and classes are reference types.) So, here are my thoughts. Your readonly is near useless if you really want it to mean "head immutable" and not "head const". The value immutable provides is due to being transitive. "Head immutable" is unnecessary restrictive and has low guarantees. For example, a head_immutable(int*)* cannot bind a int** variable, because immutable and mutable are referentially incompatible; head_const(int*)* can bind int** the same way const(int*)* can. For that, I'd suggest `final` as the name. It's already a keyword and that's what it means on the first level. The difference to Java would be that it's not (Java's equivalent of) a storage class, but a type constructor. There would be final(int*)* which looks funny through Java eyes, but I think it could work. One of the problems std.experimental.Final has: Say T is a struct (or union). On a Final!T object, you cannot call mutable or immutable methods, only const methods. But even const methods are too restrictive. If T has indirections, like a `int* ptr` member, mutating *ptrof a Final!T object would be fine. That's something a template cannot express. So, final must also become a function attribute, too. If you go with that DIP, it will probably be rejected because final would have to be implemented and maintained, while its application is very limited. There's probably a reason D went from D1 to D2 trading head const for transitive const. final as a type constructor has only value on a middle part of indirection: 1. final(int**) can be treated as an int** except when referencing. The only gain is that you don't accidentally assign it. 2. final(int)** is the same as const(int)**. 3. Only final(int*)* is actually different from anything the vanilla language provides. What you'd have to argue is, how valuable is it? While final(T*) can easily be replaced by a struct template Ref!T (same goes for T[] and reference types), final(T) is interesting for structs T with indirections.
Jan 11 2021
On Monday, 11 January 2021 at 19:05:13 UTC, Q. Schroll wrote:Your readonly is near useless if you really want it to mean "head immutable" and not "head const". The value immutable provides is due to being transitive. "Head immutable" is unnecessary restrictive and has low guarantees. For example, a head_immutable(int*)* cannot bind a int** variable, because immutable and mutable are referentially incompatible; head_const(int*)* can bind int** the same way const(int*)* can.So, I disagree strongly with this. Head immutable is basically the default mutability of tuples in most languages. It is used a lot. Const is too weak for the optimizer to make good use of. I basically never want transitive immutable. The only exception would be a lookup-table, but I have no problem modelling that manually...For that, I'd suggest `final` as the name. It's already a keyword and that's what it means on the first level.No, final is semantically different. I understand what you mean, but mixing concepts like that is no good it makes a language more hard to read. "readonly" is pretty standard with a very clear and concise meaning. E.g. if you put a pointer in read-only-memory then that means you cannot change the pointer, it does not mean that what is pointed to cannot be changed. So "readonly" would be far more useful than immutable currently is, as it can modell anything immutable can. This also means you can make much more immutable than today which may have optimization benefits.If you go with that DIP, it will probably be rejected because final would have to be implemented and maintained, while its application is very limited. There's probably a reason D went from D1 to D2 trading head const for transitive const.Transitive const is a mistake. Transitive immutable is useful for lookup-structures, but that makes it very limited and also prevents extending such structures with caching mechanisms. The fact that immutable cannot even model read-only-memory (ROM), which is very useful in system level programming, is a sign that it is inadequate.
Jan 11 2021
On Monday, 11 January 2021 at 20:07:48 UTC, Ola Fosheim Grøstad wrote:On Monday, 11 January 2021 at 19:05:13 UTC, Q. Schroll wrote:When I see readonly I think of this: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly[...]So, I disagree strongly with this. Head immutable is basically the default mutability of tuples in most languages. It is used a lot. [...]
Jan 13 2021
On Wednesday, 13 January 2021 at 09:24:36 UTC, Imperatorn wrote:When I see readonly I think of this: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonlyYes, it is a common term, Typescript also uses it: https://www.typescriptlang.org/docs/handbook/classes.html#readonly-modifier https://www.typescriptlang.org/docs/handbook/utility-types.html
Jan 13 2021
On Monday, 11 January 2021 at 17:15:39 UTC, Ola Fosheim Grøstad wrote:Introduce "readonly" as head-immutable. That means that all values in an immutable array are immutable, including the address of reference, but not the objects being pointed to.This is an idiomatic break to the meaning of immutability which is global const, while readonly serves the purpose of being locally const. You can mutate a readonly var outside the readonly scope, this is never possible with immutable, let it be either head, tail or head & tail immutable.
Jan 13 2021
On Wednesday, 13 January 2021 at 15:14:29 UTC, sighoya wrote:On Monday, 11 January 2021 at 17:15:39 UTC, Ola Fosheim Grøstad wrote:No, readonly would be one-level immutable. Basically the same as every other language. The only difference between readonly and D's immutable is that you can have immutable pointers to mutable data. What you are talking of is head const?Introduce "readonly" as head-immutable. That means that all values in an immutable array are immutable, including the address of reference, but not the objects being pointed to.This is an idiomatic break to the meaning of immutability which is global const, while readonly serves the purpose of being locally const. You can mutate a readonly var outside the readonly scope, this is never possible with immutable, let it be either head, tail or head & tail immutable.
Jan 13 2021
On Wednesday, 13 January 2021 at 15:17:52 UTC, Ola Fosheim Grøstad wrote:No, readonly would be one-level immutable. Basically the same as every other language. The only difference between readonly and D's immutable is that you can have immutable pointers to mutable data. What you are talking of is head const?Yep, head const is usually readonly or better final, readonly should be transitive. I just find it confusing to denote readonly as head immutable because readonly in other languages denote head const, that's it.
Jan 13 2021
On Wednesday, 13 January 2021 at 18:37:02 UTC, sighoya wrote:Yep, head const is usually readonly or better final, readonly should be transitive.Why? "immutable" is transitive in D. readonly memory or registers are not transitive.I just find it confusing to denote readonly as head immutable because readonly in other languages denote head const, that's it.No, readonly in technical specifications usually means that it is isn't possible to write to it at all. e.g. readonly hardware registers. They can still point to writable memory. Which languages are you thinking of?
Jan 13 2021
On Wednesday, 13 January 2021 at 19:22:07 UTC, Ola Fosheim Grøstad wrote:On Wednesday, 13 January 2021 at 18:37:02 UTC, sighoya wrote:Is there even any value to having head-const in a language? As I think Walter has said before, it's basically just documentation/convention in C++. I can see the value in head-immutable, in terms of type system guarantees, but not head-const.Yep, head const is usually readonly or better final, readonly should be transitive.Why? "immutable" is transitive in D. readonly memory or registers are not transitive.I just find it confusing to denote readonly as head immutable because readonly in other languages denote head const, that's it.No, readonly in technical specifications usually means that it is isn't possible to write to it at all. e.g. readonly hardware registers. They can still point to writable memory. Which languages are you thinking of?
Jan 13 2021
On Wednesday, 13 January 2021 at 19:42:14 UTC, Meta wrote:I can see the value in head-immutable, in terms of type system guarantees, but not head-const.I think they are both worthwhile and if we provide one of them we should also provide the other combinations. They are already mentioned in the D documentation with comparison to C++. though a weak one.
Jan 13 2021
On Wednesday, 13 January 2021 at 19:42:14 UTC, Meta wrote:Is there even any value to having head-const in a language? As I think Walter has said before, it's basically just documentation/convention in C++. I can see the value in head-immutable, in terms of type system guarantees, but not head-const.Well, yes, but in the case of C++ there are no guarantees, so it is for catching bugs, parametric typing and overloading. I don't think it brings optimization benefits? In the case of head-immutable it allows caching of calculations across FFI calls and assembly code for instance. With transitive immutable much less data can be protected.
Jan 13 2021
On Wednesday, 13 January 2021 at 19:42:14 UTC, Meta wrote:[snip] Is there even any value to having head-const in a language? As I think Walter has said before, it's basically just documentation/convention in C++. I can see the value in head-immutable, in terms of type system guarantees, but not head-const.I don't really understand the difference between head-const and head-immutable.
Jan 13 2021
On Wednesday, 13 January 2021 at 20:21:33 UTC, jmh530 wrote:I don't really understand the difference between head-const and head-immutable.Const allows the content to change through another reference. Immutable does not. So you can have one pointer that reference an object in "const mode", and another pointer that reference the same object in "mutable mode". With immutable all pointers will reference the object in "immutable mode", so you can reuse any calculations you have derived from immutable objects with no fear of it becoming outdated. However, with head-immutable the object is allowed to reference stuff that can be modified. So, you could have an immutable meta-data-object that reference a writable-file-object. Then the meta-data-object could never change, so you can be certain that whatever you derive from it is always current.
Jan 13 2021
On Wednesday, 13 January 2021 at 20:21:33 UTC, jmh530 wrote:On Wednesday, 13 January 2021 at 19:42:14 UTC, Meta wrote:Do you understand the difference between const and immutable (the transitive ones)? For declaring an int, it's almost the same. const int i = 0; immutable int j = 0; You cannot write i and j regardless which one you use. Only &i and &j will be typed differently, and that's where the difference begins. int i = 0; const(int)* p = &i; // okay immutable(int)* q = &i; // error But because there are no indirections in an int, for p and q it would be the same if const and immutable weren't transitive. Enter double pointers: int i = 0; int* p = &i; immutable(int*)* qq = &p; // error, **qq could change through i and *p. const(int*)* pp = &p; // okay, const only means no write through this Here, it makes a difference that const and immutable are transitive. int i = 0; head_immutable(int*) p = &i; // okay, immutable ptr to mutable value head_const(int*)* pp = &p; // okay, too int* q = &i; head_immutable(int*)* qq = &p; // error, *qq could change through q. Head immutable is more restrictive than head const, obviously.[snip] Is there even any value to having head-const in a language? As I think Walter has said before, it's basically just documentation/convention in C++. I can see the value in head-immutable, in terms of type system guarantees, but not head-const.I don't really understand the difference between head-const and head-immutable.
Jan 13 2021
On Wednesday, 13 January 2021 at 22:15:59 UTC, Q. Schroll wrote:[snip] int* q = &i; head_immutable(int*)* qq = &p; // error, *qq could change through q. Head immutable is more restrictive than head const, obviously.Should that last head_immutable be &q instead of &p? I liked Ola's description, but this one works through the basics in a way that really helps. So thanks.
Jan 13 2021
On Wednesday, 13 January 2021 at 22:34:04 UTC, jmh530 wrote:On Wednesday, 13 January 2021 at 22:15:59 UTC, Q. Schroll wrote:Yes.[snip] int* q = &i; head_immutable(int*)* qq = &p; // error, *qq could change through q. Head immutable is more restrictive than head const, obviously.Should that last head_immutable be &q instead of &p?
Jan 13 2021
On Wednesday, 13 January 2021 at 19:22:07 UTC, Ola Fosheim Grøstad wrote:On Wednesday, 13 January 2021 at 18:37:02 UTC, sighoya wrote:The difference is readonly memory didn't know about the type of data saved in them. A pointer or raw data is in the end the same thing, just bytes. Immutable var in high level languages denote a value even if this value consists of pointers. Most languages simply don't follow the model.Yep, head const is usually readonly or better final, readonly should be transitive.Why? "immutable" is transitive in D. readonly memory or registers are not transitive.No, readonly in technical specifications usually means that it is isn't possible to write to it at all. e.g. readonly hardware registers. They can still point to writable memory.Usually, readonly is only a view of a variable, not a part of type, so you can't change the value of a readonly variable though the value can be changed from outside. So we still have a variable whereas for immutable we have what we call a value.But what happens if I initialize a readonly variable with an object which I mutate from the outside afterwards? This shouldn't be possible with immutable because of being globally unchangeable. In D you can only pass immutable parts into the initialization of an immutable aggregate to prohibit any side effect. But is this the same behavior with readonly in Typescript, I don't think so. shown from https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly:Because reference types contain a reference to their data, a field that is a readonly reference type must always refer to the same object. That object isn't immutable.Which languages are you thinking of?I think what D offers with immutable is unique, I can't even count other languages with my fingers providing the same feature except pure languages.
Jan 13 2021
On Wednesday, 13 January 2021 at 19:52:16 UTC, sighoya wrote:Immutable var in high level languages denote a value even if this value consists of pointers. Most languages simply don't follow the model.Not sure what you mean? In functional languages _everything_ is immutable. In imperative languages immutable tend to be only one level, not transitive?Usually, readonly is only a view of a variable, not a part of type, so you can't change the value of a readonly variable though the value can be changed from outside.No, that would be "const", "readonly" does not allow changes after initialization.But what happens if I initialize a readonly variable with an object which I mutate from the outside afterwards?No problem. But you cannot make the "var" point to a different object. It is locked to be a pointer to that memory address throughout the entire lifespan.In D you can only pass immutable parts into the initialization of an immutable aggregate to prohibit any side effect. But is this the same behavior with readonly in Typescript, I don't think so.Not sure what you mean. "immutable" is transitive, so it is only allowed to point to "immutable". This is what "readonly" is supposed to relax. That is the whole point! :-)shown from https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly:This means that the pointer is immutable, but the pointed-to object is not immutable. Same as the "readonly" that I propose as an addition to "immutable" for D.Because reference types contain a reference to their data, a field that is a readonly reference type must always refer to the same object. That object isn't immutable.
Jan 13 2021