#Sealed Interfaces: Benefits and Uses

1 messages · Page 1 of 1 (latest)

gloomy saddleBOT
#

@thorn halo has a question:

sketusky

Hey,
Do you use sealed interfaces? If so, what do you solve with them?

#

<@&987246399047479336> please have a look, thanks.

rough vale
#

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.

tame dawn
#

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

dull current
tame dawn
#

Although the switch statement they posted looks wrong

dull current
tame dawn
#

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);
dull current
tame dawn
tame dawn
#

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

dull current
#

I figure or patterns and fallthrough aren't a thing here right?

tame dawn
#

and also, it’s an expression so you can do

var a = switch (o) {
  case blahblahblah -> convertToString(o);
  default -> throw new Error();
}
dull current
tame dawn
#

you can have multiple cases

dull current
dull current
#

Cool thank you!

thorn halo
#

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

low void
#

I wrote this whole stack overflow post about it

thorn halo
#

Any other REAL usage?

thorn halo
low void
low void
#

It is of course possible

#

@tame dawn you may or may not like my JSON library

dull current
low void
#

Sealed interfaces are great though

dull current
thorn halo
#

Programming with side effects aren't better

#

IMHO, one exception that is acceptable is Exception, and none for RuntimeException

low void
#

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

thorn halo
#

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

low void
thorn halo
low void
#

Even languages like Haskell have some notion of a "panic"

low void
thorn halo
#

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

low void
#

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"

agile holly
#

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

agile holly
# thorn halo Exception should be used only when you canno recover a state

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

#
GitHub

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...

▶ Play video
#

That's how the handling of recoverable situations should be done in sane way

thorn halo
# agile holly But in Java creating and using a union type (sealed inrerface and it's implement...

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.

thorn halo
low void
# agile holly Yep, definitely

Can you figure out how that translates to runtime? Like what does java code see calling a kotlin function defined that way

agile holly
#

If we exclude GraalVM

low void
#

how is this kotlin language feature translated to those

agile holly
agile holly
#

It's future, we need to wait till this is implemented and everything will be clear

low void
agile holly
low void
agile holly
#

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.

agile holly
low void
#

it kinda has no choice but to diverge - multiplatform introduces restrictions

agile holly