Skip to content

[FR] allow opting-in to sealed classes codegen for unions #1403

@iamdanfox

Description

@iamdanfox

What happened?

In the apollo codebases, we use unions pretty heavily and on the whole they're pretty great for compile-time safety and forcing devs to consider implications of changes everywhere. That being said, the verbosity of visitors is pretty frustrating from a readability perspective. Building visitors using the new shorthand builders is a lot better than the long anonymous classes, but limitations Java's type inference seems to require us to specify return types upfront so we get stuff like this. It also frustrates me that performance sensitive codepaths jump through indirection to avoid constructing and allocating a visitor on every invocation.

return reportedServiceState.accept(ReportedServiceState.Visitor.<ApolloReportedServiceState>builder()
                .assetServer(assetServerReportedServiceState -> ...)
                .blueGreen(blueGreenReportedServiceState -> ...)

What did you want to happen?

With LTS Java 17 arriving in a couple of months (14th Sep GA), we'll get access to Sealed Classes and a preview of pattern matching for switch expressions.

This means that java has first-class support for java unions, and I'd really like to be able to take advantage of the improved readability:

static String switchExpression(MyUnion union) {
    return switch (union) {
        case FooVariant foo -> String.format("foo %s", foo);
        case BarVariant bar -> String.format("bar %s", bar);
        case BazVariant baz -> String.format("baz %f", baz);
        default -> union.toString();
    };
}

Given how many variants some unions have, I reckon it's probably fine to emit code for these across different files:

sealed interface Celestial 
    permits Planet, Star, Comet { ... }

final class Planet implements Celestial { ... }
final class Star   implements Celestial { ... }
final class Comet  implements Celestial { ... }

Some open questions are:

  • would we keep around a final class UnknownVariant implements Celestial { ... } to make consumers always acknowledge the possibility that the server might have started returning a new variant which their code wasn't compiled to handle.
  • should we keep around the accept(Visitor) method? (I think yes, because it allows a convenient migration)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions