edit: I made a mistake in this article! See Covariance in a Result<T, E> type in Kotlin for corrections.

Kotlin provides a mechanism for sum types in the form of sealed classes. It works by closing the class for extension outside the immediate context, allowing the compiler to know every possible subtype and perform the exhaustive checking expected of the variants of a sum type. However, some typical sum type use cases are a touch awkward with this approach. Let's explore by trying to write a Result<T, E> type in Kotlin using a sealed class.

In languages with first-class sum types, it's common to represent the results of fallible operations using a sum type with one variant for the successful result value, and another variant for errors. Haskell uses the extremely generic Either type; Rust uses the more specific Result. We'll use Rust as our template here, if only because its syntax and semantics are much closer to Kotlin than Haskell's.

Rust's Result type is defined as pub enum Result<T, E> { /// Contains the success value Ok(T), /// Contains the error value Err(E), } Simple enough: a two-variant sum type parameterized by the types of the success and error values. Let's try a naïve transformation of this into Kotlin. We'll have a sealed class with subclasses for the variants: sealed class Result<T, E>() { /** Contains the success value */ data class Ok(val ok: T): Result() /** Contains the error value */ data class Err(val err: E): Result() } Because the variants must be actual classes, they have to be containers for values of the desired types, resulting in the somewhat redundant additional ok and err names, but that's a fairly minor cost. (Also, while I've nested the sealed class variants within their parent class here, this isn't required.) The compiler has this to say about this attempt: Unresolved reference: T 2 type arguments expected for class Result Unresolved reference: E 2 type arguments expected for class Result Oh, okay. The subtypes are technically independent of the parent sealed class (unlike Rust's enum variants), so we'll have to parameterize them separately, and use the type parameters with the parent class constructor. sealed class Result<T, E>() { /** Contains the success value */ data class Ok<T>(val ok: T): Result<T, *>() /** Contains the error value */ data class Err<E>(val err: E): Result<*, E>() } Projections are not allowed for immediate arguments of a supertype Projections are not allowed for immediate arguments of a supertype Oh dear. So we'll need to parameterize the variants with both type parameters used by the supertype. sealed class Result<T, E>() { /** Contains the success value */ data class Ok<T, E>(val ok: T): Result<T, E>() /** Contains the error value */ data class Err<T, E>(val err: E): Result<T, E>() } And now the compiler accepts it. All this information is kind of present in the Rust implementation: any given Ok is a Result, with the values of both type parameters known, even though one is irrelevant for Ok. But specifying it is very redundant. Furthermore, we can do confusing things like sealed class Result<T, E>() { /** Contains the success value */ data class Ok<A, B>(val ok: A): Result<A, B>() /** Contains the error value */ data class Err<C, D>(val err: D): Result<C, D>() } which is A-OK as far as the language is concerned and semantically equivalent to the far clearer version that uses T and E thoughout.

Okay. So we have a Result type, and it seems like it should behave correctly. Let's give it a whirl. fun div(x: Int, y: Int): Result<Int, String> = if (y == 0) { Result.Err("Division by zero") } else { Result.Ok(x / y) } fun main(args: Array<String>) { (0..5) .map { div(5, it) } .forEach { println(it) } } Err(err=Division by zero) Ok(ok=5) Ok(ok=2) Ok(ok=1) Ok(ok=1) Ok(ok=1) Cool. There's some awkwardness, but fundamentally, that worked.

One of the nice things about representing errors as normal values is that we can easily do things with them; for example, chaining processing of valid values while short-circuiting errors. (This is called “monadic error handling”, a term and topic we definitely don't have room to explore deeply here.) Continuing to take inspiration from Rust, we have Result.map to continue processing of an Ok value while propagating Errs. Rust defines Result.map as impl<T, E> Result<T, E> { pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U, E> { match self { Ok(t) => Ok(op(t)), Err(e) => Err(e), } } } This is straightforward: apply the operator to an Ok; pass through an Err. We can do essentially a straight translation into Kotlin: sealed class Result<T, E>() { fun <U> map(op: (T) -> U): Result<U, E> = when (this) { is Result.Ok -> Result.Ok(op(this.ok)) is Result.Err -> Result.Err(this.err) } } fun main(args: Array<String>) { (0..5) .map { div(5, it) .map { it * 2 } } .forEach { println(it) } } Err(err=Division by zero) Ok(ok=10) Ok(ok=4) Ok(ok=2) Ok(ok=2) Ok(ok=2) Nice! Just about the only structural difference is that Kotlin doesn't support destructing matches in when statements, so we have to pull out the values of ok or err in the right-hand expressions.

We don't have to implement map like this, though. Because Ok and Err are themselves classes, they can have member functions independent of their parent class. So we could write it like this: sealed class Result<T, E>() { abstract fun <U> map(op: (T) -> U): Result<U, E> data class Ok<T, E>(val ok: T): Result<T, E>() { override fun <U> map(op: (T) -> U): Result<U, E> = Result.Ok(op(this.ok)) } data class Err<T, E>(val err: E): Result<T, E>() { override fun <U> map(op: (T) -> U): Result<U, E> = Result.Err(this.err) } } Oof. That's a lot of redundant type parameterization. It does work, though! This sort of structure might make sense if the different variants have long, highly divergent implementations. Probably not here, though. “Is subtyping a good idea” is a even more out of scope than discussions of monads, so let's leave it at that.

Let's try another. Rust provides and_then to chain fallible operations; essentially, something like map if op itself could fail and thus returned a Result. sealed class Result<T, E>() { fun <U> andThen(op: (T) -> Result<U, E>): Result<U, E> = when (this) { is Result.Ok -> op(this.ok) is Result.Err -> Result.Err(this.err) } } fun main(args: Array<String>) { (0..5) .map { div(5, it) .map { it - 2 } .andThen { div(2, it) } } .forEach { println(it) } } Err(err=Division by zero) Ok(ok=0) Err(err=Division by zero) Ok(ok=-2) Ok(ok=-2) Ok(ok=-2) That was easy. So, we have a clear demonstration that sealed classes are expressive enough to capture sum type idioms.

Here's an interesting follow-up, though. Result types are used for error handling. Kotlin already uses exceptions for this purpose; so can we get our Result to interoperate nicely with exceptions so someone could plausibly use it? Let's try.

First, we want to be able to transform a Result into a value or a thrown exception. Rust calls the analogous operation unwrap, so we'll go with that name. sealed class Result<T, E>() { fun unwrap(): T = when (this) { is Result.Ok -> this.ok is Result.Err -> when (this.err) { is Throwable -> throw this.err else -> throw RuntimeException(this.err.toString()) } } } The handling of Err values here is a little clunky— Err.err has no restrictions on type. If it's Throwable, throwing it is the obvious thing to do; but if it's some other type, there's not a great way to package it in the standard library types. For expediency here, we'll stringify it and throw it in a RuntimeException; but a better solution might be a custom exception type that can store an Any? as its cause.

The other function we need is one that runs a potentially throwing operation and wraps the return value or exception in a Result. fun <T> wrapResult(op: () -> T): Result<T, Exception> = try { Result.Ok(op()) } catch (e: Exception) { Result.Err(e) } (We're only catching Exception, and not Throwable, because Errors in general should not be caught.) fun main(args: Array<String>) { (0..5) .map { wrapResult { 5 / it } .map { it - 2 } .andThen { wrapResult { 2 / it } } } .forEach { println(it) } } Err(err=java.lang.ArithmeticException: / by zero) Ok(ok=0) Err(err=java.lang.ArithmeticException: / by zero) Ok(ok=-2) Ok(ok=-2) Ok(ok=-2) Nifty. fun main(args: Array<String>) { (0..5) .reverse() .map { div(5, it) .map { it - 2 } .andThen { div(2, it) } } .forEach { println(it.unwrap()) } } -2 -2 -2 Exception in thread "main" java.lang.RuntimeException: Division by zero

I'll leave the question of whether handling fallible operations this way in Kotlin codebases is a good idea for another time (but if you want to explore it, start here: it is highly unidiomatic). It's neat to show that it's clearly possible and reasonably ergonomic, though! The sample code here is available in Example.kt. I may at some point consider completing the implementation, if only for curiosity's sake; to my slight surprise, it doesn't seem anyone has done this yet.