9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Kotlinのラムダでreturnする

Posted at

最近、Kotlin+Spring BootでRESTful APIを書いてます。(とは言っても片手間でやっているので遅々として進みませんが)
コード書いていて「おやっ?」って思った表題の件について取り上げてみます。

お題とするコード

あんまり良い例が思いつかなったので、相当違和感あるコードかもしれませんがご勘弁ください。

以下の様なクラスのコレクションがあります。

data class Country(val name: String, val continent: String,  val population: Long)

val counties = listOf(
        Country("France", "Europe", 67_158_000),
        Country("Germany", "Europe", 82_349_400),
        Country("United Kingdom", "Europe", 65_648_000),
        Country("Japan", "Asia", 126_672_000),
        Country("India", "Asia", 1_324_171_354),
        Country("China", "Asia", 1_403_500_365)
)

リストに指定した国名が含まれているかをチェックする関数を書きます。標準関数で事足りる話ですが、あえて冗長なコードを書きます。

fun isContainCountry(countries: List<Country>, name: String): Boolean {
    var filtered = countries.filter { c ->
        println(c.name)
        c.name == name
    }
    println("(Country)$filtered")

    return !filtered.isEmpty()
}

関数を呼び出してみると以下の様な出力になります。

>>> println("[RESULT]${isContainCountry (counties, "Japan")}")
France
Germany
United Kingdom
Japan
India
China
(Country)[Country(name=Japan, continent=Asia, population=126672000)]
[RESULT]true
>>> println("[RESULT]${isContainCountry (counties, "United States")}")
France
Germany
United Kingdom
Japan
India
China
(Country)[]
[RESULT]false

ここからが本題

大した話でもないのに前置きが長くなりましたが本題です。

前の関数から少しだけ変更します。

fun isContainCountryNG(countries: List<Country>, name: String): Boolean {
    var filtered = countries.filter { c ->
        println(c.name)
        return c.name == name  // returnをつける
    }
    println("(Country)$filtered")

    return !filtered.isEmpty()
}

これを実行すると、

>>> println("[RESULT]${isContainCountryNG (counties, "Japan")}")
France
[RESULT]false

意図したような挙動になりません。

ラムダ内のreturnは、そのラムダを呼び出した関数からのリターンを意味します。(非ローカルリターンと呼ぶそうです)
つまり、いきなりisContainCountryNG関数の処理が終わってしまうことになります。
このような挙動になる理由は、filter関数がインライン展開されるからです。
filterの定義を見てみると、inline修飾子がついています。

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>

インライン展開されたコードを思い浮かべれば、その中のreturnにより関数からリターンされるということがイメージできます。

C#のラムダだとこういうケースではreturnがないとコンパイルエラーだったように思います。ので、ついreturnを書いてしまうこともあるかもしれません。

以下の様に書くと、この非ローカルリターンもしっくりくると思います。(そもそも標準関数を使えば良いという話はさておいて)

fun isContainCountryForEach(countries: List<Country>, name: String): Boolean {
    countries.forEach { c ->
        println(c.name)
        if (c.name == name) return true
    }

    return false
}

実行結果は、

>>> println("[RESULT]${isContainCountryForEach (counties, "Japan")}")
France
Germany
United Kingdom
Japan
[RESULT]true

となります。

ローカルリターン

非ローカルリターンに対してローカルリターンというのが当然あります。
ローカルリターンにする方法は2つあります。

ラベル付きreturn

fun isContainCountryLR(countries: List<Country>, name: String): Boolean {
    var found = false
    countries.forEach { c ->
        if (c.name == name) {
            println("I found it!")
            found = true
            return@forEach  // ラベル付きリターン
        }
        println(c.name)  // returnするとこの行は実行されない
    }
    println("Exit...")

    return found
}

無名関数

fun isContainCountryAF(countries: List<Country>, name: String): Boolean {
    var found = false
    countries.forEach ( fun(c) {  // 無名関数
        if (c.name == name) {
            println("I found it!")
            found = true
            return
        }
        println(c.name)  // returnするとこの行は実行されない
    })
    println("Exit...")

    return found
}

どちらも出力結果は同じです。

>>> println("[RESULT]${isContainCountryLR (counties, "Japan")}")
France
Germany
United Kingdom
I found it!
India
China
Exit...
[RESULT]true
9
3
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
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?