[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