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

継続モナドとリアクティブプログラミング

 継続モナドとリアクティブプログラミング

リアクティブプログラミングには長い歴史があり、シンプルな emitter / listener から始まり Functional Reactive Programming (FRP), ReactiveX, Arrowised FRP, そして Solid.js, Vue, (React) の基礎になっています。

一方、継続モナドとは Haskell 等において制御構文的なものを実現するために使われるモナドトランスフォーマー(モナドを別のモナドに変換するもの)です。
このセッションではリアクティブプログラミング、継続モナドの解説と、実はリアクティブプログラミングが継続モナドで実現可能であり、そして、実はこの実装が Solid, React, Vue 等モダンなフロントエンドフレームワークと似た API を提供するという話をします。

また、非同期処理、リソース管理の話も併せて行います。

Avatar for ゆきくらげ

ゆきくらげ

July 12, 2026

More Decks by ゆきくらげ

Other Decks in Programming

Transcript

  1. 内容 • リアクティブプログラミング • リソース • リソース指向リアクティブプログラミング (Quon) • 継続モナド

    • Rules of Hooks と ArrowChoice • Agent ⽤ DSL (Katari) • Component Based な Agent Network 継続モナドとリアクティブプログラミング 背景
  2. リアクティブ‧プログラミング 何? • → データの時間軸⽅向の変化を主軸とするパラダイム • データとそれに依存する処理を分けて書く 利点 • 更新伝播の

    Source of Truth → 後述 使われている場所 • Webフロントエンド (React、Vue など) • ゲーム など 継続モナドとリアクティブプログラミング 背景
  3. リアクティブ‧プログラミング let state = 0 state.subscribe(()  { view.text =

    state }) on("click", ()  { state = state + 1 }) 継続モナドとリアクティブプログラミング 背景 イベント駆動によってデータに依存する処理を切り出す let state = 0 updateView = ()  { view.text = state } on("click", ()  { state = state + 1 updateView() })
  4. View 更新伝播の Source of Truth 継続モナドとリアクティブプログラミング 背景 Data Event Event

    ? View Process Data ‧Process をすべて実⾏するのは⾮効率。ではどの Process を実⾏する? ‧Event はそれぞれの Process がどのデータに依存しているか知らない Data や Process が複雑になると……
  5. 更新伝播の Source of Truth 継続モナドとリアクティブプログラミング 背景 Data Event Process Event

    リアクティブプログラミングでは Process が Data を購読する 値の更新 購読
  6. 更新伝播の Source of Truth 継続モナドとリアクティブプログラミング 背景 Process Data Event Event

    Process Process Data 購読 Processは、⾃⾝が依存する Data のみを Subscribe すればよい。
  7. 種類と特徴 • React Snapshot と差分適⽤ • Vue Reactive 値を明⽰的に扱う •

    FRP Event 列と Behavior の分離 • Rx 実践的リアクティブプログラミングライブラリスタック 継続モナドとリアクティブプログラミング 背景
  8. リソース安全性 リソース r が r' に依存しているとする リソース安全 = 任意の時点で、 「r

    が確保されている → r' が確保されている」 継続モナドとリアクティブプログラミング 背景 A B C 依存
  9. 観察 継続モナドとリアクティブプログラミング 背景 リソース r が r' に依存している → r'

    を r より前に解放してはいけない Rust とかは所有権でどうにかしている 発想: ⾃⾝の寿命を親に管理させる (つまり任意解放できない) 親側が⼦のリソースの初期化‧解放を管理 → これを時間軸⽅向に畳めば リアクティブプログラミングになりそう
  10. Routine 型 継続モナドとリアクティブプログラミング Quon type Routine t = Sync {result:

    Async t, dispose: Async ()} Sync / Async は Haskell では IO 外側から、任意のタイミングで Sync を呼ぶ その後、Routine は内部でいくつかリソースを確保 確保したリソースの内⼀つを result に表出 Sync を呼んだ後は任意のタイミングで dispose を呼べる
  11. Routine 型 継続モナドとリアクティブプログラミング Quon type Routine t = Sync {result:

    Async t, dispose: Async ()} time 初期化 result (t) dispose Routine t 外部
  12. Routine 型の bind 継続モナドとリアクティブプログラミング Quon let program = do let

    r1 = useR1(x) let r2 = useR2(r1) return resource2 初期化 解放 program useR1 useR2 Routine は解放処理を逆順に合成する 提供された Routine が Safe → 合成したものも必ず Safe
  13. Source 型 継続モナドとリアクティブプログラミング Quon type Source a = (a ->

    Routine ()) -> Routine () 直感: 内部に a 型のリソースを複数持つ構造 時間で変化しうる リソースが追加された時リスナーを初期化 リソースが削除された時そのリスナーを解放 この subscribe 状態⾃体もリソース(外側の Routine) リスナー “Subscribeリソース”の確保
  14. Source による Routine の階層 継続モナドとリアクティブプログラミング Quon type Source a =

    (a -> Routine ()) -> Routine () リスナーは、いわゆる「コンポーネント」の初期化 Routine はモナドであるので、 リスナーを作る時内側でネストした購読を張れる リスナー
  15. 継続モナド 突然ですが ContT r m a = (a -> m

    r) -> m r と定めます 継続モナドとリアクティブプログラミング Quon
  16. 継続モナド ContT r m a = (a -> m r)

    -> m r a 型の値を少しリッチにしたもの 「後続の計算 a -> m r を受け取り m r を返す関数」 を a 型の値の代わりとして⽤いる。 継続モナドとリアクティブプログラミング Quon
  17. 継続モナド ContT r m a = (a -> m r)

    -> m r a 型の値は容易に Cont r m a へ変換できる (渡された関数 a -> m r を a に適⽤し、m r を返す) これは Monad における pure (return) になりそう 継続モナドとリアクティブプログラミング Quon
  18. 継続モナド ContT r m a = (a -> m r)

    -> m r Monad のもう⼀つの操作 Join : Cont r m (Cont r m a) -> Cont r m a つまり ((((a -> m r) -> m r) -> m r) -> m r) -> (a -> m r) -> m r はできる? 継続モナドとリアクティブプログラミング Quon
  19. 継続モナド ((((a -> m r) -> m r) -> m

    r) -> m r) -> (a -> m r) -> m r 継続モナドとリアクティブプログラミング Quon 1. m r を作らなければいけない 2. (a -> m r) -> m r から m r を作る処理があれば m r が得られる 3. (a -> m r) -> m r に (a -> m r) を適⽤すれば良い。 実は Monad 則を満たす[読者への課題]ので Monad ややこしいが、できる
  20. 継続モナド 結局何ができる? reset : Monad m => ContT r m

    r -> ContT r' m r shift : Monad m => ((a -> m r) -> ContT r m r) -> ContT r m a 継続モナドとリアクティブプログラミング Quon その時点での後続の計算 後続の計算を好きに加⼯して実⾏
  21. 継続モナドの使⽤例 リソースマネジメント 後続の計算 c = hdl を受け取った後の処理 shift により open

    / close で c を挟む → hdl の make / freeが⼀か所で書ける 継続モナドとリアクティブプログラミング Quon f = do hdl <- shift (\c -> hdl’ <- open c hdl’ close hdl’ ) content <- read hdl …
  22. 継続モナドの使⽤例 ⼤域脱出 後続の計算を呼ばないことも可 すると⼤域脱出が可能 data が Invalid の場合短絡 継続モナドとリアクティブプログラミング Quon

    f = do data  fetch something t  shift (\c  case data of Invalid  return “error” Valid text  c text ) return $ “Hello”  t その他にもいろんな制御ができる
  23. 余談: Gleam の use はほぼ継続モナド 継続モナドとリアクティブプログラミング Quon // f :

    fn(fn(a)  r)  r // g : fn(fn(b)  s)  s // に対して use x  f use y  g body → f(fn(x){ g(fn(y){ body }) }) に脱糖される use を使うことで、 その時点での継続を⼊⼿ 構⽂レベルで Cont モナドを実装
  24. Source は継続モナド ContT r m a = (a -> m

    r) -> m r なので‧‧‧ Source a = (a -> Routine ()) -> Routine () = ContT () Routine a 継続モナドとリアクティブプログラミング Quon
  25. Source は継続モナド 何故 ContT になるのか Cont は後続の処理のコントロールを可能にする Source の場合は ‧「後続処理」はリスナー

    ‧「コントロール」は動的なリソースの追加/削除 を表す 継続モナドとリアクティブプログラミング Quon
  26. 継続モナドとしてSourceを使った場合 source = do let x = sourceX let y

    = sourceY(x) f(x, y) 継続モナドとリアクティブプログラミング Quon ネスト構造をフラットに書ける 空間を張り合わせているように⾒える 利点: ⾯⽩い sourceX x x x y y y y f(x, y) f(x, y) f(x, y) f(x, y) sourceY sourceY sourceY
  27. Quon : 例 app = do tab  useCell TabCounter

    onEvent $ \case EvKey (KChar '2') []  set tab TabProfile EvKey (KChar '3') []  set tab TabTodos _  pure () body  dyn loadingView $ cellSignal tab <&> \case TabProfile  profileTab TabTodos  todosTab pure $ body 継続モナドとリアクティブプログラミング Quon Tab 状態の作成 Event に対する反応 loadingView は React の Suspense みたいなもの Tab 状態の派⽣状態 (ここでは View)
  28. ⽐較 この Routine の仕組みは Vue や Solid.js の setup function

    に似ている ⼀⽅ Source のようなリソースの動的集合を主軸とするも のはあまりなさそう [要出典] Setup が Async にできるのは Async React っぽい 継続モナドとリアクティブプログラミング Quon
  29. 形式化(おまけ) ℛ : Resources Γ, Δ ⊆ ℛ : Environment

    r ↝ r′ : r depends to r' (ℛ : partial ordered by ↝) Σ = {init, dipose} ⨄ {7ill(r), dd} ⨄ {acq(r), rel(r)} : Events init, dispose: Input fill, dd: Output acq, rel: Internal 𝜎 ∈ Σ!" : Trace 𝑒 ∈ 𝜎 ↾ 𝑖: event e is evoked before time = i 𝜌# 𝑖 = {𝑟 ∈ ℛ: 𝘢𝘤𝘲 𝑟 ∈ 𝜎 ↾ 𝑖} ∖ {𝑟: 𝘳𝘦𝘭 𝑟 ∈ 𝜎 ↾ 𝑖}: Resources when t = i 継続モナドとリアクティブプログラミング Formalization
  30. 形式化(おまけ) Under environment Γ,𝜎 ∶ Σ!" is admissible iff: -

    (Uniq. resource) ack(r′) ∶ uniq. to r′, r′ ∉ Γ - (Init) 𝜎[0] = init - (Uniq. event) 7ill, dispose, dd, init ∶ up to 1 - (Fill consist.) if 𝜎[i] = 7ill(r), r ∈ 𝜌# 𝑖 - (Res. safety) if r ∈ 𝜌# 𝑖 , ∀r′ ↜ r, r′ ∈ 𝜌# 𝑖 - (Res. keep) until 7ill(r) to dispose, r ∈ 𝜌# t - (Disp.) if 𝜎[i] = dd, i is 7inal elem, and 𝜌# i = ∅ 継続モナドとリアクティブプログラミング Formalization
  31. 形式化(おまけ) 継続モナドとリアクティブプログラミング Formalization Definition of Routine Set M ⊆ Σ!",

    Γ ⊢ Routine(M) iff (1) ∀𝜎 ∈ M is admissible under Γ (2) ∃𝜎 ∈ M s. t. 𝜎[0] = init (3) ∀𝜎 ∈ M, ∀𝜎′: pre7ix of 𝜎, ∃𝜎′′ ∈ M, [. . . 𝜎′, dispose] is pre7ix of 𝜎′′
  32. 形式化(おまけ) 継続モナドとリアクティブプログラミング Formalization 今後の流れ ‧Routine の Monadic Chain を定義 ‧Routine

    が Resource Safe → それらの合成も Safe を証明 ‧Source の理想的な定義が Safe であることを証明 ここでは説明しません 何故なら…… ☝出来ていない (修論にする予定)
  33. Row型 そこで導⼊するのが Row 型 Row a: 変化する a 型の連続値 (Recap:

    Source a: 変化するリソース集合) 詳しい data 定義は述べない (まだ決まっていない) 継続モナドとリアクティブプログラミング Patch Semantics
  34. Patch型 Patch a b: Row a に対するリスナー Routine ではダメなのか? ‧Routine

    はそれぞれ独⽴したコンテキストを持つ ‧「前回の状態」を使いたい処理には向かない → 内部状態的なものを持つ必要がある 継続モナドとリアクティブプログラミング Patch Semantics
  35. Patch の性質 Patch a b は次のような定義を持ちそう Patch a b =

    a  Sync { result : Async b, next : Patch a b // 再帰的に呼べる } Patch a b は同じ対象に対する連続的変更を表す next を呼び、そこから得られた next を呼ぶ…を繰り返す 内部状態を持てる 継続モナドとリアクティブプログラミング Patch Semantics
  36. Patch は Monad ではない 継続モナドとリアクティブプログラミング Patch Semantics Patch a b

    Monad → if による分岐が可能 Patch は内部状態を持つので 1. 毎回同じ Patch を実⾏する (if ⽂なし) 2. if ⽂は特別扱いし、グラフ再構築&内部状態リセット 筋がよさそう Patch b d Patch b c Patch c d if
  37. Arrow の登場 a から b への抽象化された⼿続き P a b が

    Arrow → 変数の導⼊と静的な P a b に対する適⽤が可能 ArrowChoice → 途中で if 分岐が可能 ArrowApply → ⼿続き P a b が第⼀級になる Arrow → ArrowChoice → ArrowApply の順で強くなる 継続モナドとリアクティブプログラミング Patch Semantics
  38. Patch は ArrowChoice なしの Arrow ArrowChoice(分岐), ArrowApply(第⼀級関数) が無い これによって常に⼀本道、同じ順で評価される →

    グラフの再構築もしない 継続モナドとリアクティブプログラミング Patch Semantics proc pattern -> do y <- f -< e z <- g -< e’ h -< e'' こんな感じで書けるが、分岐を書くと型エラー
  39. ArrowChoice なしのArrow ≒ Rules of Hooks ところで if が使えない →

    Rules of Hooks に似ている ‧同じ順番で use… を呼ばなければいけない React は内部状態を Linked List で持っている 順番でのみ、Hooks の判別をする (グラフ構造が動的に変化してはいけない) → ArrowChoice なしの Arrow が適任 継続モナドとリアクティブプログラミング Patch Semantics
  40. 発展: Agent ⽤ DSL ‧作りかけ ‧Agent 定義を簡単にできる ‧Durable Execution ‧Parallel

    実⾏が楽 ‧Type Reflection、JSON Schema ⾃動⽣成 継続モナドとリアクティブプログラミング Component-based agent system Katari
  41. 現在の Katari: 例 Discord bot の例 (DEMO?) 継続モナドとリアクティブプログラミング Component-based agent

    system agent main(channel_id: string) -> string { use gemini.provider(model = "gemini-3.5-flash", api_key = env.get_secret(key = "GEMINI_API_KEY"), system =“他のユーザーからのメッセージに適切に返答してください。") use e2b.provider(api_key = env.get_secret(key = "E2B_API_KEY")) use tavily.provider(api_key = env.get_secret(key = "TAVILY_API_KEY")) use discord.provider(token = env.get_secret(key = "DISCORD_TOKEN")) let tools = [e2b.run_python, tavily.search] serve(channel_id = channel_id, tools = tools) } Function = Agent = Tool が成り⽴つ
  42. その他の機能 ‧MCP に接続、MCP を提供できる ‧Webhook に対する購読を管理できる ‧Coeffect (に似た仕組み)による Private 値トラッキング

    ‧Algebraic Effects による Context providing ‧ 継続モナドとリアクティブプログラミング Component-based agent system
  43. 観察 ‧MCP に接続、MCP を提供できる ‧Webhook に対する購読を管理できる serve(channel_id = channel_id, tools

    = tools) : キャンセルするまで動き続ける、discord 監視 & 応答 初期化 & 解放 → リソース 継続モナドとリアクティブプログラミング Component-based agent system
  44. 将来計画 リソース、動的な状態 → リソース指向リアクティブ‧プログラミングの出番 リアクティブプログラミングを応⽤して Agent Network を構成できそう ‧サンドボックス ‧分散システム(→Server

    Components?) ‧権限周り、コンテキスト → ここら辺をコンポーネントシステムで統括する 継続モナドとリアクティブプログラミング Component-based agent system