3 {1 Defunctionalization}
5 This is inspired from student exercises proposed by
6 {h <a href="http://cs.au.dk/~danvy/">Olivier Danvy</a>}
7 at the {h <a href="http://jfla.inria.fr/2014/">JFLA 2014 conference</a>}
12 (** {2 Simple Arithmetic Expressions} *)
18 (** Grammar of expressions
30 type expr = Cte int | Sub expr expr
38 p2 = (10 - 6) - (7 - 2)
39 p3 = (7 - 2) - (10 - 6)
44 let constant p0 : prog = Cte 0
46 let constant p1 : prog = Sub (Cte 10) (Cte 6)
48 let constant p2 : prog = Sub (Sub (Cte 10) (Cte 6)) (Sub (Cte 7) (Cte 2))
50 let constant p3 : prog = Sub (Sub (Cte 7) (Cte 2)) (Sub (Cte 10) (Cte 6))
52 let constant p4 : prog = Sub (Cte 10) (Sub (Cte 2) (Cte 3))
57 (** {2 Direct Semantics} *)
71 ve : expressible_value
79 e1 => n1 e2 => n2 n1 - n2 = n3
80 ----------------------------------
83 A program e is interpreted into a value n if judgement
91 Program the interpreter above and test it on the examples.
93 eval_0 : expression -> expressible_value
94 interpret_0 : program -> expressible_value
98 (* Note: Why3 definitions introduced by "function" belong to the logic
99 part of Why3 language *)
101 let rec function eval_0 (e:expr) : int =
104 | Sub e1 e2 -> eval_0 e1 - eval_0 e2
107 let function interpret_0 (p:prog) : int = eval_0 p
109 (** Tests, can be replayed using
111 why3 defunctionalization.mlw --exec DirectSem.test
113 (Why3 version at least 0.82 required)
125 constant v3 : int = eval_0 p3
127 goal eval_p3 : v3 = 1
132 (** {2 CPS: Continuation Passing Style} *)
140 CPS-transform (call by value, left to right) the function `eval_0`,
141 and call it from the main interpreter with the identity function as
144 eval_1 : expression -> (expressible_value -> 'a) -> 'a
145 interpret_1 : program -> expressible_value
152 let rec eval_1 (e:expr) (k: int->'a) : 'a
154 ensures { result = k (eval_0 e) }
158 eval_1 e1 (fun v1 -> eval_1 e2 (fun v2 -> k (v1 - v2)))
161 let interpret_1 (p : prog) : int
162 ensures { result = eval_0 p }
163 = eval_1 p (fun n -> n)
169 (** {2 Defunctionalization} *)
171 module Defunctionalization
178 De-functionalize the continuation of `eval_1`.
182 continue_2 : cont -> value -> value
183 eval_2 : expression -> cont -> value
184 interpret_2 : program -> value
186 The data type `cont` represents the grammar of contexts.
188 The two mutually recursive functions `eval_2` and `continue_2`
189 implement an abstract machine, that is a state transition system.
193 type cont = A1 expr cont | A2 int cont | I
195 (** One would want to write in Why:
197 function eval_cont (c:cont) (v:int) : int =
200 let v2 = eval_0 e2 in
201 eval_cont (A2 v k) v2
202 | A2 v1 k -> eval_cont k (v1 - v)
206 But since the recursion is not structural, Why3 kernel rejects it
207 (definitions in the logic part of the language must be total)
209 We replace that with a relational definition, an inductive one.
213 inductive eval_cont cont int int =
215 forall e2:expr, k:cont, v:int, r:int.
216 eval_cont (A2 v k) (eval_0 e2) r -> eval_cont (A1 e2 k) v r
218 forall v1:int, k:cont, v:int, r:int.
219 eval_cont k (v1 - v) r -> eval_cont (A2 v1 k) v r
221 forall v:int. eval_cont I v v
223 (** Some functions to serve as measures for the termination proof *)
225 function size_e (e:expr) : int =
228 | Sub e1 e2 -> 3 + size_e e1 + size_e e2
231 lemma size_e_pos: forall e: expr. size_e e >= 1
233 function size_c (c:cont) : int =
236 | A1 e2 k -> 2 + size_e e2 + size_c k
237 | A2 _ k -> 1 + size_c k
240 lemma size_c_pos: forall c: cont. size_c c >= 0
242 (** WhyML programs (declared with `let` instead of `function`),
243 mutually recursive, resulting from de-functionalization *)
245 let rec continue_2 (c:cont) (v:int) : int
247 ensures { eval_cont c v result }
250 | A1 e2 k -> eval_2 e2 (A2 v k)
251 | A2 v1 k -> continue_2 k (v1 - v)
255 with eval_2 (e:expr) (c:cont) : int
256 variant { size_c c + size_e e }
257 ensures { eval_cont c (eval_0 e) result }
260 | Cte n -> continue_2 c n
261 | Sub e1 e2 -> eval_2 e1 (A1 e2 c)
264 (** The interpreter. The post-condition specifies that this program
265 computes the same thing as `eval_0` *)
267 let interpret_2 (p:prog) : int
268 ensures { result = eval_0 p }
281 (** {2 Defunctionalization with an algebraic variant} *)
283 module Defunctionalization2
290 De-functionalize the continuation of `eval_1`.
294 continue_2 : cont -> value -> value
295 eval_2 : expression -> cont -> value
296 interpret_2 : program -> value
298 The data type `cont` represents the grammar of contexts.
300 The two mutually recursive functions `eval_2` and `continue_2`
301 implement an abstract machine, that is a state transition system.
305 type cont = A1 expr cont | A2 int cont | I
307 (** One would want to write in Why:
309 function eval_cont (c:cont) (v:int) : int =
312 let v2 = eval_0 e2 in
313 eval_cont (A2 v k) v2
314 | A2 v1 k -> eval_cont k (v1 - v)
318 But since the recursion is not structural, Why3 kernel rejects it
319 (definitions in the logic part of the language must be total)
321 We replace that with a relational definition, an inductive one.
325 inductive eval_cont cont int int =
327 forall e2:expr, k:cont, v:int, r:int.
328 eval_cont (A2 v k) (eval_0 e2) r -> eval_cont (A1 e2 k) v r
330 forall v1:int, k:cont, v:int, r:int.
331 eval_cont k (v1 - v) r -> eval_cont (A2 v1 k) v r
333 forall v:int. eval_cont I v v
335 (** Peano naturals to serve as the measure for the termination proof *)
339 function size_e (e:expr) (acc:nat) : nat =
342 | Sub e1 e2 -> S (size_e e1 (S (size_e e2 (S acc))))
345 function size_c (c:cont) (acc:nat) : nat =
348 | A1 e2 k -> S (size_e e2 (S (size_c k acc)))
349 | A2 _ k -> S (size_c k acc)
352 (** WhyML programs (declared with `let` instead of `function`),
353 mutually recursive, resulting from de-functionalization *)
355 let rec continue_2 (c:cont) (v:int) : int
356 variant { size_c c O }
357 ensures { eval_cont c v result }
360 | A1 e2 k -> eval_2 e2 (A2 v k)
361 | A2 v1 k -> continue_2 k (v1 - v)
365 with eval_2 (e:expr) (c:cont) : int
366 variant { size_e e (size_c c O) }
367 ensures { eval_cont c (eval_0 e) result }
370 | Cte n -> continue_2 c n
371 | Sub e1 e2 -> eval_2 e1 (A1 e2 c)
374 (** The interpreter. The post-condition specifies that this program
375 computes the same thing as `eval_0` *)
377 let interpret_2 (p:prog) : int
378 ensures { result = eval_0 p }
391 (** {2 Semantics with errors} *)
403 ve : expressible_value
408 type value = Vnum int | Underflow
409 (* in (Vnum n), n should always be >= 0 *)
424 e1 => n1 e2 => n2 n1 < n2
425 -----------------------------
426 e1 - e2 => "underflow"
428 e1 => n1 e2 => n2 n1 >= n2 n1 - n2 = n3
429 ---------------------------------------------
432 We interpret the program `e` into value `n` if the judgement
436 holds, and into error `s` if the judgement
445 Program the interpreter above and test it on the examples.
447 eval_0 : expr -> expressible_value
448 interpret_0 : program -> expressible_value
452 function eval_0 (e:expr) : value =
454 | Cte n -> if n >= 0 then Vnum n else Underflow
457 | Underflow -> Underflow
460 | Underflow -> Underflow
462 if v1 >= v2 then Vnum (v1 - v2) else Underflow
467 function interpret_0 (p:prog) : value = eval_0 p
472 CPS-transform (call by value, from left to right)
473 the function `eval_0`, call it from the main interpreter
474 with the identity function as initial continuation.
476 eval_1 : expr -> (expressible_value -> 'a) -> 'a
477 interpret_1 : program -> expressible_value
482 function eval_1 (e:expr) (k:value -> 'a) : 'a =
484 | Cte n -> k (if n >= 0 then Vnum n else Underflow)
488 | Underflow -> k Underflow
492 | Underflow -> k Underflow
493 | Vnum v2 -> k (if v1 >= v2 then Vnum (v1 - v2) else Underflow)
498 function interpret_1 (p : prog) : value = eval_1 p (fun n -> n)
501 lemma cps_correct_expr:
502 forall e: expr. forall k:value -> 'a. eval_1 e k = k (eval_0 e)
504 lemma cps_correct: forall p. interpret_1 p = interpret_0 p
508 Divide the continuation
510 expressible_value -> 'a
514 (value -> 'a) * (error -> 'a)
516 and adapt `eval_1` and `interpret_1` as
518 eval_2 : expr -> (value -> 'a) -> (error -> 'a) -> 'a
519 interpret_2 : program -> expressible_value
526 function eval_2 (e:expr) (k:int -> 'a) (kerr: unit -> 'a) : 'a =
528 | Cte n -> if n >= 0 then k n else kerr ()
532 if v1 >= v2 then k (v1 - v2) else kerr ())
537 function eval_2 (e:expr) (k:int -> 'a) (kerr: unit -> 'a) : 'a =
539 | Cte n -> if n >= 0 then k n else kerr ()
541 eval_2 e1 (eval_2a e2 k kerr) kerr
544 with eval_2a (e2:expr) (k:int -> 'a) (kerr : unit -> 'a) : int -> 'a =
545 (fun v1 -> eval_2 e2 (eval_2b v1 k kerr) kerr)
547 with eval_2b (v1:int) (k:int -> 'a) (kerr : unit -> 'a) : int -> 'a =
548 (fun v2 -> if v1 >= v2 then k (v1 - v2) else kerr ())
552 function interpret_2 (p : prog) : value =
553 eval_2 p (fun n -> Vnum n) (fun _ -> Underflow)
556 lemma cps2_correct_expr_aux:
557 forall k: int -> 'a, e1 e2, kerr: unit -> 'a.
558 eval_2 (Sub e1 e2) k kerr = eval_2 e1 (eval_2a e2 k kerr) kerr
560 lemma cps2_correct_expr:
561 forall e, kerr: unit->'a, k:int -> 'a. eval_2 e k kerr =
562 match eval_0 e with Vnum n -> k n | Underflow -> kerr () end
564 lemma cps2_correct: forall p. interpret_2 p = interpret_0 p
568 Specialize the codomain of the continuations and of the evaluation function
569 so that it is not polymorphic anymore but is `expressible_value`, and
570 then short-circuit the second continuation to stop in case of error
572 eval_3 : expr -> (value -> expressible_value) -> expressible_value
573 interpret_3 : program -> expressible_value
575 NB: Now there is only one continuation and it is applied only in
581 function eval_3 (e:expr) (k:int -> value) : value =
583 | Cte n -> if n >= 0 then k n else Underflow
585 eval_3 e1 (eval_3a e2 k)
588 with eval_3a (e2:expr) (k:int -> value) : int -> value =
589 (fun v1 -> eval_3 e2 (eval_3b v1 k))
591 with eval_3b (v1:int) (k:int -> value) : int -> value =
592 (fun v2 -> if v1 >= v2 then k (v1 - v2) else Underflow)
595 function interpret_3 (p : prog) : value = eval_3 p (fun n -> Vnum n)
597 let rec lemma cps3_correct_expr (e:expr) : unit
599 ensures { forall k. eval_3 e k =
600 match eval_0 e with Vnum n -> k n | Underflow -> Underflow end }
604 cps3_correct_expr e1;
605 cps3_correct_expr e2;
606 assert {forall k. eval_3 e k = eval_3 e1 (eval_3a e2 k) }
609 lemma cps3_correct: forall p. interpret_3 p = interpret_0 p
613 De-functionalize the continuation of `eval_3`.
617 continue_4 : cont -> value -> expressible_value
618 eval_4 : expr -> cont -> expressible_value
619 interprete_4 : program -> expressible_value
621 The data type `cont` represents the grammar of contexts.
622 (NB. has it changed w.r.t to previous exercise?)
624 The two mutually recursive functions `eval_4` and `continue_4`
625 implement an abstract machine, that is a transition system that
626 stops immediately in case of error, or and the end of computation.
632 type cont = I | A expr cont | B int cont
636 One would want to write in Why:
638 function eval_cont (c:cont) (v:value) : value =
640 | Underflow -> Underflow
643 | A e2 k -> eval_cont (B (Vnum v) k) (eval_0 e2)
644 | B v1 k -> eval_cont k (if v1 >= v then Vnum (v1 - v) else Underflow)
645 | I -> Vnum v (* v >= 0 by construction *)
648 But since the recursion is not structural, Why3 kernel rejects it
649 (definitions in the logic part of the language must be total)
651 We replace that with a relational definition, an inductive one.
655 inductive eval_cont cont value value =
657 forall k:cont. eval_cont k Underflow Underflow
659 forall e2:expr, k:cont, v:int, r:value.
660 eval_cont (B v k) (eval_0 e2) r -> eval_cont (A e2 k) (Vnum v) r
662 forall v1:int, k:cont, v:int, r:value.
663 eval_cont k (if v1 >= v then Vnum (v1 - v) else Underflow) r
664 -> eval_cont (B v1 k) (Vnum v) r
666 forall v:int. eval_cont I (Vnum v) (Vnum v)
668 (** Some functions to serve as measures for the termination proof *)
670 function size_e (e:expr) : int =
673 | Sub e1 e2 -> 3 + size_e e1 + size_e e2
676 lemma size_e_pos: forall e: expr. size_e e >= 1
678 use Defunctionalization as D
680 function size_c (c:cont) : int =
683 | A e2 k -> 2 + D.size_e e2 + size_c k
684 | B _ k -> 1 + size_c k
687 lemma size_c_pos: forall c: cont. size_c c >= 0
689 let rec continue_4 (c:cont) (v:int) : value
692 ensures { eval_cont c (Vnum v) result }
695 | A e2 k -> eval_4 e2 (B v k)
696 | B v1 k -> if v1 >= v then continue_4 k (v1 - v) else Underflow
700 with eval_4 (e:expr) (c:cont) : value
701 variant { size_c c + D.size_e e }
702 ensures { eval_cont c (eval_0 e) result }
705 | Cte n -> if n >= 0 then continue_4 c n else Underflow
706 | Sub e1 e2 -> eval_4 e1 (A e2 c)
709 (** The interpreter. The post-condition specifies that this program
710 computes the same thing as `eval_0` *)
712 let interpret_4 (p:prog) : value
713 ensures { result = eval_0 p }
729 (** {2 Reduction Semantics} *)
731 module ReductionSemantics
734 A small step semantics, defined iteratively with reduction contexts
767 (in the case of relative integers Z, all potential redexes are true redexes;
768 but for natural numbers, not all of them are true ones:
770 n1 - n2 -> n3 if n1 >= n2
772 a potential redex that is not a true one is stuck.)
774 Contraction Function:
776 contract : redex_potentiel -> expression + stuck
777 contract (n1 - n2) = n3 si n3 = n1 - n2
779 and if there are only non-negative numbers:
781 contract (n1 - n2) = n3 if n1 >= n2 and n3 = n1 - n2
782 contract (n1 - n2) = stuck if n1 < n2
786 predicate is_a_redex (e:expr) =
788 | Sub (Cte _) (Cte _) -> true
792 let contract (e:expr) : expr
793 requires { is_a_redex e }
794 ensures { eval_0 result = eval_0 e }
797 | Sub (Cte v1) (Cte v2) -> Cte (v1 - v2)
805 C ::= [] | [C e] | [v C]
809 type context = Empty | Left context expr | Right int context
814 recompose : cont * expression -> expression
815 recompose ([], e) = e
816 recompose ([C e2], e1) = recompose (C, e1 - e2)
817 recompose ([n1 C], e2) = recompose (C, n1 - e2)
821 let rec function recompose (c:context) (e:expr) : expr =
824 | Left c e2 -> recompose c (Sub e e2)
825 | Right n1 c -> recompose c (Sub (Cte n1) e)
828 let rec lemma recompose_values (c:context) (e1 e2:expr) : unit
829 requires { eval_0 e1 = eval_0 e2 }
831 ensures { eval_0 (recompose c e1) = eval_0 (recompose c e2) }
834 | Left c e -> recompose_values c (Sub e1 e) (Sub e2 e)
835 | Right n c -> recompose_values c (Sub (Cte n) e1) (Sub (Cte n) e2)
838 (* not needed anymore
839 let rec lemma recompose_inversion (c:context) (e:expr)
841 match c with Empty -> false | _ -> true end \/
842 match e with Cte _ -> false | Sub _ _ -> true end }
844 ensures { match recompose c e with
845 Cte _ -> false | Sub _ _ -> true end }
848 | Left c e2 -> recompose_inversion c (Sub e e2)
849 | Right n1 c -> recompose_inversion c (Sub (Cte n1) e)
856 dec_or_val = (C, rp) | v
858 Decomposition function:
860 decompose_term : expression * cont -> dec_or_val
861 decompose_term (n, C) = decompose_cont (C, n)
862 decompose_term (e1 - e2, C) = decompose_term (e1, [C e2])
864 decompose_cont : cont * value -> dec_or_val
865 decompose_cont ([], n) = n
866 decompose_cont ([C e], n) = decompose_term (e, [n c])
867 decompose_term ([n1 C], n2) = (C, n1 - n2)
869 decompose : expression -> dec_or_val
870 decompose e = decompose_term (e, [])
876 predicate is_a_value (e:expr) =
882 predicate is_empty_context (c:context) =
889 use Defunctionalization as D (* for size_e *)
891 function size_e (e:expr) : int = D.size_e e
893 function size_c (c:context) : int =
896 | Left c e -> 2 + size_c c + size_e e
897 | Right _ c -> 1 + size_c c
900 lemma size_c_pos: forall c: context. size_c c >= 0
903 let rec decompose_term (e:expr) (c:context) : (context, expr)
904 variant { size_e e + size_c c }
905 ensures { let c1,e1 = result in
906 recompose c1 e1 = recompose c e /\
908 raises { Stuck -> is_empty_context c /\ is_a_value e }
909 (* raises { Stuck -> is_a_value (recompose c e) } *)
912 | Cte n -> decompose_cont c n
913 | Sub e1 e2 -> decompose_term e1 (Left c e2)
916 with decompose_cont (c:context) (n:int) : (context, expr)
918 ensures { let c1,e1 = result in
919 recompose c1 e1 = recompose c (Cte n) /\
921 raises { Stuck -> is_empty_context c }
922 (* raises { Stuck -> is_a_value (recompose c (Cte n)) } *)
925 | Empty -> raise Stuck
926 | Left c e -> decompose_term e (Right n c)
927 | Right n1 c -> c, Sub (Cte n1) (Cte n)
930 let decompose (e:expr) : (context, expr)
931 ensures { let c1,e1 = result in recompose c1 e1 = e /\
933 raises { Stuck -> is_a_value e }
935 decompose_term e Empty
940 reduce : expression -> expression + stuck
943 then reduce e = stuck
945 if decompose e = (C, rp)
946 and contract rp = stuck
947 then reduce e = stuck
949 if decompose e = (C, rp)
951 then reduce e = recompose (C, c)
956 Implement the reduction semantics above and test it
960 let reduce (e:expr) : expr
961 ensures { eval_0 result = eval_0 e }
962 raises { Stuck -> is_a_value e }
964 let c,r = decompose e in
965 recompose c (contract r)
968 Evaluation based on iterated reduction:
970 itere : red_ou_val -> value + erreur
974 if contract rp = stuck
975 then itere (C, rp) = stuck
978 then itere (C, rp) = itere (decompose (recompose (C, c)))
983 let rec itere (e:expr) : int
984 diverges (* termination could be proved but that's not the point *)
985 ensures { eval_0 e = result }
997 Optimize the step recomposition / decomposition
998 into a single function `refocus`.
1003 ensures { let c1,e1 = result in
1004 recompose c1 e1 = recompose c e /\
1006 raises { Stuck -> is_empty_context c /\ is_a_value e }
1007 = decompose_term e c
1009 let rec itere_opt (c:context) (e:expr) : int
1011 ensures { result = eval_0 (recompose c e) }
1012 = match refocus c e with
1013 | c, r -> itere_opt c (contract r)
1014 | exception Stuck ->
1015 assert { is_empty_context c };
1022 let rec normalize (e:expr)
1024 ensures { result = eval_0 e }
1030 (** {4 Exercise 2.2}
1031 Derive an abstract machine
1035 {h <blockquote><pre>
1036 (c,Cte n)_1 -> (c,n)_2
1037 (c,Sub e1 e2)_1 -> (Left c e2,e1)_1
1038 (Empty,n)_2 -> stop with result = n
1039 (Left c e,n)_2 -> (Right n c,e)_1
1040 (Right n1 c,n)_2 -> (c,Cte (n1 - n))_1
1041 </pre></blockquote>}
1045 variant { 2 * (size_c c + size_e e) }
1046 ensures { result = eval_0 (recompose c e) }
1048 | Cte n -> eval_2 c n
1049 | Sub e1 e2 -> eval_1 (Left c e2) e1
1053 variant { 1 + 2 * size_c c }
1054 ensures { result = eval_0 (recompose c (Cte n)) }
1057 | Left c e -> eval_1 (Right n c) e
1058 | Right n1 c -> eval_1 c (Cte (n1 - n))
1062 ensures { result = eval_0 p }
1082 (** {4 Exercise 2.3}
1083 An abstract machine for the case with errors
1088 {h <blockquote><pre>
1089 (c,Cte n)_1 -> stop on Underflow if n < 0
1090 (c,Cte n)_1 -> (c,n)_2 if n >= 0
1091 (c,Sub e1 e2)_1 -> (Left c e2,e1)_1
1092 (Empty,n)_2 -> stop on Vnum n
1093 (Left c e,n)_2 -> (Right n c,e)_1
1094 (Right n1 c,n)_2 -> stop on Underflow if n1 < n
1095 (Right n1 c,n)_2 -> (c,Cte (n1 - n))_1 if n1 >= n
1096 </pre></blockquote>}
1100 type context = Empty | Left context expr | Right int context
1102 use Defunctionalization as D (* for size_e *)
1104 function size_e (e:expr) : int = D.size_e e
1106 function size_c (c:context) : int =
1109 | Left c e -> 2 + size_c c + size_e e
1110 | Right _ c -> 1 + size_c c
1113 lemma size_c_pos: forall c: context. size_c c >= 0
1115 function recompose (c:context) (e:expr) : expr =
1118 | Left c e2 -> recompose c (Sub e e2)
1119 | Right n1 c -> recompose c (Sub (Cte n1) e)
1122 let rec lemma recompose_values (c:context) (e1 e2:expr) : unit
1123 requires { eval_0 e1 = eval_0 e2 }
1125 ensures { eval_0 (recompose c e1) = eval_0 (recompose c e2) }
1128 | Left c e -> recompose_values c (Sub e1 e) (Sub e2 e)
1129 | Right n c -> recompose_values c (Sub (Cte n) e1) (Sub (Cte n) e2)
1132 let rec lemma recompose_underflow (c:context) (e:expr) : unit
1133 requires { eval_0 e = Underflow }
1135 ensures { eval_0 (recompose c e) = Underflow }
1138 | Left c e1 -> recompose_underflow c (Sub e e1)
1139 | Right n c -> recompose_underflow c (Sub (Cte n) e)
1145 variant { 2 * (size_c c + size_e e) }
1146 ensures { result = eval_0 (recompose c e) }
1148 | Cte n -> if n >= 0 then eval_2 c n else Underflow
1149 | Sub e1 e2 -> eval_1 (Left c e2) e1
1153 variant { 1 + 2 * size_c c }
1155 ensures { result = eval_0 (recompose c (Cte n)) }
1158 | Left c e -> eval_1 (Right n c) e
1159 | Right n1 c -> if n1 >= n then eval_1 c (Cte (n1 - n)) else Underflow
1163 ensures { result = eval_0 p }