[Swift] シンプルなカウントアップでSequenceに強くなる

少年は言った。

「0から100までカウントアップしたい！！」

よし、じゃあ今日は0から100までの整数値を出力する、Sequenceの仲間たちを紹介するよ！
一緒にSequenceに強くなろう！:muscle:

# Sequenceのチカラ
まずはSequenceとは何かということと、そのパワーについて知りましょう。

### Sequenceとは
**Sequence**とは、標準ライブラリのの中にある以下のようなprotocolです。

```
public protocol Sequence {
    public func makeIterator() -> Self.Iterator
}
```

**makeIterator**を実装することによりSequenceに準拠することができます。Self.Iteratorは**IteratorProtocol**を実装するクラスもしくは構造体で、makeIteratorは単にIteratorProtocolに準拠するインスタンスを返す関数です。IteratorProtocolとは以下のようなprotocolです。

```
public protocol IteratorProtocol {
    public mutating func next() -> Self.Element?
}
```

Self.Elementはイテレートする任意の要素型です。IteratorProtocolはnext()を呼ぶたびに「次の要素」を返し、最後の要素の次にnilを返すように実装します。

### Sequenceに準拠するといいこと
Sequenceに準拠することにより、`contains`や`forEach`, `map`, `filter`, `reduce`などなど、多数の便利な関数を利用できるようになります。うまく使えば強力な独自クラスをつくることも可能です。

### Sequenceとfor-in
さらに、SwiftではSequenceプロトコルに準拠することにより、**for-inでイテレートできる**ようになります。標準ライブラリでよく使うArrayやDictionaryなどもSequenceプロトコルに準拠しているため、for-inでイテレートできるようになっています。以下の例はArrayをfor-inでイテレートしています。

```
let numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for i in numbers {
    print("\(i)") // 0, 1, 2...
}
```

さて、ここからは0から100までを出力する様々なSequenceを作る方法を紹介していきます。

# カウントアップするSequenceを作る様々な方法
## Closed Range Operatorを使う
おそらく最も一般的な方法はRange Operatorを使う方法です。

```
for i in 0...100 {
    print("\(i)") // 0, 1, 2...
}
```

なにも問題ありません。シンプルで過不足無く美しいコードです。

`0...100`は**CountableClosedRange**インスタンスを生成します。これはstructであり、Sequenceに準拠しているためfor-inでイテレートできます。

#### SequenceとCollectionの違い
実際のところ、CountableClosedRangeは単なるSequenceではなく、さらに高機能な**RandomAccessCollection**に準拠しています。SequenceとCollectionはfor-inの挙動において**抑えておきたい違いが2点あります**。

1. Sequenceは一度イテレートすると二回め以降は同じようにイテレートできることが保証されていませんが、Collectionは**何度もイテレート可能**です
2. SequenceはIteratorの実装によっては無限に続く可能性もありますが、Collectionは添字アクセスを可能にするため、**有限であることが保証**されています

詳しくは公式リファレンスの "Traversing a Collection" を参照してください。
https://developer.apple.com/reference/swift/collection

## Sequenceを自分で実装する
### SequenceとIteratorProtocolを実装する
自分でSequenceを実装する方法をご紹介します。

```
struct CountUp: Sequence, IteratorProtocol {
    private var count = 0
    mutating func next() -> Int? {
        defer { count += 1 }
        return count <= 100 ? count : nil
    }
}

for i in CountUp() {
  print("\(i)") // 0, 1, 2...
}
```

IteratorProtocolとSequenceを別々に実装することもできますが、こちらのほうが同時に実装できてシンプルですね。しかもこの場合`makeIterator()`はデフォルト実装を利用できるため、自分で実装する必要はありません。

公式でもこの書き方が例として紹介されています。
https://developer.apple.com/reference/swift/sequence

### AnySequenceを使う
**AnySequence**を使うと、**具体的なクラスを作ることなく独自処理のSequenceを実装**することができます。

```
let countUp = AnySequence { () -> AnyIterator<Int> in
    var count = 0
    return AnyIterator {
        defer { count += 1 }
        return count <= 100 ? count : nil
    }
}

for i in countUp {
    print("\(i)") // 0, 1, 2...
}
```

AnySequence自体はSequenceの具体的な型を隠蔽して扱う(Type Erasure)ためのクラスですが、今回のように一時的な**無名Sequenceを作る**用途でも利用できます。初期化時にmakeIteratorの実装をクロージャで渡すようなイメージです。

## StrideThroughを使う
**StrideThrough**というSequenceがあります。`stride(from:through:by:)`関数を用いて、指定した閉区間をストライドで区切ったSequenceを作ることができます。

```
let countUp = stride(from: 0, through: 100, by: 1)
for i in countUp {
    print("\(i)") // 0, 1, 2...
}
```

byに2を渡せば 0, 2, 4, 6... と1つ飛ばしにもできますし、`stride(from: 100, through: 0, by: -1)`のようにマイナスを渡せば 100, 99, 98... と減っていくようにもできます。

また、半開区間をストライドで区切る**StrideTo**と言うものあります。そちらは`stride(from:to:by:)`を使います。StrideThroughと違い、toで指定した値は出力されません。

## UnfoldSequenceを使う
**UnfoldSequence**というSequenceがあります。カウントアップは以下のように書けます。

```
let countUp = sequence(first: 0) { prev -> Int? in
    return prev >= 100 ? nil : prev + 1
}

for i in countUp {
    print("\(i)") // 0, 1, 2...
}
```

prevは１つ前に出力した値です。UnfoldSequenceは1つ前の値を保持しており、**1つ前の値から次の値を計算して出力**しています。今回は単に1つ前の値に1を足して次の出力としています。

ただし、UnfoldSequence自体は単に1つ前の値だけを保持するだけのものではありません。より正確に言えば、UnfoldSequenceは**イテレーションを通して状態を持ち、出力する要素を毎回計算する**Sequenceです。イテレーションを通して状態を持ち続けたい場合は`sequence(state:next:)`という関数を使います。`state`には自由な値を設定でき、イテレーションを通してアクセスすることができます。

UnfoldSequenceは使いどころが難しいですが、無限に続く数列を定義したり、木構造の親を辿っていくような再帰的な処理に使ったり、2つのイテレータを交互に出力したりなど、工夫次第で様々な用途で使えるようです。

# まとめ
さまざまな方法でカウントアップしてみましたが、いかがでしたでしょうか。
Sequenceに強くなれたかな？:angel:

---
:whale2: Twitter: [@a_beco](https://twitter.com/a_beco)
:guitar: SoundCloud: [@beco](https://soundcloud.com/baron1_3) <- 作業用BGMにどうぞ！