Matt's Tidbits #62 - When you're feeling exhausted...
Tags: mattstidbits, android, kotlin, tidbittuesday
Published: 2020-03-31 16:41:54

[Last time I wrote about another issue regarding version 3.6.1 of the Android Gradle Plugin.](https://medium.com/@matthew.b.groves/matts-tidbits-61-getting-out-of-a-dependency-cycle-aa07795abbd0) This week, I have some information to share about `when` statements in Kotlin.

The `when` statement in Kotlin is analogous to the `switch` statement in other C-like languages. However, it’s been given a few additional capabilities.

With a `switch` statement, one can only use it to check against primitive types (such as integers), Strings, or enums. In Kotlin, the `when` statement can also be used with sealed classes.

One of the benefits of a sealed class is that it has additional features on top of an enum, but with the same property that all options are defined at compile-time.

On top of this, Kotlin supports a feature known as `exhaustive when expressions`, where if your `when` statement does not list all of the possible options (or use an `else` branch), you will get a compiler error.

From the [Kotlin documentation](https://kotlinlang.org/docs/reference/control-flow.html#when-expression):

*If `when` is used as an expression, the `else` branch is mandatory, unless the compiler can prove that all possible cases are covered with branch conditions (as, for example, with `enum class` entries and `sealed class` subtypes).*

This is a really useful feature in that it allows you to force clients to handle new cases that might be added in the future, potentially preventing future bugs from occurring.

However, it’s important to note that not all uses of the `when` operator are *exhaustive*. For example, the following `when` statement will not have its completeness enforced by the compiler:
```
val x = 5
when(x) {
  1 -> doSomething()
  5 -> doSomethingElse()
}
```

The reason for this is that the result of the `when` is not used by anyone. However, if you instead do the following, the compiler will enforce the completeness:
```
val x = 5
val result = when(x) {
  1 -> doSomething()
  5 -> doSomethingElse()
}
//☝️ Won't compile, because there may be other possible integer values you haven't accounted for!
```

Personally, I find it a little irritating that `when` statements aren’t exhaustive in all cases. And, I think it can be a little cumbersome to have to assign the output of the `when` statement to a variable when you don’t need to use it — this would cause a lint warning if you don’t use `result`, and someone else may later come along and take Android Studio’s suggestion to remove `result`, which will also remove the exhaustive-ness from this statement!

I did some internet searching, and discussed this with my team, and we came up with a few options to help solve this problem:
## Option A
```
// Definition
val Any?.exhaustive get() = Unit
// Usage
when (foo) { ... }.exhaustive
```

**Pros:** really clean syntax, clear, easy to use!
**Cons:** since `exhaustive` is declared as an extension function on the `Any` type, it will be globally suggested as an option to apply to all types by Android Studio, not just `when` statements.

## Option B
```
// Definition
object Do {
  inline infix fun<reified T> exhaustive(any: T?) = any
}
// Usage
Do exhaustive when(foo) { ... }
```

**Pros:** syntax is still pretty clear, doesn’t pollute the global namespace!
**Cons:** a little more verbose than Option A

## Option C
```
// Definition
object Exhaustive {
  operator fun invoke(any: Any?) = any
}
// Usage
Exhaustive(
  when(foo) { ... }
)
```

**Pros:** shorter syntax than Option B, and you get to use the fun `operator invoke` trick!
**Cons:** the extra required parenthesis make this a little clunkier (believe me, I tried everything I could think of to get rid of them)

## Option D
```
val action = when(sealedClass) {
  is sealedClass.A -> ::doSomething
  is sealedClass.B -> ::doSomethingElse
}
action()
```

**Pros:** No funky definitions required, and this is exhaustive!
**Cons:** Does not work if you need to pass parameters to your functions

## Option E
```
val sealedClass = someMethodThatReturnsASealedClass()
handleSealedClassOptions(sealedClass)
fun handleSealedClassOptions(sealedClass: SealedClass) = 
  when(sealedClass) {
    is sealedClass.A -> doSomething(sealedClass)
    is sealedClass.B -> doSomethingElse(sealedClass)
  }
```

**Pros:** Very clean syntax, makes surrounding code easier to read
**Cons:** May have to define a unique method for each call site

## Summary
It’s important to note that there’s not necessarily a \*best\* option here. Each one has its advantages. Personally, I will probably be using Option E in my app, as I like how it will allow me to force exhaustiveness for all uses of a particular type of Sealed Class, and in the places where I’m intending to use this (RxJava `subscribe()` blocks), will be much easier to read.

Note that from talking to the rest of my team, some people feel that in some cases forcing a `when` to be exhaustive may be a bad practice, and you should follow the language’s setup and only have `when` statements be exhaustive in cases where the language supports it (you’re doing something with the resulting value of the `when` statement). So, it’s really one of those things where it just depends on your unique situation/preferences.

I hope you learned something useful about `when` statements! If you have other solutions to this problem, or want to share your preferences/opinion on this topic, leave a comment below! And, please follow me on Medium if you’re interested in being notified of future tidbits.

Interested in joining the awesome team here at Intrepid? [We’re hiring!](https://intrepid.io/careers)

*This tidbit was originally delivered on March 27, 2020.*