【Kotlin】Sequenceオブジェクト生成方法7種

Kotlin の [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトの生成方法を7種紹介します。
（2019-04-06 1種追加して6種に。）
（2019-04-13 1種追加して7種に。）

**この記事で最もお伝えしたいのは、最も強力でありながら最も知られていないと思われる、[`sequence` 関数](#sequence-関数-1) を使う方法です。**
ぜひそこをお読みいただいて、活用していただければと思います。

## [`asSequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/as-sequence.html) 拡張関数

[`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) を使ったことがある方は皆さんご存知でしょう。
[`List`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/index.html) インターフェイスなどが継承している [`Iterable`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-iterable/index.html) インターフェイスや、 [`Array`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-array/index.html) や [`IntArray`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int-array/index.html) などの配列系クラス。それらの拡張関数 [`asSequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/as-sequence.html) を使うことで、それらを [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) 化できます。

```Kotlin
    val sequence = listOf('A', 'B', 'C').asSequence()
    sequence.forEach { print(it) } // > ABC
```

なお、関数名は `toSequence` ではなく `asSequence` です。
[`asSequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/as-sequence.html) 関数で生成した [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトには、元となるオブジェクトへの変更が反映されます。

```Kotlin
    val mutableList = mutableListOf('A', 'B')
    val sequence = mutableList.asSequence()
    mutableList += 'C'
    sequence.forEach { print(it) } // > ABC
```

[`toList`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/to-list.html) 関数などでは、元となるオブジェクトへの変更が反映されません。
この違いが関数名に現れているのでしょう。


## [`sequenceOf`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/sequence-of.html) 関数

引数で与えられた要素から [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトを生成します。

```Kotlin
    val sequence = sequenceOf('A', 'B', 'C')
    sequence.forEach { print(it) } // > ABC
```

## [`emptySequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/empty-sequence.html) 関数

空（要素数が 0）の [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトを生成する関数です。
[シングルトンになっている](https://github.com/JetBrains/kotlin/blob/v1.3.30/libraries/stdlib/src/kotlin/collections/Sequences.kt#L40)ため軽量です。

ですが、引数なしで [`sequenceOf`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/sequence-of.html) を使ってしまっても構いません。
[要素数が 0 の場合は自動的に `emptySequence()` が呼ばれるようになっています](https://github.com/JetBrains/kotlin/blob/v1.3.30/libraries/stdlib/src/kotlin/collections/Sequences.kt#L35)。


## [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) インターフェイスの拡張関数

[`filter`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/filter.html) や [`map`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/map.html) などの、[`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトを加工して別の [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトを生成する、[`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) インターフェイスの拡張関数です。
コレクション系の型の拡張関数と同じ感覚で使えます。

```Kotlin
    val sequence = sequenceOf(0, 1, 2)
        .map { 'A' + it }
    sequence.forEach { print(it) } // > ABC
```


## [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence.html) 関数（フェイクコンストラクター）

[`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) 型はインターフェイスなのでコンストラクターを持ちませんが、この[`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence.html) 関数をコンストラクター代わりに使うことができます。

```Kotlin
    class CharIterator : Iterator<Char> {
        private var char = 'A'
        override fun hasNext(): Boolean = char <= 'C'
        override fun next(): Char = char++
    }

    val sequence = Sequence { CharIterator() }
    sequence.forEach { print(it) } // > ABC
```

[`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) インターフェイスはメンバとして、[`Iterator`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-iterator/index.html) オブジェクトを返す [`iterator`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/iterator.html) 関数だけを持っています。
[`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) 関数は引数として受け取った関数を、その [`iterator`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/iterator.html) 関数の実体として使用します。


## [`generateSequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/generate-sequence.html) 関数

要素を生成する関数[^1]から [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトを生成します。
関数が `null` を生成すると終端するため、要素に `null` を含む [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトは生成できません。
また、生成した [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトは一度しか使えません。
このような制約があるものの、ある程度自由に手軽に [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) を作成できます。

[^1]: ラムダ式を使う場合がほとんどでしょう。

```Kotlin
    var char = 'A'
    val sequence = generateSequence {
        if (char > 'C') null
        else char++
    }
    sequence.forEach { print(it) } // > ABC
    sequence.forEach { print(it) } // IllegalStateException をスローする。
```


## [`sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/sequence.html) 関数

**本記事で最もお伝えしたい方法です。**
任意の [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) を簡単に作成できます。

```Kotlin
    val sequence = sequence {
        var char = 'A'
        while(char <= 'C') {
            yield(char++)
        }
    }
    sequence.forEach { print(it) } // > ABC
    sequence.forEach { print(it) } // > ABC
```

### 使い方

[`sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/sequence.html) 関数の引数である関数（以下、 `block` と表記）の中で [`yield`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence-scope/yield.html) 関数を呼び出すごとに、[`yield`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence-scope/yield.html) 関数に引数として渡した値が、生成した [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトの要素となります。
`block` の処理が終了すると終端します。

[`yield`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence-scope/yield.html) 関数を呼び出して要素を生成すると、次の要素が要求されるまで `block` の処理は停止します。
そのため、終わりがない数列を表す [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトを生成するような場合でも、必要な計算のみが行われます。

### 解説

`block` は [`SequenceScope `](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence-scope/index.html) クラスをレシーバーとする拡張関数です。
したがって、`block` 内では `this.yield`（もしくは `this.` を省略して `yield`）とすることで、[`SequenceScope#yield`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence-scope/yield.html) メソッドを呼び出せます。

[`yield`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence-scope/yield.html) 関数は中断関数（suspending function）です。
中断関数は、呼び出されてから処理を返すまでの間に処理を「中断」することができます。
「中断」すると、続きの処理は非同期で行われることになります。
中断関数は Kotlin 1.3 で正式に採用された Coroutines の機能に関連するものです。詳しくはこの辺りをご覧ください。→ [Coroutines Overview](https://kotlinlang.org/docs/reference/coroutines-overview.html)

### 例：フィボナッチ数列

```Kotlin
    val fibonacci = sequence {
        var a = 0
        var b = 1
        while (true) {
            yield(b)
            val c = a + b
            a = b
            b = c
        }
    }
    
    fibonacci
        .take(10)
        .forEach { println(it) }
```

<details>
<summary>出力</summary>
```
1
1
2
3
5
8
13
21
34
55
```
</details>


## おわりに

以上、[`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html) オブジェクトの生成方法を6種紹介しました。
一通り網羅したつもりですが、他にもあればご教授ください。