The handleB
example wasn’t good as it lacks a default value. Maybe more like that:
inline fun <T> defaultOnError(value: T|Error, default: () -> T & ~Error): T & ~Error =
if (value is Error) default() else value
The resulting value is guaranteed to never extend Error
.
Or like this:
abstract class Error
object NetworkFailure: Error()
object NoSuchUser: Error()
object NotAuthenticated: Error()
class User
fun fetchUser(id: String): User|NetworkFailure|NoSuchUser|NotAuthenticated =
TODO()
fun <Result> process(result: Result|Error, processor: (Result & ~Error) -> Unit) {
if (result is Error)
// handle error
else
processor(result)
}
fun main() {
process(fetchUser("foo")) { user ->
println(user)
}
}
Yes. It’s easy to control as seen in the example above. Just like when
s work with sealed types already:
sealed class Foo {
class A: Foo()
class B: Foo()
class C: Foo()
}
fun handle(foo: Foo) =
when (foo) {
is A -> …
// `foo` is smart-casted to something like `Foo & ~A` here already
}
For removing something from an intersection we could simply rely on generics. There’s no need for a special type:
fun <T> removeB(value: T|B): T = …
The returned value is no longer required to extend B
but it may do.
It’'s analogous to the following which already works:
fun <T> removeNull(value: T?): T = …
Both approaches (generics and T & ~…
) will do. They’re just different. Either we remove the explicit B
from T & B
but the resulting T
may still extend B
. Or we guarantee that the resulting value is in no case B
by stating T & ~B
.