Edited at

Kotlin: なぜ while, for では break が使えて forEacn, repeat では使えないのか

Kotlin の while, for では break, continue が使えますが、forEach, repeat では使えません。

forEach, repeat は いずれも for で書き換えられるので、書き換えてしまえば break, continue が使えます。



  • something.forEach => for (it in something)


  • repeat (time) => for (it in 0..time - 1)

なぜこうなっているのか気になりますね。

(コルーチンの内部だと書き換えられない場合もあります。)


なぜなのか

(現実をいってしまえば Kotlin がそう作られているからなのですが、)

while, forforEach, repeat には大きな違いがあります。



  • while, for は Kotlin の構文


  • forEach, repeat は Kotlin の関数

似た記法になっているのでわかりにくくなっていますが、 while, for は構文として、 break, continue が使えるように設計されています。

一方で、 forEach, repeat は Kotlin の標準ライブラリで定められた高階関数です。



releat (3) {

// this is function
}

つまり { ... } は関数です。

関数の中で急に breakcontinue が出てくるのは変ですbreak, continue は使えません。


イテレーションをコントロールする他の方法はないのか

forEach, repeat などの高階関数で、 ブロック部分を抜けるには、 return を使います。

関数ですから return で終了するのは自然に思えますね。

しかし、 return をそのまま使うと、 呼び出し元の関数を終了してしまいます。

fun something() {

listOf(1, 2, 3).forEach {
println(it)
return // something を終了する
}
println("finish")
}

そこで、ラベルを使います。


continue のような処理


ラムダを使う

return@ラムダのラベル でラムダの continue を行うことができます。

fun something() {

listOf(1, 2, 3).forEach {
println(it)
return@forEach // ブロック内の処理はここまで
println(it)
}
println("finished")
}

このラベルは自分でつけることもできて ラベル名@{ ... } のように記述します。


もし forEach が二重だったら

二重で forEach がある場合は、ラベルを自分でつけます。

次のコードは外側のループに対して continue を行なっています。


Kotlin-lambda

fun something() {

listOf(1, 2, 3).forEach outer@{ a ->
listOf(4, 5, 6).forEach { b ->
print(a)
return@outer
println(b)
}
}
println("finished")
}


無名関数を使う

ラムダを無名関数に変えれば、 単純に return で同じことができます。


Kotlin-anonymousfunction

fun something() {

listOf(1, 2, 3).forEach(fun(value: Int) {
println(value)
return // ブロック内の処理はここまで
println(it)
})
}

jQuery でも似たようなコードを見たことがあります。


JavaScript

$("xxx").each(function  () {

/* Code */
if (x) return;
/* Code */
});


break のような処理


ラムダを使う

外側をスコープ関数の run で囲んで、 そのラムダを抜けるように return@run と記述します。


Kotlin-lambda

fun something() {

run {
listOf(1, 2, 3).forEach {
println(it)
return@run // ループはここでおわり
println(it)
}
}
println("finished")
}

見てわかるように、 高階関数は break を前提に作られていません。


無名関数を使う

ラムダと同じように run などのスコープ関数を使って return@run とします。


Kotlin-anonymousfunction

fun something() {

run {
listOf(1, 2, 3).forEach(fun() {
println(it)
return@run // ループはここでおわり
println(it)
})
}
println("finished")
}


まとめ

上に記述したのをまとめると次のようになります。

break
continue

ラムダ
label
label

無名関数
flag and return, label
return