|
| 1 | +#+title: Interaction Trees |
| 2 | +#+subtitle: Representing Recursive and Impure Programs in Coq |
| 3 | +#+author: Juhun Lee |
| 4 | +#+date: <2025-08-07 Thu> |
| 5 | +#+options: timestamp:nil toc:nil num:nil |
| 6 | + |
| 7 | +#+reveal_head_preamble: <style> |
| 8 | +#+reveal_head_preamble: .reveal { word-break: keep-all; } |
| 9 | +#+reveal_head_preamble: .reveal hgroup p { font-size: var(--r-heading3-size); } |
| 10 | +#+reveal_head_preamble: </style> |
| 11 | + |
| 12 | +#+reveal_plugins: (highlight math) |
| 13 | +#+reveal_talk_url: https://talking.rangho.me/paper-verdi/ |
| 14 | +#+reveal_title_slide: <hgroup><h1>%t</h1><p>%a</p></hgroup><p>%d</p> |
| 15 | +#+options: reveal_width:"90%" |
| 16 | + |
| 17 | +* Introduction |
| 18 | +Let there be a Lisp-like programming language with two operators: |
| 19 | +- ~(+ n m)~ :: Addition of ~n~ and ~m~. |
| 20 | +- ~(- n m)~ :: Subtraction of ~n~ and ~m~. |
| 21 | + |
| 22 | +In this programming language, we want to verify the following expression: |
| 23 | + |
| 24 | +#+begin_src scheme |
| 25 | + (+ 1 2) ; => 3 |
| 26 | +#+end_src |
| 27 | + |
| 28 | +** Traditional Formal Verification |
| 29 | +The most straightforward method of verifying would look like: |
| 30 | + |
| 31 | +#+begin_src coq |
| 32 | + Inductive exp : Type := |
| 33 | + | Num (n : nat) |
| 34 | + | Add (e1 e2 : exp) |
| 35 | + | Sub (e1 e2 : exp). |
| 36 | + |
| 37 | + Fixpoint eval (e : exp) : nat := |
| 38 | + match e with |
| 39 | + | Num n => n |
| 40 | + | Add e1 e2 => (eval e1) + (eval e2) |
| 41 | + | Sub e1 e2 => (eval e1) - (eval e2) |
| 42 | + end. |
| 43 | +#+end_src |
| 44 | + |
| 45 | +** Formal Semantics |
| 46 | +Formal verification is, essentially, *analyzing the meaning of the program*. |
| 47 | + |
| 48 | +- If it makes sense, *verified*. |
| 49 | +- If it does not, *unverified*. |
| 50 | + |
| 51 | +** Operational Semantics |
| 52 | +The expression is evaluated /as a whole/. |
| 53 | + |
| 54 | +#+begin_src coq |
| 55 | + Fixpoint eval (e : exp) : nat := |
| 56 | + match e with |
| 57 | + | Num n => n |
| 58 | + | Add e1 e2 => (eval e1) + (eval e2) |
| 59 | + | Sub e1 e2 => (eval e1) - (eval e2) |
| 60 | + end. |
| 61 | +#+end_src |
| 62 | + |
| 63 | +This models the whole operation. |
| 64 | + |
| 65 | +*Operational semantics*. |
| 66 | + |
| 67 | +** What If... |
| 68 | +- ...I add more operators? |
| 69 | +- ...I add loop constructs? |
| 70 | +- ...I express the "same" logic in different programming language? |
| 71 | +- ...I target the "same" program for a different environment? |
| 72 | + |
| 73 | +** Abstraction! |
| 74 | +We need an abstraction model that is both: |
| 75 | + |
| 76 | +- language-independent, and |
| 77 | +- environment-independent. |
| 78 | + |
| 79 | +If a theory is independent of everything, is the theory meaningful at all? |
| 80 | + |
| 81 | +** Abstraction? |
| 82 | +To keep a theory relevant, a central question is necessary. |
| 83 | + |
| 84 | +*What is a program?* |
| 85 | + |
| 86 | +* Interaction Trees |
| 87 | +A program is *a sequence of interactions*. |
| 88 | + |
| 89 | +A program can: |
| 90 | + |
| 91 | +1. return a value and terminate, |
| 92 | +2. transition to a different program, or |
| 93 | +3. interact with the environment. |
| 94 | + |
| 95 | +** The ITree |
| 96 | +#+begin_src coq |
| 97 | + CoInductive itree (E : Type -> Type) (R : Type) : Type := |
| 98 | + | Ret (r : R) |
| 99 | + | Tau (t : itree E R) |
| 100 | + | Vis {A : Type} (e : E A) (k : A -> itree E R). |
| 101 | +#+end_src |
| 102 | + |
| 103 | +ITree is defined as a coinductive type to model non-terminating programs. |
| 104 | + |
| 105 | +** ITree Parameters |
| 106 | +#+begin_src coq |
| 107 | + CoInductive itree (E : Type -> Type) (R : Type) : Type := |
| 108 | + | Ret (r : R) |
| 109 | + | Tau (t : itree E R) |
| 110 | + | Vis {A : Type} (e : E A) (k : A -> itree E R). |
| 111 | +#+end_src |
| 112 | + |
| 113 | +- ~E : Type -> Type~ :: The type of external interaction. |
| 114 | +- ~R : Type~ :: The type of the result (i.e. return value). |
| 115 | + |
| 116 | +** ITree Constructors |
| 117 | +- ~Ret (r : R)~ :: Terminate the program by returning value ~r~. |
| 118 | +- ~Tau (t : itree E R)~ :: Perform internal calculation by transitioning to new program ~t~. |
| 119 | +- ~Vis {A : Type} (e : E A) (k : A -> itree E R)~ :: Interact with environment by triggering event ~e~. Based on the result, transition to new program generated by ~k~. |
| 120 | + |
| 121 | +** Example: Modeling I/O |
| 122 | +Imagine a system where you can input or output a natural number. |
| 123 | + |
| 124 | +#+begin_src coq |
| 125 | + Inductive IO : Type -> Type := |
| 126 | + | Input : IO nat |
| 127 | + | Output : nat -> IO unit. |
| 128 | +#+end_src |
| 129 | + |
| 130 | +- ~Input~ will bring a natural number to you from the user input. |
| 131 | +- ~Output~ will grab a natural number from you and show it to the user. |
| 132 | + |
| 133 | +*** Echo |
| 134 | +#+begin_src coq |
| 135 | + CoFixpoint echo : itree IO void := |
| 136 | + Vis Input (fun x => Vis (Output x) (fun _ => echo)). |
| 137 | +#+end_src |
| 138 | + |
| 139 | +1. Interact with environment by receiving ~Input~. |
| 140 | +2. From the result of the interaction, transition to a new program. |
| 141 | +3. The new program will interact with environment by calling ~Output~ with the result. |
| 142 | +4. The new program will transition back to 1. |
| 143 | + |
| 144 | +*** Spin |
| 145 | +#+begin_src coq |
| 146 | + CoFixpoint spin : itree IO void := |
| 147 | + Tau spin |
| 148 | +#+end_src |
| 149 | + |
| 150 | +1. Perform internal calcuation. |
| 151 | +2. Transition to 1. |
| 152 | + |
| 153 | +*** Kill9 |
| 154 | +#+begin_src coq |
| 155 | + CoFixpoint kill9 : itree IO unit := |
| 156 | + Vis Input (fun x => if x =? 9 then Ret tt else kill9). |
| 157 | +#+end_src |
| 158 | + |
| 159 | +1. Interact with environment by receiving ~Input~. |
| 160 | +2. From the result of the interaction, transition to a new program. |
| 161 | +3. The new program checks the number is equal to 9. |
| 162 | + 1. If it is, the new program is returning ~tt~ (nothing). |
| 163 | + 2. If it is not, the new program is going back to 1. |
| 164 | + |
| 165 | +** Composing ITrees as Monads |
| 166 | +Basic operations for interaction trees are follows: |
| 167 | + |
| 168 | +#+begin_src haskell |
| 169 | + itree E A :: Type |
| 170 | + Tau :: itree E A -> itree E A |
| 171 | + Ret :: A -> itree E A |
| 172 | + Vis :: {R} (E R) -> (R -> itree E A) -> itree E A |
| 173 | + bind :: itree E A -> (A -> itree E B) -> itree E B |
| 174 | + trigger :: E A -> itree E A |
| 175 | +#+end_src |
| 176 | + |
| 177 | +Since ~itree E~ is a monad for any ~E~, we can compose ITrees according to functional programming rules. |
| 178 | + |
| 179 | +*** Monad? |
| 180 | +Monad is a type wrapping another type, useful for: |
| 181 | + |
| 182 | +1. structuring sequences of operations on values, |
| 183 | +2. representing delayed computations, and |
| 184 | +3. many more. |
| 185 | + |
| 186 | +*** Example: IO Monad |
| 187 | +Recall the I/O example before. |
| 188 | +It is also a monad wrapping a ~Nat~: |
| 189 | + |
| 190 | +#+begin_src haskell |
| 191 | + data IO a = ... |
| 192 | + |
| 193 | + getNat :: IO Nat |
| 194 | + putNat :: Nat -> IO () |
| 195 | +#+end_src |
| 196 | + |
| 197 | +*** Binding Values |
| 198 | +For a wrapper to be a monad, you must be able to bind a value to another function. |
| 199 | + |
| 200 | +#+begin_src haskell |
| 201 | + (>>=) :: IO a -> (a -> IO b) -> IO b |
| 202 | + |
| 203 | + echo :: IO () |
| 204 | + echo = |
| 205 | + getNat -- IO wrapper with Nat value |
| 206 | + >>= -- Take out Nat from IO wrapper |
| 207 | + putNat -- Give the Nat as a parameter, resulting in an IO wrapper with empty value |
| 208 | +#+end_src |
| 209 | + |
| 210 | +For ITree, this binding operator is a function called ~bind~. |
| 211 | + |
| 212 | +*** Binding Values with Names |
| 213 | +Imagine a case where we put a lambda function instead of a defined function. |
| 214 | + |
| 215 | +#+begin_src haskell |
| 216 | + echo' = getNat >>= (\x -> putNat x) |
| 217 | +#+end_src |
| 218 | + |
| 219 | +This is, essentially, taking a value out of the wrapper and giving it a name ~x~. |
| 220 | + |
| 221 | +#+begin_src haskell |
| 222 | + echo' = do |
| 223 | + x <- getNat |
| 224 | + putNat x |
| 225 | +#+end_src |
| 226 | + |
| 227 | +For ITree, this example would have been written as ~x <- getNat ;; putNat x~. |
| 228 | + |
| 229 | +*** Returning Values |
| 230 | +Also, you must be able to make a regular value into a wrapped value. |
| 231 | + |
| 232 | +#+begin_src haskell |
| 233 | + return :: a -> IO a |
| 234 | + |
| 235 | + putOne = do |
| 236 | + x <- return 1 |
| 237 | + putNat x |
| 238 | +#+end_src |
| 239 | + |
| 240 | +For ITree, this "return" function is called ~ret~, which is a shorthand for the ~Ret (r : R)~ constructor. |
| 241 | + |
| 242 | +*** Rewriting Echo Example |
| 243 | +From this information, we can rewrite the ~echo~ program as a composition of basic ITree operations: |
| 244 | + |
| 245 | +#+begin_src coq |
| 246 | + CoFixpoint echo : itree IO void := |
| 247 | + Vis Input (fun x => Vis (Output x) (fun _ => echo)). |
| 248 | + |
| 249 | + CoFixpoint echo2 : itree IO void := |
| 250 | + x <- (trigger Input) ;; trigger (Output x) ;; Tau echo2. |
| 251 | +#+end_src |
| 252 | + |
| 253 | +1. Trigger ~Input~ event and bind the result as ~x~. |
| 254 | +2. Then, trigger ~Output~ event with ~x~. |
| 255 | +3. Then, transition to step 1. |
| 256 | + |
| 257 | +** Equivalence between ITrees |
| 258 | +There are two types of equivalence relations between ITrees \( t_{1} \) and \( t_{2} \): |
| 259 | + |
| 260 | +- Strong bisimulation \( t_{1} \cong t_{2} \) :: Two ITrees have /exactly/ same shape. |
| 261 | +- Weak bisimulation \( t_{1} \approx t_{2} \) :: Two ITrees have same interactions and same return values. |
| 262 | + |
| 263 | +** KTrees: Continuation Trees |
| 264 | +KTree is the type that determines which program to run after an interaction. |
| 265 | + |
| 266 | +(cf. ~Vis {A : Type} (e : E A) (k : A -> itree E R)~) |
| 267 | + |
| 268 | +#+begin_src coq |
| 269 | + Definition ktree (E : Type -> Type) (A B : Type) : Type := |
| 270 | + A -> itree E B. |
| 271 | + |
| 272 | + Definition eq_ktree {E} {A B : Type} : ktree E A B -> ktree E A B -> Prop := |
| 273 | + fun h1 h2 => ∀a, h1 a ≈ h2 a. |
| 274 | +#+end_src |
| 275 | + |
| 276 | +* Semantics of Events and Monadic Interpreters |
| 277 | +- In ITree, an *event handler* is an object of type \( E \leadsto M \). |
| 278 | + - Event ~E~ is a monadic operation in ~M~. |
| 279 | +- An *interpreter* folds such event handler over an ITree. |
| 280 | + - Good interpretation preserves the monadic structure of ~itree E~. |
| 281 | + |
| 282 | +** Example: Semantics of States |
| 283 | +Imagine a set of events getting and putting values into a state. |
| 284 | + |
| 285 | +#+begin_src coq |
| 286 | + Variant stateE (S : Type) : Type -> Type := |
| 287 | + | Get : stateE S S |
| 288 | + | Put : S -> stateE S unit. |
| 289 | +#+end_src |
| 290 | + |
| 291 | +** Event into Monad |
| 292 | +We can define a transformer that turns this event into a monad. |
| 293 | + |
| 294 | +#+begin_src coq |
| 295 | + Definition stateT (S : Type) (M : Type -> Type) (R : Type) : Type := |
| 296 | + S -> M (S * R). |
| 297 | + Definition getT (S : Type) : stateT S M S := |
| 298 | + fun s => ret (s, s). |
| 299 | + Definition putT (S : Type) : S -> stateT S M unit := |
| 300 | + fun s' s => ret (s', tt). |
| 301 | +#+end_src |
| 302 | + |
| 303 | +~stateT~ takes a state and returns a monad of new state and resulting value. |
| 304 | + |
| 305 | +** Event Handler |
| 306 | +Then, we can actually define a handler that repackages events. |
| 307 | + |
| 308 | +#+begin_src coq |
| 309 | + Definition h_state (S : Type) {E} : (stateE S) ~> stateT S (itree E) := |
| 310 | + fun _ e => |
| 311 | + match e with |
| 312 | + | Get => getT S |
| 313 | + | Put s => putT S s |
| 314 | + end. |
| 315 | +#+end_src |
| 316 | + |
| 317 | +Note that ~stateT S (itree E)~ is ~S -> itree E (S * R)~, where it takes a state and returns an ITree with event ~E~ and result type of new state and return value. |
| 318 | + |
| 319 | +** Monadic Interpreter |
| 320 | +Finally, we can define an interpreter that will traverse the ITree. |
| 321 | + |
| 322 | +#+begin_src coq |
| 323 | + Definition interp_state {E S} : itree (stateE S) ~> stateT S (itree E) := |
| 324 | + interp h_state. |
| 325 | +#+end_src |
| 326 | + |
| 327 | +Here, ~interp~ is a generic interpreter provided by ITree that walks the whole ITree while applying event handler in every step. |
| 328 | + |
| 329 | +* Case Study: Verified Compilation of IMP to ASM |
| 330 | +Consider the IMP langauge from /Software Foundations/ volume 1. |
| 331 | + |
| 332 | +Let there be a simple instruction set called ASM. |
| 333 | + |
| 334 | +We can devise a compiler and verify its correctness. |
| 335 | + |
| 336 | +** Semantics of IMP |
| 337 | +The semantics given to ~imp~ (a ~com~ in /SF/) is shown below. |
| 338 | + |
| 339 | +[[file:assets/imp-semantics.png]] |
| 340 | + |
| 341 | +** Semantics of ASM |
| 342 | +The semantics given to ~asm~ is shown below. |
| 343 | + |
| 344 | +[[file:assets/asm-semantics.png]] |
| 345 | + |
| 346 | +** Control-flow Graph of ASM |
| 347 | +The semantics of ASM is rearranged to form a composable linking combinators. |
| 348 | + |
| 349 | +[[file:assets/asm-flow.png]] |
| 350 | + |
| 351 | +** Compilation |
| 352 | +Statements in IMP are matched with ASM linking combinators to compile. |
| 353 | + |
| 354 | +#+begin_src coq |
| 355 | + Fixpoint compile (s : stmt) {struct s} : asm 1 1 := |
| 356 | + match s with |
| 357 | + | Skip => id_asm |
| 358 | + | Assign x e => raw_asm_block (after (compile_assign x e) (Bjmp l1)) |
| 359 | + | Seq l r => seq_asm (compile l) (compile r) |
| 360 | + | If e l r => if_asm (compile_expr 0 e) (compile l) (compile r) |
| 361 | + | While e b => while_asm (compile_expr 0 e) (compile b) |
| 362 | + end. |
| 363 | +#+end_src |
| 364 | + |
| 365 | +If the IMP program and corresponding ASM program have bisimulational relationship, then two programs are equivalent. |
| 366 | + |
| 367 | +#+begin_src coq |
| 368 | + Theorem compile_correct (s : stmt) : equivalent s (compile s). |
| 369 | +#+end_src |
| 370 | + |
| 371 | +* Conclusion |
| 372 | +*ITree* forms a basis on which to represent impure recursive programs with Coq. |
| 373 | + |
| 374 | +Its denotational approach allows highly expressive represntation of programs. |
| 375 | + |
| 376 | +* Further Works |
| 377 | +- Automation of proofs using interaction tree? |
| 378 | +- Porting this logic to proof assistants other than Coq/Rocq? |
0 commit comments