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

OCaml Meeting 2026 in Tokyo

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Masayuki Mizuno Masayuki Mizuno
August 22, 2026
250

OCaml Meeting 2026 in Tokyo

Avatar for Masayuki Mizuno

Masayuki Mizuno

August 22, 2026

Transcript

  1. チャーチエンコーディング 帰納的データ型を型なしλ計算で表現する手法 畳み込みを行う関数で帰納的データ型の値を表現する データ 畳み込む関数 [1; 2; 3] = 1

    :: 2 :: 3 :: [] l = fun f x -> f 1 (f 2 (f 3 x)) :: ↦ f、[] ↦ x と置き換える = 畳み込み fold̲right f fun f x -> 1 :: f 1 2 :: f 2 3 :: f 3 [] (要素 1, 2, 3 はそのまま持ち越す) x l f x = f 1 (f 2 (f 3 x)) = fold_right f [1; 2; 3] x ©2026 Wantedly, Inc.
  2. チャーチ数 自然数は与えられた関数のn回適用で表現できる ペアノ自然数の畳み込みと捉えると良い ペアノの公理 3 = 0 に「+1」を 3 回

    ヴァリアントで表現 畳み込む関数 type nat = Z | S of nat fun f x -> f (f (f x)) +1 ↦ S、0 ↦ Z fun f x -> +1 S f +1 S f +1 S f 0 ©2026 Wantedly, Inc. S ↦ f、Z ↦ x (まだ計算しない) Z (畳み込み) x
  3. OCaml実行時の関数の表現 環境と関数ポインタの組の関数閉包が使われる let make_adder z = ① 引数は a だけ。z

    は外から来る = 自由変数 fun a -> a + z ② make̲adder が返ると、変数 z は消える → でも返された関数は、呼ばれた時に z が要る ③ だから z を保存して持ち歩く。その箱が環境 ̶ 関数 = 〈コードポインタ, 環境〉の組 環境(個別) コードポインタ add10 z = 10 code = make_adder 10 add1 z = 1 = make_adder 1 ©2026 Wantedly, Inc. (コードは共有・環境は個別) code fun a -> a + z のコード(機械語・共有で 1 つ)
  4. チャーチ数の実行時表現 ペアノの公理に従って帰納的に定義した自然数と同型 ※匿名関数を直接書かずzeroとsuccの組み合わせでチャーチ数を表現した場合 チャーチ数(実行時のクロージャ) ペアノ数(ヴァリアント) let zero = fun f

    x -> x let succ = fun n f x -> f (n f x) three = succ (succ (succ zero)) type nat = Z | S of nat S (S (S Z)) 1 クロージャ 1 コンストラクタ fun f x -> f (n f x) n = ≅ S fun f x -> f (n f x) n = ≅ S fun f x -> f (n f x) n = ≅ S (succ が捕獲した n = 子への参照 = S of nat の中身) fun f x -> x zero(環境なし) ©2026 Wantedly, Inc. ≅ Z
  5. チャーチのリストの実行時表現 nilとconsで表現される通常のリストと同型になる ※匿名関数を直接書かずnilとconsの組み合わせでチャーチのリストを記述した場合 チャーチリスト(実行時のクロージャ) 標準ライブラリのリスト let nil = fun f

    x -> x let cons h t = fun f x -> f h (t f x) l = cons 1 (cons 2 (cons 3 nil)) [1; 2; 3] = 1 :: 2 :: 3 :: [] 1 クロージャ 1 cons セル fun f x -> f h (t f x) h = 1 t = ≅ 1 fun f x -> f h (t f x) h = 2 t = ≅ 2 fun f x -> f h (t f x) h = 3 t = ≅ 3 (h = 要素、t = 次への参照) fun f x -> x nil(環境なし) ©2026 Wantedly, Inc. ≅ []
  6. λ計算とチューリングマシンの対応から関数閉包を見る 関数ポインタは状態機械に、関数閉包はテープに対応 λ計算とチューリングマシンの計算能力は等価 コンパイル時 プログラムは有限のテキスト 実行時 make_adder 1, make_adder 2,

    ... let make_adder z = z = 1 code z = 2 code z = 42 code fun a -> a + z ラムダ式は有限個 = 有限の制御(TM の状態表) コードは 1 つを共有 ⋮ 差分は環境だけが担う = 可変の記憶(テープ) ©2026 Wantedly, Inc.
  7. 例1: List.fold_rightでList.fold_leftを書く 関数閉包をアキュムレータで引き回すとできる 初期値は fold_right で作った関数閉包へ最後に渡してやる 実行時に、要素の数だけこのクロージャが生まれる 環境に捕獲するのは x と

    g。g : 'b -> 'b = 続きの畳み込み(継続) let fold_left f z xs = List.fold_right (fun x g -> fun a -> g (f a x)) xs (fun a -> a) z 初期の継続 id(環境なし) 初期値 z は最後、出来上がった継続に流す ©2026 Wantedly, Inc.
  8. List.fold_rightで書いたList.fold_leftの実行時の挙動 中間表現でペアノのリストが作られる スタック2つでキューを作るような挙動になるが、実行効率は良くない 継続 g の鎖(実行時のクロージャ) 入力のリスト let fold_left f

    z xs = List.fold_right (fun x g -> fun a -> g (f a x)) xs (fun a -> a) z 1 クロージャ xs = [1; 2; 3] = 1 :: 2 :: 3 :: [] 1 cons セル fun a -> g (f a x) x = 1 g = ≅ 1 fun a -> g (f a x) x = 2 g = ≅ 2 fun a -> g (f a x) x = 3 g = ≅ 3 (x = 要素、g = 次への参照) fun a -> a id(環境なし) ©2026 Wantedly, Inc. ≅ []
  9. 例2: repMin 一回の走査で葉の要素を最小値で置き換えられる 遅延評価を使っている 答えの最小値 m を、計算している最中に使う data Tree =

    Leaf Int | Node Tree Tree repMin :: Tree -> Tree repMin tree = t where (m, t) = replace m tree replace m (Leaf n) = (n, Leaf m) replace m (Node t1 t2) = let (m1, t1') = replace m t1 (m2, t2') = replace m t2 in (m1 `min` m2, Node t1' t2') ©2026 Wantedly, Inc.
  10. repMinをOCamlで書く 遅延評価のサンクを関数抽象で置き換えると書ける 評価結果は一度しか使われないのでメモ化せず名前呼びで十分 type tree = Leaf of int |

    Node of tree * tree let rep_min tree = let rec replace = function | Leaf n -> (n, fun m -> Leaf m) ← 環境なし | Node (t1, t2) -> let (m1, c1) = replace t1 and (m2, c2) = replace t2 in (min m1 m2, fun m -> Node (c1 m, c2 m)) in let (m, c) = replace tree in c m ©2026 Wantedly, Inc.
  11. repMinの実行時の挙動 結局木が2回走査される 最小値を求める走査で木と同型のクロージャが作られ、関数呼び出しで再走査される 入力の木 replace が返すクロージャ c Node (Node (Leaf

    3, Leaf 1), Leaf 4) let (m, c) = replace tree Node fun m -> Node(c1 m, c2 m) Node Leaf 4 ≅ fun m -> Node(c1 m, c2 m) 同じ形 Leaf 3 ©2026 Wantedly, Inc. Leaf 1 fun m -> Leaf m fun m -> Leaf m fun m -> Leaf m
  12. 計算可能性の制約を使った面白いプログラム 実は (int→bool)→int 同士の等価性は決定可能 ※引数は純粋関数である必要がある type cantor = int ->

    bool ← 入力は無限のビット列 type bs = Cons of bool * bs Lazy.t let rec nth (l : bs Lazy.t) n = ← 遅延ビット列(探索結果の置き場) ← 補助(ビット列の読み書き) let Cons (b, t) = Lazy.force l in if n = 0 then b else nth t (n - 1) let cantor_of l : cantor = fun n -> nth l n let cons b (a : cantor) : cantor = fun n -> if n = 0 then b else a (n - 1) let rec find_s (p : cantor -> bool) : bs Lazy.t = lazy ( ← 頭も含めて遅延(停止性の要) let b = forsome (fun a -> p (cons true a)) in Cons (b, find_s (fun a -> p (cons b a)))) and forsome (p : cantor -> bool) : bool = p (cantor_of (find_s p)) ↑ 見つけて、確かめる let forevery p = not (forsome (fun a -> not (p a))) let equal f g = forevery (fun a -> f a = g a) ©2026 Wantedly, Inc. ← De Morgan
  13. (int→bool)→int の等価性を試す # let f a = if a 3

    then 10 else if a 7 then 20 else 30;; val f : (int -> bool) -> int = <fun> # let h a = if a 7 then (if a 3 then 10 else 20) else (if a 3 then 10 else 30);; val h : (int -> bool) -> int = <fun> # let k a = if a 3 then 11 else 30;; val k : (int -> bool) -> int = <fun> # equal f h;; - : bool = true ← 外延は同じ・形は違う 2 つが、等しいと判定された # equal h k;; # equal k f;; - : bool = false - : bool = false (k は bit 3 のとき 11 を返すので、f とも h とも等しくない) ©2026 Wantedly, Inc.
  14. 表示的意味論からの正当化 (1/2) 計算機上で書ける純粋関数は連続になる プログラムの領域 D が D ≅ (D →

    D) に閉じている必要がある ✕ D ≅ (D → D) ◯ D ≅ [D → D] |D → D| > |D|(Cantor) 連続関数に制限する(Scott 1969) 集合論的な関数 D → D 連続関数 = 有限近似で決まる 計算可能な関数 (機械は入力を有限しか観察できない) 計算可能 ©2026 Wantedly, Inc. 連続: 制限しても、はみ出すものはない
  15. 表示的意味論からの正当化 (2/2) int→bool はカントール空間でありコンパクト 入力 a : int -> bool

    = 無限のビット列(この埋め方の全体 = カントール空間) f a = if a 3 then 10 else if a 7 then 20 else 30 0 1 2 3 4 5 6 7 8 9 ? ? ? ? ? ? ? ? ? ? f が読むのは有限個だけ(計算可能 … (無限に続く) 連続) その位置には全入力共通の上限がある(コンパクト 一様) a 3 = true a 3 = F, a 7 = true a 3 = F, a 7 = false f a = 10 f a = 20 f a = 30 非可算個の入力が、有限個のケースに割れる (このケースを比べ尽くせば equal は終わる = 有限時間の全数探索) ©2026 Wantedly, Inc.