Upgrade to Pro — share decks privately, control downloads, hide ads and more …

止まらない比較を止める — 余帰納法で無限ストリームを比べる

止まらない比較を止める — 余帰納法で無限ストリームを比べる

Scala の LazyList で作った2つの無限ストリームは、要素ごとに比べると、いつまでも比較が終わりません。この LT では、ストリームを生成する有限状態機械に注目し、余帰納法 (coinduction) と bisimulation の考え方を使って、無限の振る舞いの一致を判定します。最後に、この考え方をもとに、必ず停止する同値性判定を Scala で書きます。

INA Lintaro

November 13, 2026

Transcript

24 slides
  1. 比較が止まるのは、リストに終わりがあるから 先頭を比較して、残りを再帰的に比較する 最後は必ず Nil に到達するので、比較は止まる = = = 1 →

    2 → 3 → Nil = def equal[A](xs: List[A], ys: List[A]): Boolean = (xs, ys) match case (Nil, Nil) => true case (x :: xt, y :: yt) => x == y && equal(xt, yt) case _ => false 1 → 2 → 3 → Nil
  2. 比較が止まるのは、リストに終わりがあるから 先頭を比較して、残りを再帰的に比較する 最後は必ず Nil に到達するので、比較は止まる = = = 1 →

    2 → 3 → Nil = def equal[A](xs: List[A], ys: List[A]): Boolean = (xs, ys) match case (Nil, Nil) => true case (x :: xt, y :: yt) => x == y && equal(xt, yt) case _ => false 1 → 2 → 3 → Nil
  3. Scala なら、終わりのないストリームも簡単に作れる 有限個の状態から、 LazyList で無限ストリームを生成する Machine 1 a (1) Machine

    2 b (2) x (1) y (2) z (1) 括弧内は、その状態で観測される出力値 1 → 2 → 1 → 2 → ⋯ enum S1: case a, b def step1(s: S1): (Int, S1) = s match case S1.a => (1, S1.b) case S1.b => (2, S1.a) val stream1 = LazyList.unfold(S1.a)(s => Some(step1(s))) val stream2 = LazyList.unfold(S2.x)(s => Some(step2(s))) 一段ずつ構造を生成する = 余再帰 (corecursion) 1 → 2 → 1 → 2 → ⋯ enum S2: case x, y, z def step2(s: S2): (Int, S2) = s match case S2.x => (1, S2.y) case S2.y => (2, S2.z) case S2.z => (1, S2.y)
  4. 同じ比較を無限ストリームにすると、止まらない def equal[A](xs: LazyList[A], ys: LazyList[A]): Boolean = (xs, ys)

    match case (LazyList(), LazyList()) => true case (x #:: xt, y #:: yt) => x == y && equal(xt, yt) case _ => false 有限リスト LazyList List[A] LazyList[A] Nil LazyList() :: #:: 1 = 1 ✓ 2 = 2 ✓ 1 = 1 ✓ 2 = 2 ✓ ⋯ 終わらない
  5. 同じ比較を無限ストリームにすると、止まらない def equal[A](xs: LazyList[A], ys: LazyList[A]): Boolean = (xs, ys)

    match case (LazyList(), LazyList()) => true case (x #:: xt, y #:: yt) => x == y && equal(xt, yt) case _ => false 有限リスト LazyList List[A] LazyList[A] Nil LazyList() :: #:: 1 = 1 ✓ 2 = 2 ✓ 1 = 1 ✓ 2 = 2 ✓ ⋯ 終わらない
  6. 同じ比較を無限ストリームにすると、止まらない def equal[A](xs: LazyList[A], ys: LazyList[A]): Boolean = (xs, ys)

    match case (LazyList(), LazyList()) => true case (x #:: xt, y #:: yt) => x == y && equal(xt, yt) case _ => false 有限リスト LazyList List[A] LazyList[A] Nil LazyList() :: #:: 1 = 1 ✓ 2 = 2 ✓ 1 = 1 ✓ 2 = 2 ✓ ⋯ 終わらない でも、生成元の状態機械は有限なのだから、判定できてもよさそうでは?
  7. 値でなく状態を見れば、止める手がかりがある Machine 1 a (1) Machine 2 b (2) x

    (1) ステップ 状態対 出力 0 (a,x) 1=1 1 (b,y) ✓ 2=2✓ y (2) z (1)
  8. 値でなく状態を見れば、止める手がかりがある Machine 1 a (1) Machine 2 b (2) x

    (1) ステップ 状態対 出力 0 (a,x) 1=1 1 (b,y) 2 (a,z) ✓ 2=2✓ 1=1✓ y (2) z (1)
  9. 値でなく状態を見れば、止める手がかりがある Machine 1 a (1) Machine 2 b (2) x

    (1) ステップ 状態対 出力 0 (a,x) 1=1 1 (b,y) 2 (a,z) 3 (b,y) ← 再登場 ✓ 2=2✓ 1=1✓ 2=2✓ y (2) z (1)
  10. 値でなく状態を見れば、止める手がかりがある Machine 1 a (1) Machine 2 b (2) x

    (1) ステップ 状態対 出力 0 (a,x) 1=1 1 (b,y) 2 (a,z) 3 (b,y) ← 再登場 ✓ 2=2✓ 1=1✓ 2=2✓ (a,x) y (2) z (1) (b,y) (a,z) 状態対が、一度現れた組み合わせに戻った! どちらの機械も、次の状態は1つに決まる → 同じ状態対に戻れば、そこから先も同じ繰り返し R = {(a, x), (b, y), (a, z)} で全部
  11. 止めてよい根拠: 1ステップの確認で十分 状態対 R (a,x) 1=1✓ (b,y) 2=2✓ (a,z) 1=1✓

    (a,x) (b,y) (a,z) どの状態対でも、次の2条件が成り立つ 1. 現在の出力値が一致する 2. 次の状態対も R に含まれる 出力値は一致? ✓1=1 ✓2=2 ✓1=1 次も R の中? ✓ (b,y) ✓ (a,z) ✓ (b,y)
  12. 止めてよい根拠: 1ステップの確認で十分 (a,x) 1=1✓ (b,y) 2=2✓ どの状態対でも、次の2条件が成り立つ 1. 現在の出力値が一致する 2.

    次の状態対も R に含まれる 出力値は一致? 次も R の中? (a,x) ✓1=1 ✓ (b,y) (b,y) ✓2=2 ✓ (a,z) (a,z) ✓1=1 ✓ (b,y) 2 より、何回遷移しても R の外に出ない 1 より、R の中では出力値が一致する だから、出力値はずっと一致する 状態対 R (a,z) 1=1✓
  13. 止めてよい根拠: 1ステップの確認で十分 (a,x) 1=1✓ (b,y) 2=2✓ 出力値は一致? 次も R の中?

    (a,x) ✓1=1 ✓ (b,y) (b,y) ✓2=2 ✓ (a,z) (a,z) ✓1=1 ✓ (b,y) 2 より、何回遷移しても R の外に出ない 1 より、R の中では出力値が一致する だから、出力値はずっと一致する 状態対 R (a,z) 1=1✓ どの状態対でも、次の2条件が成り立つ 1. 現在の出力値が一致する 2. 次の状態対も R に含まれる このような関係を bisimulation (双模倣) という
  14. 止めてよい根拠: 1ステップの確認で十分 (a,x) 1=1✓ (b,y) 2=2✓ 出力値は一致? 次も R の中?

    (a,x) ✓1=1 ✓ (b,y) (b,y) ✓2=2 ✓ (a,z) (a,z) ✓1=1 ✓ (b,y) 2 より、何回遷移しても R の外に出ない 1 より、R の中では出力値が一致する だから、出力値はずっと一致する 状態対 R (a,z) 1=1✓ どの状態対でも、次の2条件が成り立つ 1. 現在の出力値が一致する 2. 次の状態対も R に含まれる このような関係を bisimulation (双模倣) という 「ずっと一致する」は、各組で1ステップ分を確かめれば示せる これが 余帰納法 (coinduction)
  15. 余帰納法の考え方を、そのままコードにする def equivalent[S, T, A]( s: S, t: T, stepS:

    S => (A, S), stepT: T => (A, T) ): Boolean = def loop(s: S, t: T, seen: Set[(S, T)]): Boolean = if seen.contains((s, t)) then true else val (vs, ns) = stepS(s) val (vt, nt) = stepT(t) vs == vt && loop(ns, nt, seen + ((s, t))) loop(s, t, Set.empty) 数学的な説明 条件1: 出力値が一致する 条件2: 次の状態対も確かめる 確かめた状態対を R に加える R に戻った = R が閉じた コード vs == vt loop(ns, nt, ...) seen + ((s, t)) seen.contains((s, t)) なぜ true と言える? (正しさ) seen は閉じた関係 = bisimulation → 余帰納法で「ずっと一致する」と言える 単なる最適化ではない なぜ止まる? (停止) 状態対は有限個なので、必ず 値が違って false か、 seen に戻って true
  16. 余帰納法の考え方を、そのままコードにする def equivalent[S, T, A]( s: S, t: T, stepS:

    S => (A, S), stepT: T => (A, T) ): Boolean = def loop(s: S, t: T, seen: Set[(S, T)]): Boolean = if seen.contains((s, t)) then true else val (vs, ns) = stepS(s) val (vt, nt) = stepT(t) vs == vt && loop(ns, nt, seen + ((s, t))) loop(s, t, Set.empty) 数学的な説明 条件1: 出力値が一致する 条件2: 次の状態対も確かめる 確かめた状態対を R に加える R に戻った = R が閉じた コード vs == vt loop(ns, nt, ...) seen + ((s, t)) seen.contains((s, t)) なぜ true と言える? (正しさ) seen は閉じた関係 = bisimulation → 余帰納法で「ずっと一致する」と言える 単なる最適化ではない なぜ止まる? (停止) 状態対は有限個なので、必ず 値が違って false か、 seen に戻って true
  17. 余帰納法の考え方を、そのままコードにする def equivalent[S, T, A]( s: S, t: T, stepS:

    S => (A, S), stepT: T => (A, T) ): Boolean = def loop(s: S, t: T, seen: Set[(S, T)]): Boolean = if seen.contains((s, t)) then true else val (vs, ns) = stepS(s) val (vt, nt) = stepT(t) vs == vt && loop(ns, nt, seen + ((s, t))) loop(s, t, Set.empty) 数学的な説明 条件1: 出力値が一致する 条件2: 次の状態対も確かめる 確かめた状態対を R に加える R に戻った = R が閉じた コード vs == vt loop(ns, nt, ...) seen + ((s, t)) seen.contains((s, t)) なぜ true と言える? (正しさ) seen は閉じた関係 = bisimulation → 余帰納法で「ずっと一致する」と言える 単なる最適化ではない なぜ止まる? (停止) 状態対は有限個なので、必ず 値が違って false か、 seen に戻って true
  18. 余帰納法の考え方を、そのままコードにする def equivalent[S, T, A]( s: S, t: T, stepS:

    S => (A, S), stepT: T => (A, T) ): Boolean = def loop(s: S, t: T, seen: Set[(S, T)]): Boolean = if seen.contains((s, t)) then true else val (vs, ns) = stepS(s) val (vt, nt) = stepT(t) vs == vt && loop(ns, nt, seen + ((s, t))) loop(s, t, Set.empty) 数学的な説明 条件1: 出力値が一致する 条件2: 次の状態対も確かめる 確かめた状態対を R に加える R に戻った = R が閉じた コード vs == vt loop(ns, nt, ...) seen + ((s, t)) seen.contains((s, t)) なぜ true と言える? (正しさ) seen は閉じた関係 = bisimulation → 余帰納法で「ずっと一致する」と言える 単なる最適化ではない なぜ止まる? (停止) 状態対は有限個なので、必ず 値が違って false か、 seen に戻って true
  19. 余再帰で作ったものは、余帰納法で比べる 有限リスト → 再帰で最後まで比較できる 無限ストリーム → 最後まで比較することはできない 有限状態機械が生成する無限ストリーム → 状態対の関係が保たれることを確認すればよい

    余再帰で作ったものを、余帰納法で比べる いつ使う?: 無限に続くものが一致することを示したいとき 状態が無限でも、閉じた関係が見つかれば一致を証明できる ただし、自動で判定できるとは限らない
  20. 余再帰で作ったものは、余帰納法で比べる 有限リスト → 再帰で最後まで比較できる 無限ストリーム → 最後まで比較することはできない 有限状態機械が生成する無限ストリーム → 状態対の関係が保たれることを確認すればよい

    余再帰で作ったものを、余帰納法で比べる いつ使う?: 無限に続くものが一致することを示したいとき 状態が無限でも、閉じた関係が見つかれば一致を証明できる ただし、自動で判定できるとは限らない その構造を生成する状態が有限なら、 余帰納法によって振る舞いの一致を判定できる

More Decks by INA Lintaro

Other Decks in Programming