Kotlinの yield
関数の明確な定義がわかりません。
上記のリンクの例では、以下についてはあまり言及されていませんが、
val sequence = sequence {
val start = 0
// yielding a single value
yield(start)
// yielding an iterable
yieldAll(1..5 step 2)
// yielding an infinite sequence
yieldAll(generateSequence(8) { it * 3 })
}
println(sequence.take(7).toList()) // [0, 1, 3, 5, 8, 24, 72]
しかし、上記の例は歩留まりの重要性を指摘していません。
yield()
は「戻り、次に停止したところから開始する」と考えることができます。
val sequence = sequence {
val start = 0
yield(start) // first return
yieldAll(1..5 step 2) // if called again, will start from here
yieldAll(generateSequence(8) { it * 3 }) // if called more that six times, start from here
}
ステートマシンなどを作成しますが、Javaのようなものに変換できます。
class Seq {
private AtomicInteger count = new AtomicInteger(0);
private int state = 0;
public int nextValue() {
if (count.get() == 0) {
return state;
}
else if (count.get() >= 1 && count.get() <= 5) {
state += 2;
return state;
}
else {
state *= 3;
return state;
}
}
}
Javaクラスでは、count
とstate
の2つの変数を使用して明示的な状態を維持しています。sequence
とyield
の組み合わせこの状態を暗黙的に維持できるようにします。
シーケンスの次の要素を生成できるが、Javaイテレータを実装する簡単な方法がわからない場合の例を考えてみましょう。
fun fibonacci() = sequence {
var a_0 = 1
var a_1 = 1
// this sequence is infinite
while(true) {
val a_n = a_0 + a_1
a_0 = a_1
a_1 = a_n
//this is a suspend function call
yield(a_0)
}
}
この例では、yield
関数を使用して、シーケンスの次の要素を返します。この関数は、Kotlinのsuspend
関数の例です。関数の呼び出しはsequence{..}
ブロックの実行を一時停止するため、呼び出しスタックは解放されます。
次のようにするとします
fibonacci().take(10).forEach{
println(it)
}
forEach
ループを繰り返すたびに、以前のyield
関数呼び出しからsequence{..}
ブロックが再開され、次のyield
関数呼び出しまで実行されます。実行フローは、forEach
ループの反復とsequence{..}
ブロックの評価を組み合わせます。 Java Iterator
と同じように書いてみて、Kotlinコンパイラが舞台裏で行っていることを感じてみてください。
Kotlinのsuspend
関数は、言語と標準ライブラリ側で最小限に抑えられ、残りはライブラリに実装できます。 kotlinx.coroutines
ライブラリで詳細、例、ドキュメントを確認することをお勧めします https://github.com/Kotlin/kotlinx.coroutines