Hi Maurizio,
On 06/15/2017 01:58 PM, Maurizio Cimadamore wrote:
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]).
This is similar, but not the same. In above example, Generic<T> is not a
subtype of Option. It's just an interface implemented by constant's
subclasses. So you can not access Option members via an instance of
Generic<T>... Generic<T> therefore has to declare all the interesting
methods that can then be implemented by Option. You also have to
accompany this solution with "sealed" interfaces if you don't want other
implementations of Generic<T> besides the enum constants...
My example OTOH inserts a class between the enum type and the constant
type which is designated by enum declaration and must be part of the
same compilation unit (i.e. enum member static class). This middle class
could have the same constraints regarding its constructor (must be
private) as the enum constructor so no other class could subclass it or
instantiate it. In effect it would be "sealed" by the virtue of
constructor access modifier(s).
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.
Or to view the Generic as subtype of Option...
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.
Yes, but it would be nice for custom generic supertypes of constants to
also be subtypes of enum type. This would have even more use cases.
Regards, Peter
[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