I just saw the ETI rejection over in Announce, and I just wanted to note that there's a handy feature in Neat (my lang), that would make this very simple.
Neat has Identifier Types. An identifier is an expression of the form :word
. The type of :word
is :word
, ie. identifiers do double-duty as types and expressions. The only value that can be assigned to a variable of type :word
is :word
; it is a type with a size of 0, like void
.
Identifier types were mostly introduced to allow disambiguating sumtypes: void sleep((:delay, long seconds | :until, long seconds_since_epoch) target)
. But here they give us an unexpected upside.
In terms of effort, it would be extremely easy (though I haven't done it yet) to say that an identifier can implicitly convert to an enum iff the enum contains a member with the same name as the identifier.
What are the advantages of this? There is zero need to complexify the compiler by defining contexts in which a type can be inferred. There is no impact on type inference in this feature. The type of :Dog
is perfectly unambiguous: it's :Dog
. It so happens that a value of this type can implicitly convert to enum Animals { Dog, Cat, Pigeon, }
, but this just uses the well-established implicit conversion system, and when it fails you get a nice readable error.
Now, this is one instance where a rich typesystem hangs together in a way that D's relatively sparse typesystem does not. Consider the following: Animal[] animals = [:Dog, :Cat];
. What is the type of the array literal? Since we don't know we're assigning to Animal[]
, D would error here because it cannot unify :Dog
and :Cat
; they're different types. In Neat, because we have built-in sumtypes, we can just say the type is (:Dog | :Cat)[]
, and then notice that oh yeah, this can still implicitly convert to Animal[]
: because every member of the sumtype is implicitly convertible, the sumtype is implicitly convertible. But even without this, identifier types would allow implementing ETI without making the overall language design any more complex - they add a built-in type, but they don't change the existing language semantics at all.
(If we had opImplicitCast(T)
, we could implement identifier types in a library...)