Files
hakorune/docs/private/roadmap/language-evolution/desugaring-contract.md

14 KiB
Raw Blame History

🎯 De-sugaring Contractデシュガリング契約

作成日: 2025-10-02 対象: すべての新機能実装 関連: 言語進化ロードマップ v2.0


📖 概要

De-sugaring Contractデシュガリング契約 は、Hakoruneの最も重要な設計原則です。

新構文は既存構文・既存Boxへ有限段で必ず落ちること。IR命令の追加は最後の手段。

この原則により、「コアは最小・糖衣は最強」 を実現します。


🎯 5つの不変ルール赤線

1 MIR14は増やさない

原則: すべてデシュガリング/静的パス/標準ライブラリで実現

現状のMIR14命令セット:

  • 基本演算(5): Const, UnaryOp, BinOp, Compare, TypeOp
  • メモリ(2): Load, Store
  • 制御(4): Branch, Jump, Return, Phi
  • 呼び出し(1): MirCallCallee で Global/Extern/ModuleFunction/Method/Constructor/Closure/Value を表現)
  • GC(2): Barrier, Safepoint
  • 構造(2): Copy, Nop最適化/検証用・意味論不変)

📖 詳細: MIR Instruction Set

なぜ凍結するか:

  • VM/LLVM/WASMすべてのバックエンドで恩恵を受ける
  • セルフホストの保守性が維持される
  • 実装の複雑度が爆発しない

2 Everything is Box/flow Main.main を維持

原則: すべての値はBox、すべての関数はBox/flowのメソッド

// ✅ 正しい設計
box OptionBox<T> {
    value: T
    is_some: BoolBox

    birth(v: T) {
        me.value = v
        me.is_some = true
    }

    unwrap(): T {
        if not me.is_some {
            panic("unwrap on None")
        }
        return me.value
    }
}

// T? → OptionBox<T> へデシュガリング
local x: IntegerBox? = getSomeValue()
// ↓
local x: OptionBox<IntegerBox> = getSomeValue()

Pulse は下限:

  • 最小限の型として利用
  • 言語機能には持ち込まない

3 Effects/Capabilities/Contracts は meta 側

原則: 言語仕様には持ち込まない、metaレイヤーで処理

// ❌ 言語機能化(採用しない)
effect IO {
    read(path: StringBox): ResultBox<StringBox>
    write(path: StringBox, data: StringBox): ResultBox<Unit>
}

fn process() with IO {
    // IO効果を要求
}

// ✅ meta側で処理視覚糖衣のみ
with net.out, fs.read {
    http.get("https://api.example.com")
    file.read("config.txt")
}
// ↓ 実際は何も変えない(ログに印を出すだけ)

効果システムの取り扱い:

  • 静的解析ツールで検出Linter/型チェッカー)
  • ランタイムは関与しない
  • 視覚的な開発体験向上のみ

4 例外は導入しない

原則: panicはVM/実装バグ用、通常はResultBoxで伝播

// ✅ 正しいエラー処理ResultBox + ? 演算子)
box FileBox {
    read(path: StringBox): ResultBox<StringBox> {
        // エラー時はResultBox::Err返却
    }
}

flow Main.main() {
    local content = FileBox::read("config.txt")?  // ? で伝播
    processContent(content)
}

// ❌ 例外(採用しない)
try {
    let content = FileBox::read("config.txt")
} catch(FileNotFoundError e) {
    // ...
}

panic の用途:

  • VM内部エラー配列境界外アクセス等
  • 実装バグunreachable!相当)
  • 通常のエラーハンドリングには使わない

5 dev→prod の挙動差は policy のみ

原則: warn/audit/enforce で制御

// ✅ policy制御
debug_assert(internal.isValid())  // dev: panic, prod: nop

// ❌ 挙動差を言語機能に(採用しない)
if DEBUG_MODE {
    checkInvariant()
}

policy種別:

  • warn: 警告のみ
  • audit: ログ記録
  • enforce: エラーdev/prod共通

🍬 デシュガリング実例集

A. 型システム

Optional型 T?

// 糖衣構文
local x: IntegerBox? = getSomeValue()

// ↓ デシュガリング後
local x: OptionBox<IntegerBox> = getSomeValue()

// OptionBoxは標準ライブラリで実装
box OptionBox<T> {
    value: T
    is_some: BoolBox

    birth(v: T) { me.value = v; me.is_some = true }
    birth_none() { me.is_some = false }

    unwrap(): T { /* ... */ }
    is_some(): BoolBox { return me.is_some }
    is_none(): BoolBox { return not me.is_some }
}

MIR命令増加: なし既存のMirCall, Branch, Return等で実現

Union型 A|B

// 糖衣構文
type Result = OkBox | ErrorBox

// ↓ デシュガリング後
type Result = SumBox<OkBox, ErrorBox>

// SumBoxは標準ライブラリで実装
box SumBox<A, B> {
    tag: StringBox  // "A" or "B"
    value_a: A
    value_b: B

    birth_left(a: A) { me.tag = "A"; me.value_a = a }
    birth_right(b: B) { me.tag = "B"; me.value_b = b }

    match_sum<R>(on_left: fn(A) -> R, on_right: fn(B) -> R): R {
        if me.tag == "A" {
            return on_left(me.value_a)
        } else {
            return on_right(me.value_b)
        }
    }
}

MIR命令増加: なし


B. 並行処理

async {} スコープ

// 糖衣構文
async {
    nowait task1 = longTask1()
    nowait task2 = longTask2()
    // スコープ終了で自動待機
}

// ↓ デシュガリング後
local group = new TaskGroupBox()
group.spawn(fn() { longTask1() })
group.spawn(fn() { longTask2() })
group.await_all()  // スコープ終了時自動

// TaskGroupBoxは標準ライブラリで実装
box TaskGroupBox {
    tasks: ArrayBox<FutureBox>

    spawn(task: fn()) {
        local future = nowait task()
        me.tasks.push(future)
    }

    await_all() {
        loop(me.tasks.len() > 0) {
            local future = me.tasks.pop()
            await future
        }
    }
}

MIR命令増加: なし既存のnowait/awaitで実現

select {}

// 糖衣構文
match select {
    ch1.receive() => { handle1() }
    ch2.receive() => { handle2() }
    timeout(1000) => { handleTimeout() }
}

// ↓ デシュガリング後
local sel = new SelectBox()
sel.on(ch1, fn(v) { handle1() })
sel.on(ch2, fn(v) { handle2() })
sel.on_timeout(1000, fn() { handleTimeout() })
sel.run()

// SelectBoxは標準ライブラリで実装
box SelectBox {
    channels: ArrayBox<ChannelBox>
    handlers: ArrayBox<fn(Any)>

    on(ch: ChannelBox, handler: fn(Any)) {
        me.channels.push(ch)
        me.handlers.push(handler)
    }

    on_timeout(ms: IntegerBox, handler: fn()) { /* ... */ }

    run() {
        // poll all channels, invoke first ready handler
    }
}

MIR命令増加: なし


C. 糖衣構文

パイプライン演算子 |>

// 糖衣構文
value |> f(_) |> g(_, 42) |> h(_)

// ↓ デシュガリング後
h(g(f(value), 42))

// 実用例
[1, 2, 3, 4, 5]
    |> map(_, fn(x) { x * 2 })
    |> filter(_, fn(x) { x % 3 == 0 })
    |> reduce(_, 0, fn(acc, x) { acc + x })

// ↓ デシュガリング後
reduce(filter(map([1,2,3,4,5], fn(x){x*2}), fn(x){x%3==0}), 0, fn(acc,x){acc+x})

MIR命令増加: なし(ただの関数呼び出し順序変更)

名前付き引数

// 糖衣構文
download(url: u, timeout: 3.s, retry: 5)

// ↓ デシュガリング後(位置引数 + デフォルト補完)
download(u, 3.s, 5)

// 欠けた引数はデフォルト補完
download(url: u)
// ↓
download(u, DEFAULT_TIMEOUT, DEFAULT_RETRY)

MIR命令増加: なし(静的パスで位置引数に変換)

with capability スコープ

// 糖衣構文
with net.out, fs.read {
    http.get("https://api.example.com")
    file.read("config.txt")
}

// ↓ デシュガリング後(何も変えない)
// metaレイヤーでログに印を出すだけ
http.get("https://api.example.com")
file.read("config.txt")

MIR命令増加: なし(完全に視覚糖衣)


D. マクロ

@test マクロ

// 糖衣構文
@test
it("should add numbers") {
    local calc = new Calculator()
    expect(calc.add(2, 3)).toBe(5)
}

// ↓ デシュガリング後HIRパッチ
TestRunnerBox::register("should add numbers", fn() {
    local calc = new Calculator()
    expect(calc.add(2, 3)).toBe(5)
})

// TestRunnerBoxは標準ライブラリ
static box TestRunnerBox {
    tests: ArrayBox<TestCase>

    register(name: StringBox, test: fn()) {
        me.tests.push(new TestCase(name, test))
    }

    run_all() {
        loop(me.tests.len() > 0) {
            local test = me.tests.pop()
            test.run()
        }
    }
}

MIR命令増加: なしHIRレベルでパッチ

@profile マクロ

// 糖衣構文
@profile
calculate() {
    // 処理
}

// ↓ デシュガリング後HIRパッチ
calculate() {
    local _prof = ProfileBox::start("calculate")
    // 処理
    _prof.end()
}

// ProfileBoxは標準ライブラリ
box ProfileBox {
    name: StringBox
    start_time: TimeBox

    static start(name: StringBox): ProfileBox {
        local prof = new ProfileBox()
        prof.name = name
        prof.start_time = TimeBox::now()
        return prof
    }

    end() {
        local duration = TimeBox::now() - me.start_time
        ConsoleBox::log("Profile: " + me.name + " took " + duration.toString())
    }
}

MIR命令増加: なし


📊 デシュガリング規則一覧表

構文 デシュガリング後 実装方法 MIR命令増加 Phase
T? OptionBox<T> 標準ライブラリ なし 20
A|B SumBox<A,B> 標準ライブラリ なし 20
x |> f(_) f(x) 静的パスAST変換 なし 17-18
f(a:1, b:2) f(1,2) 静的パス(引数並び替え) なし 18-19
async { body } TaskGroupBox::scoped(...) 標準ライブラリ なし 22
select { ... } SelectBox::new()...run() 標準ライブラリ なし 23
@test it(...){...} TestRunnerBox::register(...) マクロHIRパッチ なし 16.1
@bench(iter:N) { ... } BenchmarkBox::run(...) マクロHIRパッチ なし 16.3
@profile { ... } ProfileBox::start() + .end() マクロHIRパッチ なし 20
repr(obj) @derive(Debug) 連携 マクロHIRパッチ なし 18
x.move() 所有権移転メソッド 標準ライブラリ なし 28
x.share() Arc参照増加メソッド 標準ライブラリ なし 28
x.weak() 弱参照取得メソッド 標準ライブラリ なし 28
with cap { ... } (何も変えない) 視覚糖衣meta なし 20-22
comptime { ... } 定数埋め込み 静的パス(ビルド時評価) なし 22-25
pattern Some(x) OptionBox::Some(x) 静的パス(パターン別名) なし 23-25

重要: すべてMIR命令増加なし


🔍 デシュガリング検証方法

1. コンパイラフラグ

# デシュガリング前のAST表示
$ hakorune --dump-ast program.hkr

# デシュガリング後のHIR表示
$ hakorune --dump-hir program.hkr

# 最終MIR表示MIR14のみ
$ hakorune --dump-mir program.hkr

2. スモークテスト

# デシュガリング検証スイート
$ tools/smokes/v2/run.sh --profile desugaring

3. Linter検出

# レガシー命令検出
$ HAKO_OPT_DIAG=1 hakorune program.hkr

# レガシー命令禁止Fail-Fast
$ HAKO_OPT_DIAG_FORBID_LEGACY=1 hakorune program.hkr

🚨 契約違反の例(やってはいけない)

悪い例1: MIR命令を追加

// ❌ NG: Optional型のために新命令追加
enum MirInstruction {
    // ... 既存14命令 ...
    SomeCheck(ValueId),  // ← 追加してはいけない!
    NoneCheck(ValueId),  // ← 追加してはいけない!
}

// ✅ OK: OptionBoxで実現
box OptionBox<T> {
    is_some(): BoolBox { return me.is_some }
    // MirCall で呼び出し(既存命令)
}

悪い例2: 効果システムを言語機能に

// ❌ NG: 効果システムを型システムに統合
effect IO {
    read(path: StringBox): ResultBox<StringBox>
}

fn process() with IO {
    // VM/コンパイラが効果を追跡
}

// ✅ OK: meta側で処理視覚糖衣
with io {
    process()  // ログに印を出すだけ
}

悪い例3: 例外を導入

// ❌ NG: try-catch構文を実装
try {
    dangerousOp()
} catch(NetworkError e) {
    // ...
}

// ✅ OK: ResultBox + ? 演算子
local result = dangerousOp()?  // Err時は早期return

📚 参考資料


🎊 まとめ

De-sugaring Contractデシュガリング契約 により、Hakoruneは

  1. コアは最小 - MIR14命令セットを凍結
  2. 糖衣は最強 - すべての新機能をデシュガリング/Box/マクロで実現
  3. 保守性最高 - セルフホストの実装容易性維持
  4. 一貫性抜群 - Everything is Box哲学貫徹
  5. 拡張性無限 - Box/マクロでいくらでも拡張可能

この原則により、次世代言語の標準 を打ち立てます。


作成者: Claude Sonnet 4.5 + ChatGPT Pro 作成日: 2025-10-02 関連: 言語進化ロードマップ v2.0