Help us understand the problem. What is going on with this article?

Kotlinのラムダでreturnする

More than 1 year has passed since last update.

最近、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
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away