Union types

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

3 Likes