# 機能設計書 50-Enumerator::Chainクラス

## 概要

本ドキュメントは、mruby-enum-chain gemが提供するEnumerator::Chainクラスの機能設計を記述する。複数のEnumerableオブジェクトを連結し、順次イテレーションを行うチェーンEnumeratorを提供する。

### 本機能の処理概要

mruby-enum-chain gemは、Enumerator::ChainクラスとEnumerable#chainメソッド、Enumerator#+メソッドを提供する。全てRuby(mrblib/chain.rb)で実装されている。Chainは複数のEnumerableオブジェクトを順番に連結し、あたかも単一のコレクションであるかのようにイテレーションを行う。各Enumerableのeachメソッドを順次呼び出すことで実現される。

**業務上の目的・背景**：複数のデータソース（配列、Range、Enumerator等）を統合的に扱いたい場面は多い。Chainクラスにより、データソースの種類を意識せず、連結された単一のシーケンスとして処理できる。flattenとは異なり、各要素を展開せずそのまま順次イテレーションする点が特徴である。

**機能の利用シーン**：異なる型のコレクションを順次処理する場合（`[1,2,3].chain(4..6).to_a #=> [1,2,3,4,5,6]`）、Enumerator同士を`+`演算子で連結する場合（`e1 + e2`）、複数データソースのストリーム処理に使用される。

**主要な処理内容**：
1. Enumerable#chain - 複数Enumerableを連結したChainを生成
2. Enumerator#+ - 2つのEnumeratorを連結したChainを生成
3. Chain#initialize - @enumsに引数をfreezeして保持
4. Chain#each - 各Enumerableを順次eachで処理
5. Chain#size - 全Enumerableのsizeの合計（不明ならnil）
6. Chain#rewind - イテレーション済みEnumerableを逆順にrewind
7. Chain#+ - ChainにさらにEnumerableを連結
8. Chain#inspect - 文字列表現を返す

**関連システム・外部連携**：mruby-enumeratorに依存する（Enumeratorクラスの利用）。

**権限による制御**：なし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能はライブラリ機能であり、画面との関連はない |

## 機能種別

ライブラリ拡張（複数Enumerable連結イテレーション）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| *args (initialize) | Enumerable[] | No | 連結対象のEnumerableオブジェクト群（可変長） | eachメソッドを持つ（each呼び出し時に検証） |
| block (each) | Block | No | 各要素に対するブロック | ブロック未指定時はEnumerator返却 |
| other (+) | Object | Yes | 連結対象のオブジェクト | 制約なし（each呼び出し時に検証） |
| *enums (chain) | Enumerable[] | No | 連結対象（可変長） | 制約なし（each呼び出し時に検証） |

### 入力データソース

メソッド呼び出し時の引数およびブロック

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Chain生成戻り値 | Enumerator::Chain | チェーン連結されたEnumerator |
| each戻り値（ブロックあり） | Enumerator::Chain | 自身(self)を返す |
| each戻り値（ブロックなし） | Enumerator | to_enum(:each)で生成されたEnumerator |
| size戻り値 | Integer/nil | 全Enumerableのsize合計、不明ならnil |
| rewind戻り値 | Enumerator::Chain | 自身(self)を返す |
| inspect戻り値 | String | 文字列表現 |

### 出力先

メソッドの戻り値として呼び出し元に返却

## 処理フロー

### 処理シーケンス

```
1. enum.chain(*enums) / e1 + e2
   └─ Enumerator::Chain.new(self, *enums) / Chain.new(self, other)
       └─ @enums = args.freeze, @pos = -1

2. chain.each(&block)
   ├─ ブロックなし → return to_enum(:each)
   └─ ブロックあり
       ├─ i = 0
       ├─ while i < @enums.size
       │   ├─ @pos = i（現在の位置を記録）
       │   ├─ @enums[i].each(&block)
       │   └─ i += 1
       └─ return self

3. chain.size
   └─ @enums.reduce(0) { |a, e|
           return nil unless e.respond_to?(:size)
           a + e.size
       }

4. chain.rewind
   └─ while 0 <= @pos && @pos < @enums.size
       ├─ e = @enums[@pos]
       ├─ e.rewind if e.respond_to?(:rewind)
       └─ @pos -= 1

5. chain + other
   └─ self.class.new(self, other)
```

### フローチャート

```mermaid
flowchart TD
    A[Enumerable#chain / Enumerator#+] --> B[Chain.new *enums]
    B --> C["@enums = args.freeze, @pos = -1"]

    C --> D{each呼び出し}
    D -->|ブロックなし| E[to_enum :each]
    D -->|ブロックあり| F["i = 0"]
    F --> G{"i < @enums.size?"}
    G -->|Yes| H["@pos = i"]
    H --> I["@enums[i].each &block"]
    I --> J["i += 1"]
    J --> G
    G -->|No| K[return self]

    C --> L{size呼び出し}
    L --> M["@enums.reduce(0)"]
    M --> N{"respond_to? :size?"}
    N -->|No| O[return nil]
    N -->|Yes| P["a + e.size"]
    P --> Q[return 合計値]

    C --> R{rewind呼び出し}
    R --> S["@pos から 0 まで逆順"]
    S --> T{"respond_to? :rewind?"}
    T -->|Yes| U[e.rewind]
    T -->|No| V[スキップ]
    U --> W["@pos -= 1"]
    V --> W
    W --> X[return self]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-50-01 | 順次イテレーション | eachは@enumsの各要素を先頭から順にeachで処理する | each呼び出し時 |
| BR-50-02 | 位置追跡 | @posで現在イテレーション中のenumのインデックスを追跡する | each実行中 |
| BR-50-03 | 遅延検証 | 連結対象がeachメソッドを持つかは、実際にeach呼び出し時に検証される | each実行時 |
| BR-50-04 | size合算 | sizeは全要素がsizeメソッドを持つ場合のみ合計を返す。1つでもsizeを持たなければnilを返す | size呼び出し時 |
| BR-50-05 | 逆順rewind | rewindはイテレーション済みのEnumerableを逆順にrewindする | rewind呼び出し時 |
| BR-50-06 | @enums凍結 | initializeで@enumsをfreezeし、後から変更不可にする | initialize時 |
| BR-50-07 | +演算子チェーン | +は新しいChainを生成し、自身とotherを連結する | +呼び出し時 |

### 計算ロジック

sizeの計算: `@enums.reduce(0) { |a, e| return nil unless e.respond_to?(:size); a + e.size }` - 全要素のsizeの合計値。いずれかがsizeを持たなければnilを即座に返す。

## データベース操作仕様

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | NoMethodError | eachメソッドを持たないオブジェクトが@enumsに含まれ、each実行時に到達した場合 | Enumerableオブジェクトのみを連結する |

### リトライ仕様

リトライは不要

## トランザクション仕様

該当なし

## パフォーマンス要件

Chainは単純なループによる順次イテレーションであるため、オーバーヘッドは最小限。@enumsの各要素に対してeachを呼ぶだけであり、中間データ構造を生成しない。sizeはrespond_to?チェックを含むreduce操作であり、@enums数に比例するO(n)。

## セキュリティ考慮事項

@enumsをfreezeすることで、初期化後の改ざんを防止している。特段のセキュリティリスクはない。

## 備考

- Enumerator::ChainはEnumerableをincludeしており、map/select/to_a等のEnumerableメソッドが使用可能
- Chain#+はself.class.newを使用するため、サブクラスでも正しいクラスのインスタンスが返る
- rewindはイテレーションされたEnumerableのみを対象とし、未到達のEnumerableはrewindしない
- @posは初期値-1で、eachのループ内で更新される
- eachメソッドを持たないオブジェクトもChain.newで受け入れるが、each呼び出し時にNoMethodErrorが発生する
- mruby-enumeratorに依存している

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | chain.rb | `mrbgems/mruby-enum-chain/mrblib/chain.rb` | Chainクラスの定義位置（37行目）、Enumerable include（38行目） |

**読解のコツ**: ChainクラスはEnumeratorクラスの内部クラスとして定義されている。Enumerableをincludeすることで、to_a, map, select等の標準メソッドが利用可能になる。内部状態は@enums（連結対象配列、frozen）と@pos（現在のイテレーション位置）の2つのインスタンス変数のみで、非常にシンプルな構造である。

#### Step 2: 生成メソッドを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | chain.rb | `mrbgems/mruby-enum-chain/mrblib/chain.rb` | Enumerable#chain（16-18行目）、Enumerator#+（33-35行目） |

**主要処理フロー**:
- **16-18行目**: `Enumerable#chain(*args)` - `Enumerator::Chain.new(self, *args)`でselfを先頭に含むChainを生成
- **33-35行目**: `Enumerator#+(other)` - `Chain.new(self, other)`で2つのEnumeratorを連結

#### Step 3: initializeを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | chain.rb | `mrbgems/mruby-enum-chain/mrblib/chain.rb` | initialize（50-53行目） |

**主要処理フロー**:
- **50行目**: `def initialize(*args)` - 可変長引数で任意個数のEnumerableを受け取る
- **51行目**: `@enums = args.freeze` - 引数配列をfreezeして保持（不変性の保証）
- **52行目**: `@pos = -1` - イテレーション位置の初期値（未イテレーション状態）

#### Step 4: eachメソッドを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | chain.rb | `mrbgems/mruby-enum-chain/mrblib/chain.rb` | each（68-79行目） |

**主要処理フロー**:
- **69行目**: `return to_enum(:each) unless block` - ブロック未指定時はEnumeratorを返す
- **71行目**: `i = 0` - ループカウンタ初期化
- **72行目**: `while i < @enums.size` - 全Enumerable分ループ
- **73行目**: `@pos = i` - 現在のイテレーション位置を記録（rewindで使用）
- **74行目**: `@enums[i].each(&block)` - 各Enumerableのeachにブロックを委譲
- **78行目**: `self` - メソッドチェーン可能にselfを返す

#### Step 5: size, rewind, +, inspectを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | chain.rb | `mrbgems/mruby-enum-chain/mrblib/chain.rb` | size（91-96行目）、rewind（111-119行目）、+（132-134行目）、inspect（145-147行目） |

**主要処理フロー**:
- **91-96行目**: `size` - reduceで全sizeを合算。respond_to?(:size)がfalseならnilを即時return
- **111-119行目**: `rewind` - @posから0まで逆順にループし、rewindメソッドを持つものだけrewind呼び出し
- **132-134行目**: `+` - `self.class.new(self, other)`で新しいChainを生成
- **145-147行目**: `inspect` - `"#<#{self.class}: #{@enums.inspect}>"`の形式で文字列表現

### プログラム呼び出し階層図

```
Enumerable#chain(*args)
    |
    +-- Enumerator::Chain.new(self, *args)
            |
            +-- initialize(*args)
            |       +-- @enums = args.freeze
            |       +-- @pos = -1
            |
            +-- each(&block)
            |       +-- to_enum(:each)  [ブロックなし]
            |       +-- while i < @enums.size
            |               +-- @pos = i
            |               +-- @enums[i].each(&block)
            |
            +-- size
            |       +-- @enums.reduce(0)
            |               +-- e.respond_to?(:size) チェック
            |               +-- a + e.size
            |
            +-- rewind
            |       +-- while @pos >= 0
            |               +-- e.rewind if e.respond_to?(:rewind)
            |               +-- @pos -= 1
            |
            +-- +(other)
            |       +-- self.class.new(self, other)
            |
            +-- inspect
                    +-- "#<#{self.class}: #{@enums.inspect}>"

Enumerator#+(other)
    |
    +-- Chain.new(self, other)
```

### データフロー図

```
[入力]                            [処理]                           [出力]

Enumerable + *args        --> Enumerable#chain              --> Chain
Enumerator + other        --> Enumerator#+                  --> Chain
Chain + block             --> Chain#each                    --> Chain (self)
Chain (ブロックなし)        --> Chain#each                    --> Enumerator
Chain                     --> Chain#size                    --> Integer / nil
Chain                     --> Chain#rewind                  --> Chain (self)
Chain + other             --> Chain#+                       --> Chain (新規)
Chain                     --> Chain#inspect                 --> String

[連結イテレーション例]
[1,2].chain(3..5, [6])
  --> @enums = [[1,2], 3..5, [6]]
  --> each: [1,2].each -> 3..5.each -> [6].each
  --> 結果: [1, 2, 3, 4, 5, 6]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| chain.rb | `mrbgems/mruby-enum-chain/mrblib/chain.rb` | ソース | Chain全メソッドのRuby実装（149行） |
| mrbgem.rake | `mrbgems/mruby-enum-chain/mrbgem.rake` | 設定 | gem定義ファイル（mruby-enumerator依存） |
| enum_chain.rb | `mrbgems/mruby-enum-chain/test/enum_chain.rb` | テスト | Chainクラスのテスト（109行） |
