# 機能設計書 53-ScopedValues

## 概要

本ドキュメントは、Julia の `ScopedValue` / `LazyScopedValue` 型と `with` / `@with` による動的スコープ付き値の伝播機能の設計を記述する。

### 本機能の処理概要

本機能は、動的スコープに基づく値の伝播を提供する。`ScopedValue` は特定のダイナミックスコープ内でのみ有効な値を保持し、スコープを抜けると元の値に戻る。タスク間でスコープが伝播されるため、`@async` や `Threads.@spawn` で生成されたタスクからも親タスクのスコープ付き値にアクセスできる。グローバル変数の代替として、スレッドセーフかつ構造化されたコンテキスト伝播を実現する。

**業務上の目的・背景**: マルチスレッド環境において、グローバル変数によるコンテキスト伝播はデータ競合のリスクがある。タスクローカルストレージは手動管理が煩雑で、深いコールスタック全体に値を渡すには全ての関数に引数を追加する必要がある。ScopedValues は、関数引数を変更することなく、動的スコープを通じて安全にコンテキスト情報（ログレベル、認証情報、設定値など）を伝播する仕組みを提供する。

**機能の利用シーン**: ログレベルの動的変更、リクエストコンテキストの伝播（トレーシング ID、認証トークン等）、テスト時の依存性注入、設定値の一時的なオーバーライド、並列処理での環境設定の伝播。

**主要な処理内容**:
1. `ScopedValue{T}(default)` によるデフォルト値付きスコープ付き値の生成
2. `LazyScopedValue{T}(f)` による遅延初期化のスコープ付き値の生成
3. `with(f, var => val, ...)` による新しい動的スコープの作成と関数実行
4. `@with var=>val expr` によるクロージャなしのスコープ付き式評価
5. `val[]` による現在のスコープでの値取得
6. `ScopedValues.get(val)` による安全な値取得（`Some{T}` または `nothing`）
7. `ScopedThunk(f)` による動的スコープのキャプチャとリプレイ

**関連システム・外部連携**: `Core.current_scope()` ランタイム関数によるスコープチェーンの管理。`Base.PersistentDict` による不変辞書ベースのスコープストレージ。タスクスケジューラとの連携によるスコープ伝播。

**権限による制御**: 特に権限制御は行われない。

## 関連画面

本機能は GUI 画面を持たないライブラリ機能であり、直接的に関連する画面はない。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | REPL から ScopedValue を呼び出して使用 |

## 機能種別

計算処理（コンテキスト伝播）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| default | T | No | ScopedValue のデフォルト値 | 型パラメータ T と一致する型であること |
| f (with) | Function | Yes | スコープ内で実行する関数 | 0 引数の呼び出し可能オブジェクト |
| pairs | Pair{<:AbstractScopedValue, Any}... | Yes (with) | スコープで設定する値のペア | キーが AbstractScopedValue であること。値は対応する型 T に convert 可能 |
| getdefault (LazyScopedValue) | OncePerProcess{T} | Yes | 遅延初期化デフォルト値を提供する関数 | OncePerProcess{T} 型であること |

### 入力データソース

ユーザーコードから直接引数として渡される値。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| val[] | T | 現在のスコープでのスコープ付き値 |
| ScopedValues.get(val) | Union{Nothing, Some{T}} | 安全な値取得。未設定・デフォルトなしの場合は nothing |
| with(f, ...) | Any | スコープ内で f() を評価した結果 |
| @with ... expr | Any | スコープ内で expr を評価した結果 |
| isassigned(val) | Bool | 値が設定されているかどうか |

### 出力先

関数/マクロの戻り値として呼び出し元に返却。

## 処理フロー

### 処理シーケンス

```
1. ScopedValue/LazyScopedValue の生成
   └─ デフォルト値（またはデフォルト値生成関数）を保持
2. with/@ によるスコープ突入
   └─ Core.current_scope() で現在のスコープを取得
   └─ Scope(parent, key=>val, ...) で新しい Scope を生成
   └─ PersistentDict に key=>val を追加
   └─ tryfinally 式で新しいスコープを設定し、本体を実行
3. val[] による値取得
   └─ ScopedValues.get(val) を呼び出し
   └─ Core.current_scope() で現在のスコープを取得
   └─ scope.values（PersistentDict）から値を検索
   └─ スコープに存在しなければデフォルト値を返す
   └─ デフォルト値もなければ KeyError を throw
4. スコープ脱出
   └─ tryfinally の finally で元のスコープに戻る
```

### フローチャート

```mermaid
flowchart TD
    A["with(f, var=>val)"] --> B["現在のスコープを取得\nCore.current_scope()"]
    B --> C["新しい Scope を生成\nScope(parent, var=>val)"]
    C --> D["@with マクロで tryfinally 式を生成"]
    D --> E["新スコープで f() を実行"]
    E --> F{"val[] アクセス?"}
    F -->|Yes| G["Core.current_scope() でスコープ取得"]
    G --> H{"スコープに値あり?"}
    H -->|Yes| I["スコープの値を返却"]
    H -->|No| J{"デフォルト値あり?"}
    J -->|Yes| K["デフォルト値を返却"]
    J -->|No| L["KeyError を throw"]
    F -->|No| M["f() の結果を返却"]
    E --> N["finally: 元のスコープに復帰"]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-53-1 | スコープ不変性 | 動的スコープ内での値は一度設定すると変更できない（定数） | 常時 |
| BR-53-2 | スコープ伝播 | 動的スコープはタスク生成時に子タスクに伝播される | Task 生成時 |
| BR-53-3 | スコープスタック | ネストした with はスコープをスタック的に管理し、内側が外側をシャドウする | ネストした with 使用時 |
| BR-53-4 | 型変換 | 設定値は ScopedValue の型パラメータ T に convert される | with で値設定時 |
| BR-53-5 | 遅延初期化 | LazyScopedValue のデフォルト値は初回アクセス時に評価される | LazyScopedValue 使用時 |

### 計算ロジック

特別な計算ロジックは存在しない。PersistentDict による O(log n) のキー検索が主要な処理。

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

該当なし。本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | KeyError | デフォルト値なしの ScopedValue にスコープ外からアクセス | with でスコープを設定してからアクセスするか、デフォルト値付きで生成 |
| - | MethodError | convert(T, value) が失敗した場合 | 型パラメータ T に変換可能な値を設定 |

### リトライ仕様

該当なし。

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

tryfinally 式により、スコープ内で例外が発生しても確実に元のスコープに復帰する。

## パフォーマンス要件

- PersistentDict ベースのスコープストレージにより O(log n) のルックアップ
- @with マクロはクロージャを生成しないため with 関数よりもオーバーヘッドが小さい
- コンパイラ最適化により、定数伝播が可能な場合はスコープルックアップが省略される可能性がある

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

ScopedValue で認証情報やシークレットを伝播する場合、意図しないタスクへの伝播に注意が必要。

## 備考

- Julia 1.11 で ScopedValue が導入された
- Julia 1.13 で LazyScopedValue と AbstractScopedValue が導入された
- Julia 1.8 以降は外部パッケージ ScopedValues.jl で互換実装が利用可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | scopedvalues.jl | `base/scopedvalues.jl` | AbstractScopedValue{T}（19行目）: 全スコープ付き値型の抽象基底型 |
| 1-2 | scopedvalues.jl | `base/scopedvalues.jl` | ScopedValue{T}（91-99行目）: mutable struct。hasdefault フラグと default 値を持つ。mutable なのはオブジェクト同一性が必要なため |
| 1-3 | scopedvalues.jl | `base/scopedvalues.jl` | LazyScopedValue{T}（51-53行目）: OncePerProcess{T} でデフォルト値を遅延評価 |
| 1-4 | scopedvalues.jl | `base/scopedvalues.jl` | ScopeStorage（136行目）: `PersistentDict{AbstractScopedValue, Any}` 型の別名 |
| 1-5 | scopedvalues.jl | `base/scopedvalues.jl` | Scope（138-140行目）: ScopeStorage を保持する構造体。スコープチェーンの各ノード |
| 1-6 | scopedvalues.jl | `base/scopedvalues.jl` | ScopedThunk{F}（339-346行目）: 動的スコープをキャプチャするコーラブル |

**読解のコツ**: `ScopedValue` が `mutable struct` である理由は、Dict のキーとしてオブジェクト同一性（`===`）が必要だから。フィールドは `const` で実質不変だが、型としては mutable。`Scope` は PersistentDict（永続データ構造）を使っているため、新しいスコープ生成時に親スコープのデータをコピーせず効率的に共有する。

#### Step 2: スコープ生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | scopedvalues.jl | `base/scopedvalues.jl` | Scope コンストラクタ（144-161行目）: 親スコープと key=>val ペアから新 Scope を生成 |
| 2-2 | scopedvalues.jl | `base/scopedvalues.jl` | @with マクロ（269-281行目）: tryfinally 式を生成してスコープを設定。クロージャ不要 |
| 2-3 | scopedvalues.jl | `base/scopedvalues.jl` | with 関数（323-326行目）: @with マクロを内部的に使用 |

**主要処理フロー**:
- **145行目**: `convert(T, value)` で値を型変換
- **147行目**: 親が nothing なら新規 PersistentDict を生成
- **149行目**: 親がある場合は `ScopeStorage(parent.values, key=>val)` で親を拡張
- **280行目**: `Expr(:tryfinally, esc(ex), nothing, :(Scope(...)))` で tryfinally 式を生成

#### Step 3: 値取得を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | scopedvalues.jl | `base/scopedvalues.jl` | get 関数（203-218行目）: Core.current_scope() でスコープ取得、PersistentDict から値検索 |
| 3-2 | scopedvalues.jl | `base/scopedvalues.jl` | getindex（220-224行目）: get を呼び出し、nothing なら KeyError |
| 3-3 | scopedvalues.jl | `base/scopedvalues.jl` | isassigned（129-134行目）: hasdefault またはスコープに存在するか |

**主要処理フロー**:
- **204行目**: `Core.current_scope()` で現在のスコープを取得
- **205行目**: scope が nothing なら hasdefault をチェックしてデフォルト値を返す
- **211行目**: hasdefault ありなら `Base.get(Fix1(getdefault, val), scope.values, val)` でデフォルト付き検索
- **213-215行目**: hasdefault なしなら `KeyValue.get` で検索し、なければ nothing

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

```
with(f, var=>val, ...)
    │
    └─ @with(var=>val, ..., f())
           │
           ├─ Scope(Core.current_scope(), var=>val, ...)
           │      └─ ScopeStorage(parent.values, key=>convert(T, val))
           │             └─ PersistentDict の拡張
           │
           └─ tryfinally(f(), nothing, new_scope)
                  │
                  ├─ f() 実行中に val[] がアクセスされると:
                  │      └─ Base.getindex(val::AbstractScopedValue)
                  │             └─ ScopedValues.get(val)
                  │                    ├─ Core.current_scope() → Scope
                  │                    ├─ scope.values[val] → Some{T}(value)
                  │                    └─ デフォルト値 → Some{T}(default)
                  │
                  └─ finally: スコープ復帰

ScopedThunk(f)
    │
    ├─ 生成時: scope = Core.current_scope() を保存
    └─ 呼び出し時: @enter_scope sf.scope sf.f()
```

### データフロー図

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

ScopedValue(default)  → mutable struct 生成              → ScopedValue{T}
                         hasdefault=true, default=val

with(f, sv=>val)      → Scope(parent, sv=>val)           → f() の結果
                         PersistentDict に追加
                         tryfinally でスコープ設定
                         f() 実行

sv[]                  → Core.current_scope()              → T 型の値
                         scope.values[sv] 検索
                         デフォルト値フォールバック
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| scopedvalues.jl | `base/scopedvalues.jl` | ソース | ScopedValues モジュールの全実装 |
| dict.jl | `base/dict.jl` | ソース | PersistentDict の実装。スコープストレージの基盤 |
| task.jl | `base/task.jl` | ソース | Task 生成時のスコープ伝播 |
| essentials.jl | `base/essentials.jl` | ソース | OncePerProcess の実装。LazyScopedValue で使用 |
