Some findings regarding records, sealed types and switch expressions
Daniel Dietrich
cafebab3 at googlemail.com
Thu Aug 22 21:45:33 UTC 2019
Hello, my name is Daniel Dietrich (creator of Vavr).
This is my first email to an OpenJDK mailing list.
I ask myself how threads work. Does one simply reply to an email?
Do I need to subscribe to amber-dev in order to see answers?
I tested records, sealed types and switch expressions the last days. I know, that some of these features might still be under heavy development. But maybe some of my findings/questions help you.
Thanks!
Best regards,
Daniel
1) Questions (sealed)
1.1) Question: Do make generics in the permits section sense?
I can’t find a use case. The example below is misleading for a user.
// why restrict an implementation to specific generic types?
sealed interface A permits B<String> {}
class B<T> implements A
A a = new B<>(1);
1.2) Error: A generic sealed interface cannot be subclassed
sealed interface A<T> permits B<T> {}
class B<T> implements A<T> {}
sealed interface A<T> permits B {}
class B<T> implements A<T> {}
sealed interface A<T> permits B {}
class B implements A {}
In all three examples we get "cannot inherit from sealed class: A"
Removing the generic type parameter from the sealed interface satisfies the compiler:
sealed interface A permits B {}
class B<T> implements A {}
sealed interface A permits B<String> {}
class B<T> implements A {}
1.3) Error: switch cases on generic records do not work
sealed interface Option permits Some, None {}
record Some<T>(T t) implements Option {}
record None() implements Option {}
Option o1 = new Some<>("1");
var res = switch(o1) {
case Some(var s) -> s;
^-- error: cannot access AutoCloseable
bad class file: /modules/java.base/java/lang/AutoCloseable.class
undeclared type variable: T
Please remove or make sure it appears in the correct subdirectory of the classpath.
case None none -> "";
};
If I add AutoCloseable, another
sealed interface Option extends AutoCloseable permits Some, None {}
Option o1 = new Some<>("1");
var res = switch(o1) {
case Some(var s) -> s;
^-- error: cannot access ElementType
bad class file: /modules/java.base/java/io/Serializable.class
undeclared type variable: T
Please remove or make sure it appears in the correct subdirectory of the classpath.
case None none -> "";
};
I'm not sure, if the latter error (Serializable) was already fixed on the 'record-and-sealed' branch today, I've seen activity there.
However, it isn't merged to the 'patterns-deconstruction', yet, so I couldn't test it (without manual merge effort).
2) Questions (records)
I get a couple of errors on the 'pattern-deconstruction' branch (changeset 57026:00dece45a5de).
2.1) Compiler error: "Unexpected kind: RECORD"
The compiler does not recognize if record parameters are missing (or added) on instance creation:
// example
record A(String s);
new A(); // exception in the compiler (see below)
An exception has occurred in the compiler (14-internal). Please file a bug against the Java compiler via the Java bug reporting page (http://bugreport.java.com) after checking the Bug Database (http://bugs.java.com) for duplicates. Include your program, the following diagnostic, and the parameters passed to the Java compiler in your report. Thank you.
java.lang.AssertionError: Unexpected kind: RECORD
at jdk.compiler/com.sun.tools.javac.code.Kinds.kindName(Kinds.java:307)
at jdk.compiler/com.sun.tools.javac.comp.Resolve$InapplicableSymbolError.getDiagnostic(Resolve.java:4011)
at jdk.compiler/com.sun.tools.javac.comp.Resolve.logResolveError(Resolve.java:3731)
at jdk.compiler/com.sun.tools.javac.comp.Resolve.accessInternal(Resolve.java:2496)
at jdk.compiler/com.sun.tools.javac.comp.Resolve.accessMethod(Resolve.java:2514)
at jdk.compiler/com.sun.tools.javac.comp.Resolve$BasicLookupHelper.access(Resolve.java:3328)
at jdk.compiler/com.sun.tools.javac.comp.Resolve.lookupMethod(Resolve.java:3569)
at jdk.compiler/com.sun.tools.javac.comp.Resolve.resolveConstructor(Resolve.java:2784)
at jdk.compiler/com.sun.tools.javac.comp.Resolve.resolveConstructor(Resolve.java:2775)
at jdk.compiler/com.sun.tools.javac.comp.Attr.visitNewClass(Attr.java:2599)
at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCNewClass.accept(JCTree.java:1854)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribTree(Attr.java:685)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribExpr(Attr.java:736)
at jdk.compiler/com.sun.tools.javac.comp.Attr.visitExec(Attr.java:2048)
at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCExpressionStatement.accept(JCTree.java:1585)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribTree(Attr.java:685)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribStat(Attr.java:758)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribStats(Attr.java:777)
at jdk.compiler/com.sun.tools.javac.comp.Attr.visitBlock(Attr.java:1331)
at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:1047)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribTree(Attr.java:685)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribStat(Attr.java:758)
at jdk.compiler/com.sun.tools.javac.comp.Attr.visitMethodDef(Attr.java:1135)
at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:889)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribTree(Attr.java:685)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribStat(Attr.java:758)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribClassBody(Attr.java:5211)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribClass(Attr.java:5064)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attribClass(Attr.java:4993)
at jdk.compiler/com.sun.tools.javac.comp.Attr.attrib(Attr.java:4938)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.attribute(JavaCompiler.java:1346)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:972)
at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:318)
at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:176)
at jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:57)
at jdk.compiler/com.sun.tools.javac.Main.main(Main.java:43)
2.2) Error: Not possible to match record with no arguments
sealed interface Option permits Some, None {}
record Some(String s) implements Option {}
record None() implements Option {}
Option o1 = new Some("1");
var res = switch(o1) {
case Some(var s) -> s;
case None() -> "";
^-- error: illegal start of expression
};
// workaround: case None none -> "";
// however, it fails with 2.3) then
2.3) Error: Exhaustiveness check does not seem to work in this case
sealed interface Option permits Some, None {}
record Some(String s) implements Option {}
record None() implements Option {}
Option o1 = new Some("1");
var res = switch(o1) {
^-- error: the switch expression does not cover all possible input values
case Some(var s) -> s;
case None none -> "";
};
// workaround: add default case
2.4) Question: Are instanceof checks planned in switch expressions?
record A();
var res = switch(new Object()) {
case A -> 1 // instanceof A check
default -> 0
};
2.5) Question: Best way to implement singletons?
Scala:
sealed trait Option[+T]
case class Some[+T](t: T) extends Option[T]
case object None extends Option[Nothing] // singleton
val o1: Option[String] = Some("")
val o2: Option[String] = None
val res = o1 match {
case Some(t) => t
case None => ""
}
In Java, I would use a singleton class or an enum:
sealed interface Option<T> permits Some, None {}
record Some<T>(T t) implements Option<T> {}
class None<T> implements Option<T> {
private static final None<?> INSTANCE = new None<>();
private None() {}
static <T> None<T> instance() {
@SuppressWarnings("unchecked")
final None<T> none = (None<T>) None.INSTANCE;
return none;
}
}
Option<String> o1 = new Some<>("");
Option<String> o2 = None.instance();
var res = switch(o1) {
case Some(t) -> t;
case None none -> "";
}
However, what I really want is a singleton with all the benefits of a record:
sealed interface Option<T> permits Some, None {}
record Some<T>(T t) implements Option<T> {}
__singleton__ record None<T>() implements Option<T> {} // no new None() possible
Option<String> o1 = new Some<>("");
Option<String> o2 = None.instance(); // generated method
var res = switch(o1) {
case Some(t) -> t;
case None none -> "";
}
However, I see that the cast is a corner case (possible, because record has no entries).
More information about the amber-dev
mailing list