fn foo () -> A | B | C {
if condition {
bar();
} else {
baz();
}
}
fn bar() -> A | B {
...
}
fn baz() -> B | C {
...
}
vs fn foo () -> Either<A, Either<B, C> {
if condition {
match bar() {
Either::Left(a) => Either::Left(a),
Either::Right(b) => Either::Right(Either::Left(b)),
}
} else {
match baz() {
Either::Left(b) => Either::Right(Either::Left(b),
Either::Right(c) => Either::Right(Either::Right(b)),
}
}
}
fn bar() -> Either<A, B> {
...
}
fn baz() -> Either<B, C> {
...
}
The latter code composes poorly and requires an extra branch at runtime. It is fundamentally more complex to dispatch on nested discriminated unions instead of flat non-discriminated unions both for the programmer to write, read, and for the runtime to execute.The compiler can also optimize the representation of the anonymous enum based on the context in which its created, whereas its more difficult to do that in the discriminated case.
This isn't a controversial opinion, there are mountains of Typescript written in this style.