[enhanced enums] - end of the road?
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Thu Jun 15 11:58:37 UTC 2017
I think the moral equivalent of what you wanted to say is something like
this:
enum Option implements Consumer<String> {
D implements Generic<String>("-d", ...) { ... }
PROC implements Generic<ProcOption>("-proc", ...) { ... }
...
}
Which is not too terrible (in fact has been put forward by John as a
comment in the JEP [1]).
That said, if we went down that path, note that, at least in my
re-formulation, there's no way to view Option as a subtype of Generic.
In other words, the enum as a whole would have nothing to do with
Generic, subtyping wise. Now, I think for the particular use case we
were considering, this could be still ok - at the end of the day we
wanted to write something like:
public Z get(Option<Z> option) { ... }
and in this model we could rewrite this as:
public Z get(Generic<Z> option) { ... }
One observation, if we had sealed interface, one could do this:
enum Option implements Consumer<String> {
...
sealed interface Generic<X> { ... }
}
so that the interface can only be effectively implemented by the enum
constants.
So, what you're saying (or what I'm inferring from what you're saying
:-)) is: if we can't have generic on enums, having custom generic
supertypes on constants seems a pretty good approximation. Which is, I
think, a fair point.
[1] -
https://bugs.openjdk.java.net/browse/JDK-8170351?focusedCommentId=14064981&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14064981
On 15/06/17 12:43, Peter Levart wrote:
> Hi Maurizio,
>
> What if the enum type was kept non-generic, but there could optionally
> be a designated generic supertype inserted between the enum type and
> the constant type. For example:
>
> public enum Option implements Consumer<String> super Generic {
> D<String>("-d", ...),
> PROC<ProcOption>("-proc", ...),
> ...;
>
> class Generic<T> implements Function<String, T> {
> Generic(...) {
> super(...);
> }
>
> public T apply(String s) {
> ...
> }
> }
>
> Option(...) {
> ...
> }
>
> public void accept(String s) {
> ...
> }
> }
>
>
> this would translate to:
>
> public class Option extends Enum<Option> implements Consumer<String> {
>
> public static final Generic<String> D = new Generic<>("D", 0,
> "-d", ...);
> public static final Generic<ProcOption> PROC = new
> Generic<>("PROC", 1, "-proc", ...);
> ...
>
> static class Generic<T> extends Option implements Function<String,
> T> {
> Generic(String name, int ordinal, ...) {
> super(name, ordinal, ...);
> }
>
> public T apply(String s) {
> ...
> }
> }
>
> Option(String name, int ordinal, ...) {
> super(name, ordinal);
> ...
> }
>
> public void accept(String s) {
> ...
> }
> }
>
>
> The "super" keyword in enum declaration could only designate a class
> in the same compilation unit - the enum member static class.
>
> Hm...
>
>
> Regards, Peter
>
> On 05/23/2017 07:49 PM, Maurizio Cimadamore wrote:
>> Hi,
>> over the last few weeks we've been carrying out experiments to test
>> the feasibility of the enhanced enums feature. As described in the
>> JEP, this feature is particularly powerful as it allows enums
>> constants to be carrier of generic type information, which can then
>> be fed back to the inference machinery.
>>
>> One experiment we wanted to make was to see if enhanced enums could
>> make javac's own Option [1] enum better. This enum defines a bunch of
>> constants, one for each supported javac command line arguments (e.g
>> -classpath, -d, etc.). Furthermore, the enum defines method so that
>> each constant can be parsed given the javac command line and
>> processed, accordingly to some OptionHelper. Most options, along with
>> the value of their arguments would simply be stored into a
>> Map<String, String>, which is the backbone of the Options class [2].
>>
>> One problem with storing option values as Strings is that clients
>> need to do the parsing. So, if an option has an integer argument,
>> it's up to the client to get the value of that option off the map,
>> parse it into a number and (maybe) check as to whether the range
>> makes sense.
>>
>> With enhanced enums it should be possible to do better than this;
>> more specifically, if enums supported generics, each option could
>> specify a type argument - that is, the type of the argument that
>> javac expects for that particular option.
>>
>> So, an option with a plain String argument would be encoded as
>> Option<String>, as follows:
>>
>> D<String>("-d", ...)
>>
>> While an option for which multiple choices are available, could be
>> encoded using an enum as a type-argument - for instance:
>>
>> PROC<ProcOption>("-proc", ...)
>>
>> where ProcOption would be defined as follows:
>>
>> enum ProcOption {
>> NONE, ONLY;
>> }
>>
>> Finally, for an option whose argument can be a set of values, we
>> would use the following encoding:
>>
>> G_CUSTOM<EnumSet<DebugOption>>("-g:", ...)
>>
>> where DebugOption would be defined as follows:
>>
>> enum DebugOption {
>> LINES, VARS, SOURCE;
>> }
>>
>> So, instead of storing all options into a Map<String, String>, we
>> could store them into a Map<Option, Object>. Then, we would turn the
>> Options.get method from this:
>>
>> public String get(String option) { ... }
>>
>> to something like this:
>>
>> public Z get(Option<Z> option) { ... }
>>
>> granted, there will be some unchecked operations carried out by the
>> body of this method, but the map should be well-constructed by
>> design, so it should be safe. What we get back is that now clients
>> can do things like:
>>
>> boolean g_vars = options.get(Option.G_CUSTOM).contains(DebugOption.VARS);
>>
>> Note how we raised the expressiveness level of the client, which no
>> longer has to do parsing duties (and domain conversions). So, that
>> was the experiment we wanted to carry out - ultimately, this is the
>> kind of stuff you'd like to be writing with enhanced enums, so this
>> seemed like a reasonably comprehensive test for the feature.
>>
>>
>> Unfortunately, the results of the experiment were not as successful
>> as we'd hoped. As soon as we turned the Option enum into a generic
>> class (by merely adding a type parameter in its declaration), we
>> immediately started hitting dozens of compile-time errors. The errors
>> were rather cryptic, all pointing to some obscure failure when
>> calling EnumSet.noneOf or EnumSet.allOf with the newly generified
>> Option class. In other words, code like this:
>>
>> EnumSet.noneOf(Option.class)
>>
>> Was now failing. The issue that was underpinning all these failures
>> is - in retrospect - rather obvious: the following type:
>>
>> EnumSet<Option>
>>
>> is *not* a well-formed type if Option is a generic class. Why? Well,
>> EnumSet is declared like this:
>>
>> class EnumSet<*E extends Enum<E>*> { ... }
>>
>> which means the type parameter has an f-bound. In concrete terms, we
>> have to check that:
>>
>> Option <: [E:=Option]Enum<E>
>>
>> That is, the actual type-argument must conform to its declared bound.
>> But if we follow that check, we obtain:
>>
>> Option <: [E:=Option]Enum<E>
>> Option <: Enum<Option>
>> Enum (*) <: Enum<Option>
>> false
>>
>> (*) note that Option is now a 'raw' type - and a raw type has all
>> supertypes erased, as per JLS 4.8.
>>
>> In other words, there's no way to write down the type of an enum set
>> which contains heterogeneous options - the wildcard path doesn't help
>> either:
>>
>> EnumSet<Option<?>>
>>
>> As, the above check would develop in the following way:
>>
>> Option<?> <: [E:=Option<?>]Enum<E>
>> Option<?> <: Enum<Option<?>>
>> Enum<#CAP> (**) <: Enum<Option<?>>
>> false
>>
>> (**) the supertype of a wildcard-parameterized type is obtained by
>> first capturing, and then recursing to the supertype, as per JLS 4.10.2
>>
>>
>> In other words, generic enums are not interoperable with common data
>> structures such as enum sets (and, more generally, with any f-bounded
>> generic data structure).
>>
>> While we could just deliver the part of JEP 301 regarding sharper
>> typing of enum constants and leave generic enums alone, we feel
>> there's not much value into pursuing that path alone. After all, the
>> benefits of enhanced enums were exactly in combining sharper typing
>> with generic type information, so that enum constants could be used
>> as type carriers. If generic enums are not viable, then much of the
>> usefulness of this JEP is lost.
>>
>> It is unclear at this point in time if type system improvements
>> (which we are pursuing as part of a separate activity [Dan is there a
>> link for this??]) would ameliorate the situation.
>>
>> Until we figure this out, I suggest that we put this JEP on hold for
>> the time being
>>
>> [1] -
>> http://hg.openjdk.java.net/jdk10/jdk10/langtools/file/tip/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java
>> [2] -
>> http://hg.openjdk.java.net/jdk10/jdk10/langtools/file/tip/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java#l48
>>
>>
>>
>> Cheers
>> Maurizio
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20170615/1aa5cb71/attachment.html>
More information about the amber-spec-experts
mailing list