Re: Question about pattern matching and sealed types – redundant switch cases

Brian Goetz brian.goetz at oracle.com
Sat Apr 19 17:51:59 UTC 2025


Yes, `flatMap` would be the traditional name.  I used `andThen` because 
`flatMap` scares a lot of Java developers.  And it's fine for you to do 
the same!  I think you would agree that it's better to get your team 
writing the simpler, more readable code, than being scared off by the 
"correct" terminology.  Some day they will realize that they've been 
flatMapping for years, and it's not as scary as they thought.

If I were designing this API I would also want to do a requirements 
analysis on error handling modes.  In your original example, you didn't 
just throw whatever error the Err wrapped; you wrapped that error with 
some other error, that presumably adds some context to the error, like 
what you were trying to do at the time.  And that can be pretty valuable 
for diagnosing the app when things go wrong.  So you may want to provide 
some support for mapping errors as well, to widen the happy path that 
you want to keep developers on.



On 4/19/2025 1:42 PM, Andreas Berheim Brudin wrote:
> True, you may very well have a point here.
>
> I actually have the "andThen" method on my Result, but I call it 
> "flatMap" for consistency. Some people had a hard time understanding 
> what was going on and I don't want to be the one pushing for every 
> developer needing a degree in category theory so I am trying to 
> introduce this safe programming style without requiring familiarity 
> with too advanced functional concepts (even though many seem happy to 
> flatMap their lists!).
>
> But I guess just calling it "andThen" might alleviate some of the 
> burden, thanks!
>
> /Andreas
>
> On Sat, Apr 19, 2025 at 7:34 PM Brian Goetz <brian.goetz at oracle.com> 
> wrote:
>
>     Right, you've built a monad here, and you want to express the
>     happy path with a monadic do.  In Haskell you'd write this as:
>
>         do
>             something <- getSomething()
>             result <- doThingWithSomething(something)
>
>     and let the language desugar it accordingly.  But you can do this
>     yourself in Java!
>
>         sealed interface Result<T> permits Ok, Err {
>             <U> Result<U> andThen(ThrowyFunction<T, U> f);
>             static<T> Result<T> of(ThrowySupplier<T> s) { ... }
>         }
>
>     and then express your code as:
>
>         Result.of(() -> getSomething())
>               .andThen(C::doThingWithSomething)
>               ...
>
>     No nested code needed.
>
>
>     On 4/19/2025 1:26 PM, Andreas Berheim Brudin wrote:
>>     Thank you both for the quick replies.
>>
>>     I guess the fact that b1 and b2 is available outside of the if,
>>     hints at some kind of flow-based type analysis (even though the
>>     new type is implicitly declared in the pattern), but I would then
>>     have proven Brian's point - that once that door is open, people
>>     will ask for more.
>>
>>     But given the semantics of Java, I understand that this doesn't
>>     fit very well. The reason I ask was that we use a Result type
>>     (Ok/Err) quite a lot, and the biggest complaint is the nestedness
>>     of the code, which often becomes:
>>     return switch(getSomething()) {
>>       case Err(var e) -> yield someError(e);
>>       case Ok(var something) -> {
>>         yield switch (doThingWithSomething(something)) {
>>           case Err(var e) -> yield someOtherError(e);
>>           case Ok(var result) -> etc...
>>       }
>>     }
>>
>>     I guess you don't have any quick fixes for that other than
>>     resorting to traditional exception handling.
>>
>>     Best,
>>     Andreas
>>
>>     On Sat, Apr 19, 2025 at 7:08 PM Remi Forax <forax at univ-mlv.fr> wrote:
>>
>>
>>
>>         ------------------------------------------------------------------------
>>
>>             *From: *"Andreas Berheim Brudin" <andreas.brudin at gmail.com>
>>             *To: *"amber-dev" <amber-dev at openjdk.org>
>>             *Sent: *Saturday, April 19, 2025 6:17:43 PM
>>             *Subject: *Question about pattern matching and sealed
>>             types – redundant switch cases
>>
>>             Hi all,
>>
>>             I'm new to the list—apologies if this has been discussed
>>             before, and thanks in advance for your time.
>>
>>             I have a question about pattern matching with sealed
>>             types. Consider this example:
>>
>>             sealed interface A permits B, C {}
>>             record B(String b1, String b2) implements A {}
>>             record C(String c) implements A {}
>>
>>             A myVar = ...;
>>             if (!(myVar instanceof B(var b1, var b2))) {
>>                 return switch (myVar) {
>>                     case C(var c) -> c;
>>                     case B b -> throw new
>>             IllegalStateException("should not happen");
>>                 };
>>             }
>>             // use b1, b2
>>
>>             Here, I want to keep an early-return style, but since I
>>             need to return a value from C, I have to use a switch.
>>             This leads to redundancy: we've already tested myVar is
>>             not a B, so that case should be statically unreachable.
>>
>>             My questions:
>>
>>             1. Is there any consideration or ongoing work to allow
>>             the compiler to automatically eliminate such unreachable
>>             cases?
>>
>>
>>         The short answer is no :)
>>
>>         Long answer,
>>           (1) myVar does not change its type, it is declared as an A,
>>         it always be an A (Groovy or Kotlin behave differently, they
>>         use flow typing)
>>           (2) the type system of Java has no notion of exclusion, the
>>         type A but not B does not exist
>>           (3) instanceof and switch does not have the same semantics,
>>         instanceof has no notion of exhaustiveness while a switch on
>>         a sealed type has.
>>
>>         so because of (1) the type of myVar can not be changed,
>>         because of (2) its new type inside the if can not be "A but
>>         not B" and because of (3) the new type can not be only C.
>>
>>
>>
>>             2. If only one case remains (in this case, C), could it
>>             be possible to treat the variable as a C directly without
>>             requiring an explicit switch?
>>
>>
>>         No,
>>
>>
>>
>>             Again, apologies if this has already been discussed. I'd
>>             appreciate any pointers to relevant threads or JEPs if so.
>>
>>
>>         The relevant JEPs are JEP 394 (for instanceof) and 441 (for
>>         switch).
>>
>>
>>
>>             Thanks,
>>             Andreas
>>
>>
>>         regards,
>>         Rémi
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20250419/ef395c0f/attachment-0001.htm>


More information about the amber-dev mailing list