head const (again), but for free?

Q. Schroll qs.il.paperinik at gmail.com
Mon Jan 11 19:05:13 UTC 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.


More information about the Digitalmars-d mailing list