No we should not support enum types derived from strings

Andrei Alexandrescu SeeWebsiteForEmail at erdani.org
Wed May 12 13:39:50 UTC 2021


On 5/11/21 10:39 PM, Timon Gehr wrote:
> Deadalnix is saying that there is a subtyping relationship for rvalues, 
> while you are pointing out that there is no subtyping relationship for 
> lvalues. I think those are both correct.

Well put. Rvalues can afford the luxury to change representation (e.g. 
from byte to int of float to double) because they're only used once. So 
a passable polymorphism scheme can be implemented via coercion.

> (Type theory has no notion of 
> lvalues or rvalues, so those would indeed have to be interpreted as 
> different types.)

Hmmm... haven't looked in a while, but don't some of Java formalizations 
account for int, double etc. being values and consequently rvalues when 
passed around? (Though they can't be passed by reference so a 
formalization could get away with assuming int is a reference, e.g. ++x 
means "rebind reference x to a new reference to the value x + 1").

> I fail to see why the semantics of lvalues should have any bearing on 
> format strings even though I understand why most of Phobos might want to 
> assume isSomeString talks about lvalues of the type.

It doesn't, the format string is just a symptom. The problem is that we 
change (already did, and massively... >100 instances of StringTypeOf) 
the standard library to accommodate what I think is an unproductive form 
of genericity.

>> There's no true subtyping or polymorphism with value semantics.
>> ...
> 
> There's certainly subtyping. The point about "polymorphism" (in type 
> theory, polymorphism typically refers to parametric polymorphism, but I 
> guess you mean existential types), is a bit more tricky. I guess the 
> point is that a language that wants `f(σ) ⊆ ∃τ. f(τ)` to hold without 
> any runtime semantics can't support data types whose values do not embed 
> runtime type info. However, it can certainly support value types, even 
> value types that are stored without indirections.

One matter is to distinguish what can be done from what D has already 
done and cannot change. For example, I tried some code just now and 
was... surprised.

Meta mentioned that increment works with enums, and lo and behold it does:

void main() {
     import std;
     enum X : int { x = 10, y = 20 }
     X x;
     writeln(x);
     ++x;
     writeln(x);
}

That prints "x" and then "cast(X)11". Meaning you can easily write a 
program that takes you outside enumerated values without a cast, which 
somewhat dilutes the value of "final switch" and the general notion that 
enumerated types are a small closed set. Arguably ++ should not be 
allowed on enumerated values.

Surprises go on:

void main() {
     import std;
     enum X : string { x = "Hello, world!", y = "xyz" }
     X x;
     writeln(x);
     x = x[1 .. $];
     writeln(x);
}

That prints:

x
cast(X)ello, world!

which showcases, as a little distraction, a bug in the formatting of 
enums - the string should be quoted properly.

But the larger point is that enum types derived from string actually 
allow, again, stepping outside their universe with ease.

This cramps my style somewhat because during the whole discussion I 
assume that doesn't work, or at least shouldn't. I guess an argument 
could be built that its semantics is what it is.

Anyway, the other side of the argument that got ignored is the alias 
this thing:

void main() {
     import std;
     static struct X {
         string fun();
         alias fun this;
     }
     X x;
     x = x[1 .. $];
}

This doesn't compile; the slice does, but the assignment doesn't. Which 
means there are differences in what would be expected of a string (or, 
as it turns out, an enum string) and what would be expected of a type 
that converts to string by means of alias this.


More information about the Digitalmars-d mailing list