1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kotlin 2.1からのwhen式のGuard Conditionsに入門する

Last updated at Posted at 2025-02-16

使用条件

現時点ではpreviewの機能なので、Kotlin 2.1に上げるだけでは使えません。
以下の手順で使えるようになります。

  1. Android Studio等でK2 modeをonにする
  2. 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")
}

上記のErrorCodeResultのクラス定義はこちら

参考

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

Conditions and loops | Kotlin Documentation

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?