www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - head const (again), but for free?

reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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
next sibling parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
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
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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
parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
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:
 [...]
So, I disagree strongly with this. Head immutable is basically the default mutability of tuples in most languages. It is used a lot. [...]
When I see readonly I think of this: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly
Jan 13
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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/readonly
Yes, 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
prev sibling parent reply sighoya <sighoya gmail.com> writes:
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
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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:
 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.
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?
Jan 13
parent reply sighoya <sighoya gmail.com> writes:
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
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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
next sibling parent reply Meta <jared771 gmail.com> writes:
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:
 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?
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.
Jan 13
next sibling parent sighoya <sighoya gmail.com> writes:
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
prev sibling next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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
prev sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
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
next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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
prev sibling parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Wednesday, 13 January 2021 at 20:21:33 UTC, jmh530 wrote:
 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.
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.
Jan 13
parent reply jmh530 <john.michael.hall gmail.com> writes:
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
parent Q. Schroll <qs.il.paperinik gmail.com> writes:
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:
 [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?
Yes.
Jan 13
prev sibling parent reply sighoya <sighoya gmail.com> writes:
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:
 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.
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.
 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
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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:

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