hi everyone, Q: Do 'Sealed (exhaustive) `when` sta...
# getting-started
m
hi everyone, Q: Do 'Sealed (exhaustive) 
when
 statements' break OCP(open-closed-principle) ? I saw the new sealed when compiler feature on Kotlin 1.6.0 and shared it to my team, thinking we could use it, but there was an opinion that this kind of logic breaks the ocp rule because, If you add new inheritors to the sealed class(or interface), you would have to change all the when statements that take that sealed class(which is the parent class) and thus, ocp is broken Would this be true? We agreed that the oop rules were made before sealed classes/interfaces were introduced and this might be the "trend" of programming languagues, but still some are hesitant to use it. Any kind of related opinion & reading material is welcome to be shared,, thanks!
a
This problem has always existed and exhaustive when doesn't change anything.
👀 2
j
I would argue that using a
when
statement already breaks the open close principle, because you're listing all possible entries (
switch
is considered harmful in OOP because it ruins how polymorphism works, it doesn't abstract away the behaviour behind a swappable interface). Using an
else
in a
when
is just a way to feel safe but more often than not it's just
else -> error("unknown case X")
. Not using an
else
at all (with existing non-exhaustive statements) is even more sneaky! If you add new subclasses or enum values, your
when
statement will likely fail or not behave as expected - at runtime. Using sealed `when`s is a way to detect all those changes at compile time instead.
👍 1
👀 1
d
There are two different usecases of
when
with sealed type: 1. You want to check some inheritors and don't care about others 2. You really need to check all inheritors (e.g. you want to switch on all kinds of response from client or smth like that) If you use
when
as expression then in first case you just add some empty
else
, and if someone add new inheritor in future compiler warn you about all cases from 2. which should be fixed. And if
when
was a statement then compiler didn't can distinguish 1. from 2., which might lead to errors when you forgot to process this new inheritor and silently ignored it. So IMO adding
else -> {}
in cases 1. is not much price to keep your code correct in cases when you really need to check them all
m
I see,, my main concern was mainly on whether it breaks OCP not on it's safety(which was strengthened by the new feature) What I understood from what @Albert Chang and @Joffrey said is that this is an old problem and using enums or sealed classes has always broken ocp... I guess this kind of logic is better to be avoided than used (though we can't always do that)
thanks!! It helped me a lot 🙇
j
Yes, so in short using a
when
statement already forces you to check all usages if you add a new subclass or enum entry. Because even with non-exhaustive `when`s, you can't know (without looking) whether the new case should be "ignored" (leaving the
when
as-is) or included as a new branch. The only difference between exhaustive and non-exhaustive is that the former helps find these issues earlier. Note that using an
else
in a
when
statement is almost as bad as a non-exhaustive when.
👍 1
K 1
t
I think you can also read https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html. imo
when
is a form of pattern matching (albeit primitive) and I feel some patterns/principles, while valid, were thought to overcome limitations in the language. the article makes a good example of the visitor pattern being used to overcome the lack of pattern matching in java, which becomes “obsolete” with pattern matching (then it is still a matter of preferences, and of use case).
👀 1
for instance, I think if you tend to add more operations than new implementations, I would probably go with
when
, but if you add more implementations than operations I would choose visitor/polymorphism
🙇 1
🙏 1
m
I still don't like the forced exhaustiveness
Copy code
var x: Int = 0
when(someSealedClass){
    case1 -> x = 3
    case2, case3, case4 -> x = 5
    else -> { }
}
I don't want to mutate the int in some cases, but now I am forced to add an
else -> { }
I also really don't like using if's here
Copy code
var x: Int = 0
if(someSealedClass is case1){
    x = 3
} else if(someSealedClass is case2 || someSealedClass is case3 || someSealedClass is case4){
    x = 5
}
j
Just don't mutate the int, initialize it with a
when
expression instead