使用条件
現時点ではpreviewの機能なので、Kotlin 2.1に上げるだけでは使えません。
以下の手順で使えるようになります。
- Android Studio等でK2 modeをonにする
- build.gradleに以下を追加
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xwhen-guards")
}
}
Guard Conditionsとは
Guardとは警備員、みたいな意味で、条件に満たすかどうかを見張る役割があります。
Kotlin2.1で追加されたwhen式のGuard Conditionsの機能により、追加の条件をif
によって書くことができます。
下記のようなクラスを考えます。
enum class ErrorCode {
NOT_FOUND,
UNKNOWN
}
sealed interface Result {
data object Success : Result
data class Error(val code: ErrorCode) : Result
}
このresultをwhen式で判定します。これは従来のやり方です。
when (result) {
Result.Success -> println("Success")
is Result.Error -> {
when (result.code) {
ErrorCode.NOT_FOUND -> println("Error: NOT_FOUND")
ErrorCode.UNKNOWN -> println("Error: UNKNOWN")
}
}
}
Guard Conditionsを使うと、以下のように書けます。
when (result) {
Result.Success -> println("Success")
is Result.Error if (result.code == ErrorCode.NOT_FOUND) -> println("Error: NOT_FOUND")
is Result.Error -> println("Error: UNKNOWN") // これはelse -> でも可
}
Guard Conditionsを使ってwhen式を書くメリットは下記の通りです。
- when式の条件分岐を簡潔にし、可読性を向上させる
- スマートキャストが働いているので、Result.Errorと判定した後に、codeプロパティにアクセスできる
- whenの網羅性が働くので、例えば以下のとき、IDE上でエラーを出してくれる
// コンパイルエラー
when (result) {
Result.Success -> println("Success")
is Result.Error if (result.code == ErrorCode.NOT_FOUND) -> println("Error: NOT_FOUND")
}
見落としているケースがあればエラーを出してくれるので間違いに気づけます。
しかし反面、Guard Conditionsの網羅性は検知しないパターンがあり、以下のような条件を網羅されているコードでもエラーとなってしまうことに注意です。
// 網羅しているのにコンパイルエラーとなってしまう
when (result) {
Result.Success -> println("Success")
is Result.Error if (result.code == ErrorCode.NOT_FOUND) -> println("Error: NOT_FOUND")
is Result.Error if (result.code == ErrorCode.UNKNOWN) -> println("Error: UNKNOWN")
}
ちなみに、Guard Conditionsのifの条件は、引数に関係なくてもよいです。関係ある場合はスマートキャストが働きますが、別に関係なくてもよいです。
when (result) {
Result.Success -> println("Success")
is Result.Error if Math.random() > 0.5 -> println("Error but happy")
else -> println("Error and unhappy")
}
else ifを使う
また、else ifを使うこともできます。
これによって、引数の値を評価することなしに、追加の条件をいきなり使うことができます。
// 別途拡張関数を定義
fun Result.isKnown(): Boolean = when (this) {
Result.Success -> true
is Result.Error -> this.code != ErrorCode.UNKNOWN
}
fun main() {
// 略
when (result) {
Result.Success -> println("Success")
else if result.isKnown() -> println("Error: KNOWN")
else -> println("Error: UNKNOWN")
}
}
先にResultのどちらの型か判定せずとも、Guard Conditionsで条件を判定することができていろいろ使えそうですね。
まぁこの条件だと使う意味は薄いですが、型が増えたり条件が複雑になってくると使えます。
たとえばこんな感じでしょうか。
sealed interface Status {
data object Loading : Status
data class Success(val message: String) : Status
data class Error(val code: ErrorCode) : Status
}
fun Status.hasNoMessage(): Boolean = when (this) {
Status.Success -> this.message.isEmpty()
else -> true
}
fun main() {
// 略
when (status) {
Status.Loading -> println("Loading")
else if status.hasNoMessage() -> println("Error: NO_MESSAGE")
else -> println("Has message")
}
まぁうまい例を出せたかどうかはわかりませんが、複雑な条件を使うときに良さそうです。
なお、else ifの場合も、引数に関係ない条件も入れられます。
まとめ
Guard conditionsの用途として、sealed classを使って複雑な条件を使う、上記のような場合に効力を発揮します。
まだpreviewなので、現時点で使うには設定が必要ですし、最終的に取り入れられるかは様子見ですが、
いい感じなのでデフォルト機能になってくれることに期待です。
おまけ
Guard conditionsの構文として、最初は&&
が提案されていたが、問題が生じて、if
に変更されたとのこと。
Guards Alternative syntax using &&
問題とは、&&
を使うと、以下のような書き方が同じ評価になってしまう。
is Status.Success && info.isEmpty || status is Status.Error
// would be equivalent to
is Status.Success && (info.isEmpty || status is Status.Error)
直感的に見ると最初の&&
が評価される用に見えるが、Guard conditionsがひとまとまりだから、&&
後のinfo.isEmpty || status is Status.Error
の条件が先に評価される。これは直感に反しているので、やめたということです。
こちらのXのポストで上記知ったのですが、Kotlin/KEEP: Kotlin Evolution and Enhancement Processのリポジトリを見ると、Kotlinの機能の提案や採択理由が見れるので、面白いですね。
おまけのおまけ
新機能と関係ないですが、whenでenumかsealed classを使うとき、isがいるかどうかです。
オブジェクトの場合は一致しているかどうかなので、isがいらないです。enumかdata objectはオブジェクトです。
data classは型を判定しているので、isが必要です。
// enum class
val errorCode: ErrorCode
when (errorCode) {
ErrorCode.NOT_FOUND -> println("Error: NOT_FOUND")
else -> println("Error: UNKNOWN")
}
// sealed class
val status: Result
when (status) {
// data object
Result.Success -> println("Success")
// data class
is Result.Error -> println("Error: ${status.code}")
else -> println("Success")
}
上記のErrorCode
とResult
のクラス定義はこちら
参考
Kotlin 2.1.0: Smarter when with Guard Conditions | by Tom Sabel | Medium
Kotlin 2.1.0の新しい言語機能Preview. Kotlin… | by Kenji Abe | Dec, 2024 | Medium