#Sealed Interfaces: Benefits and Uses
1 messages · Page 1 of 1 (latest)
They are useful for modelling data. The biggest benefit is that u can do exhaustive switch expressions over them.
sealed interface Fruit {}
record Apple() implements Fruit {}
record Orange() implements Fruit {}
class Main() {
static void doSomething(Fruit fruit) {
case Apple a -> IO.prinltn("a tasty Apple");
case Orange o -> IO.println("a tasty Orange");
// no default needed!
}
}
If you add another Implementation of Fruit the compiler will throw an error that your switch in doSomething is not exhaustive anymore and you can fix it right away. Hence you should never use a default branch when using switch over enums/sealed classes so you dont lose this benefit.
it also helps the JVM intrinsify things since the JVM will know which classes it can extend
so in theory, if in the future, all of the sealed interface's children were value classes, it could do optimizations there
the best types of data for sealed interfaces is stuff like JsonObject, JsonArray, JsonNumber, JsonNull etc
since there isn't going to be any user-provided json types
Oh that's great! How does this compare to java enums? It seems to me those are better for switching on constants and here potentially more complex shapes. the Apple a is a field a on apple right?
it’s kinda like syntax sugar for instanceof
Although the switch statement they posted looks wrong
Ah so a is a binding for the Fruit in that branch that is narrowed to Apple?
it’s like how you cant do
String a = "x";
int a = 5;
the original var name is already taken up so you need a new var name
similar to:
if (a instance of String b) {
doSomethingWithStr(a);
} else if (a instance of Integer i) {
doSomethingWithInt(i);
} else doSomethingElse(a);
Makes sense! I'm extrapolating from other langs where Apple would be the constructor and a would be a value held within like so
data Fruit a = Apple a | Orange a
case fruit of
Apple a -> ...
Orange o -> ...
except now it’s:
switch (a) {
case String b -> doSomethingWithStr(b);
case Integer i -> doSomethingWithInt(i);
default -> doSomethingElse(a);
}
👍, also the switch case as an expression needs to be exhaustive
so needs to cover all possible values
if you have a sealed interface, the compiler knows whether you’ve covered all possible types and so you don’t need a default case
I love that. So many java features that I'ven't run into being stuck in java 11 land at work
I figure or patterns and fallthrough aren't a thing here right?
switch expression iirc you do not need to break every case
and also, it’s an expression so you can do
var a = switch (o) {
case blahblahblah -> convertToString(o);
default -> throw new Error();
}
if there were another case, would this switch put it in the default branch?
var a = switch (o) {
case Resolved r -> convertToString(r);
case AwaitingResponse _ -> "awaiting";
case Failed failed -> failed.err();
default -> throw new Error();
}
you can have multiple cases
My question is, if I omitted Failed from this switch, would it go to the default case if o doesn't match other case
it would
Cool thank you!
I started using them for error handling, to create a hierarchy of errors, but one thing I don’t like is the pattern matching in switch statements. I have a kind of trigger when I see a switch, and I always regret using it
yeah, but that's the future
I wrote this whole stack overflow post about it
Any other REAL usage?
Could you give me a link?
I actually asked JVM engineers about this. It is currently not the plan to do this yet
It is of course possible
@tame dawn you may or may not like my JSON library
What kind of issues do you run into? I use exhaustive switches in non java environments all the time and am a big fan when there's 3+ variants
Yeah - exceptions have their cons but they are generally better
Sealed interfaces are great though
Glad you brought up rust since that's where my mental model was for this the whole time
What do you mean by better?
Programming with side effects aren't better
IMHO, one exception that is acceptable is Exception, and none for RuntimeException
Exceptions aren't really side effects - not really
They are an alternative return value mechanism that interacts with control flow
A function that throws DivisionByZero is the same as one that returns a sealed interface of either a number of a division by zero variant
What about a scenario when you validate domain object and you found that the value is for example > 7.
RuntimeException or Error with Sealed interface?
This is not side effect it's a branch of your program that should be handled
Like left side in Either
So that is a valid complaint about unchecked exceptions
Runtime too
Even languages like Haskell have some notion of a "panic"
Runtime exceptions are unchecked
I mean, complaint to unchecked exception too, they are overused
Exception should be used only when you canno recover a state
disk destroyed
network outage
I want you to take a step back here and separate your "should"s from how the mechanics actually function
Your mental model should treat all "sensible" - read, there are people in the world not on drugs who will do them - usages as "valid"
Yes, this parse function should be modeled to return union type ValidDomanModel | Invalid
There is no place for throwing runtime exceptions in this case
But in Java creating and using a union type (sealed inrerface and it's implementations) is too much of hand work because on each step of a program you must explicitly map and re-propagate errors. The code becomes rigid and hard to understand.
That's why I'm waiting for Kotlin 2.4 Rich Errors
That's a really good idea.
Runtime exceptions - panics - for unrecoverable situations.
And Either/Result/sealed interface for recoverable situations - but in Java doing this leads to hard to manage verbosity of re-propagation errors.
That's why ppl prefer runtime(unchecked) exceptions even in recoverable situations - to make the code flexible and not rigid in exchange for cons of runtime exceptions - implicit control flow, goto programming style
To address these issues, we will have Kotlin 2.4 Rich Errors for recoverable situations and Runtime exceptions for unrecoverable situations.
Similar to how Rust has Result and panic.
Mapping:
Result - Kotlin 2.4 Rich Errors.
panic - runtime exceptions.
Similar to Gleam language's Result vs panic
This is a discussion of motivation and rationale behind rich errors. The current full text of the proposal can be found here: fun load(): User | NotFound when (val user = load()) { is User -> pr...
Recording brought to you by American Express. https://americanexpress.io/kotlin-jobs
Kotlin's nullability model is one of its core features. In this talk, we’ll explore how this model can be generalized to restricted union types specifically designed for handling error cases.
Additionally, we’ll discuss how errors can currently be represe...
That's how the handling of recoverable situations should be done in sane way
We started using Either from Vavr, and it works quite well. We have a hierarchy of errors, and in the worst case, it can be a GeneralError. This makes it easy to propagate errors with Either.
The only downside I see is the lack of some functions in the library and in functional programming itself. It’s also a bit difficult at the beginning to get used to the functional style, and the lack of official support(oracle) makes it harder.
Still, I’m hopeful, because Vavr now has a new maintainer who takes it seriously.
Anyway, it’s much easier to just throw a runtime exception, since that’s what we’re used to. But that doesn’t mean it’s the better way.
That's nice feature. Maybe we will see something similiar in java within the next century 
Yep, definitely
Can you figure out how that translates to runtime? Like what does java code see calling a kotlin function defined that way
There is no java or kotlin at runtime. There is a JVM bytecode interpreted in runtime. As of my understanding
If we exclude GraalVM
okay but when you compile java code you point it to classfiles
how is this kotlin language feature translated to those
From what I've read
https://github.com/Kotlin/KEEP/blob/main/proposals%2FKEEP-0441-rich-errors-motivation.md, I really do not know
That's interesting question.
I don't know as of now.
But since Kotlin team really cares about interop, I assume there will be something for Java calling Kotlin function
It's future, we need to wait till this is implemented and everything will be clear
right - so is it going to just translate to checked exceptions?
I don't think there will be translation to Java checked exceptions because of their significant disadvantages
Here's what I found
https://github.com/Kotlin/KEEP/discussions/447#discussioncomment-14230662
kotlin really losing its "two way integration with java" pitch
I hope they'll implement
Yes, it is possible to provide an annotation to produce bridges with another representation for top-level values. It might be part of the plugin or even the standard one.
So, it's too early to state this
i mean this isn't the first point
it kinda has no choice but to diverge - multiplatform introduces restrictions
Sorry, but this is too abstract for me. Are there concrete examples?