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