Skip to content

Commit a1d2e1f

Browse files
committed
feat: add itree paper review
1 parent 6ca572b commit a1d2e1f

File tree

4 files changed

+378
-0
lines changed

4 files changed

+378
-0
lines changed

paper-itree/assets/asm-flow.png

59.5 KB
Loading
165 KB
Loading
118 KB
Loading

paper-itree/index.reveal.org

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
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

Comments
 (0)