summaryrefslogtreecommitdiff
path: root/backends/hol4/divDefExampleScript.sml
diff options
context:
space:
mode:
authorSon Ho2023-05-12 20:17:26 +0200
committerSon HO2023-06-04 21:54:38 +0200
commit8a5c5e4ae0cab0ab627c25ece59453a8e4bd4b64 (patch)
tree2e92885f457b610d183cf2e7f18fd05c5219ba60 /backends/hol4/divDefExampleScript.sml
parentc49fd4b6230a1f926e929f133794b6f73d338077 (diff)
Cleanup the files of the HOL4 backend
Diffstat (limited to 'backends/hol4/divDefExampleScript.sml')
-rw-r--r--backends/hol4/divDefExampleScript.sml418
1 files changed, 418 insertions, 0 deletions
diff --git a/backends/hol4/divDefExampleScript.sml b/backends/hol4/divDefExampleScript.sml
new file mode 100644
index 00000000..338385ab
--- /dev/null
+++ b/backends/hol4/divDefExampleScript.sml
@@ -0,0 +1,418 @@
+(* Manually written examples of how to use the fixed-point operator from divDefScript *)
+
+open HolKernel boolLib bossLib Parse
+open boolTheory arithmeticTheory integerTheory intLib listTheory stringTheory
+
+open primitivesArithTheory primitivesBaseTacLib ilistTheory primitivesTheory
+open primitivesLib
+open divDefTheory
+
+val _ = new_theory "divDefExample"
+
+(* Rewrite the goal once, and on the left part of the goal seen as an application *)
+fun pure_once_rewrite_left_tac ths =
+ CONV_TAC (PATH_CONV "l" (PURE_ONCE_REWRITE_CONV ths))
+
+(*=============================================
+ * Example 1: nth (simply recursive function)
+ *=============================================*)
+
+Datatype:
+ list_t =
+ ListCons 't list_t
+ | ListNil
+End
+
+(* We want to use the fixed-point operator ‘fix’ to define a function ‘nth’
+ which satisfies the following equation: *)
+
+val nth_qt = ‘
+ nth (ls : 't list_t) (i : u32) : 't result =
+ case ls of
+ | ListCons x tl =>
+ if u32_to_int i = (0:int)
+ then (Return x)
+ else
+ do
+ i0 <- u32_sub i (int_to_u32 1);
+ nth tl i0
+ od
+ | ListNil => Fail Failure
+’
+
+(* When generating a recursive definition, we apply a fixed-point operator to a
+ non-recursive function *body*, in which the recursive calls are handled to
+ a continuation. In case we define a group of mutually recursive
+ definitions, we generate *one* single body for the whole group of definitions.
+ It works as follows.
+
+ The input of the body is an enumeration: we start by branching over this input such
+ that every branch corresponds to one function in the mutually recursive group.
+ Whenever we make a recursive call, we wrap the input parameters into the proper
+ variant before calling the continuation: in effect this allows us to choose
+ the function from the group which gets evaluated.
+
+ Moreover, because of constraints on the way the fixed-point operator is defined,
+ the input of the body must have the same type as its output: we also
+ store the outputs of the functions in this big enumeration.
+
+ In order to make this work, we need to shape the body so that:
+ - input values (at call sites) and output values (when returning) are properly
+ injected into the enumeration
+ - whenever we get an output value (which is an enumeration) from a recursive
+ call, we extract the value from the proper variant of the enumeration
+
+ We encode the enumeration with a nested sum type, whose constructors
+ are ‘INL’ and ‘INR’.
+
+ In the case of ‘nth’, we generate the encoding below, with the following
+ peculiarities:
+ - the input type is ‘: ('t list_t # u32) + 't’ the same as the output type
+ (modulo the ‘:result’). ‘:'t list_t # u32’ is for the input value, while
+ ‘t’ is for the output.
+ *)
+
+val nth_body_def = Define ‘
+ nth_body
+ (* The continuation is used for the recursive calls - this is required
+ by the fixed-point operator *)
+ (f : (('t list_t # u32) + 't) -> (('t list_t # u32) + 't) result)
+
+ (* The input. Its type is: nth input type + nth output type *)
+ (x : (('t list_t # u32) + 't)) :
+
+ (* The output type is the same as the input type - this constraint
+ comes from limitations in the way we can define the fixed-point
+ operator inside the HOL logic *)
+ (('t list_t # u32) + 't) result =
+
+ (* Destruct the input. We need this to call the proper function in case
+ of mutually recursive definitions, but also to eliminate arguments
+ which correspond to the output value (the input type is the same
+ as the output type). *)
+ case x of
+ | INL x => ( (* Input case: normal function call *)
+ (* Body of nth *)
+ let (ls, i) = x in
+ case ls of
+ | ListCons x tl =>
+ if u32_to_int i = (0:int)
+ then Return (INR x)
+ else
+ do
+ i0 <- u32_sub i (int_to_u32 1);
+ (* Recursive call: we call the continuation.
+
+ We must wrap the input (here, in INL) then extract the
+ proper output (this last part is really important in
+ case of mutually recursive definitions).
+ *)
+ x <- (case f (INL (tl, i0)) of
+ | Fail e => Fail e
+ | Diverge => Diverge
+ | Return r =>
+ case r of
+ | INL _ => Fail Failure
+ | INR x => Return x);
+
+ (* Wrap the return value *)
+ Return (INR x)
+ od
+ | ListNil => Fail Failure)
+ | INR _ => (* Output case: invalid, we fail *)
+ Fail Failure
+’
+
+(* Then, we prove that the body we just defined satisfies the validity property
+ required by the fixed-point theorem.
+
+ Remark:
+ We first prove the theorem with ‘SUC (SUC n)’ where ‘n’ is a variable
+ to prevent this quantity from being rewritten to 2 during the proofs. *)
+Theorem nth_body_is_valid_aux:
+ is_valid_fp_body (SUC (SUC n)) nth_body
+Proof
+ pure_once_rewrite_tac [is_valid_fp_body_def] >>
+ gen_tac >>
+ (* Expand *)
+ fs [nth_body_def, bind_def] >>
+ (* Simply explore all paths *)
+ Cases_on ‘x’ >> fs [] >>
+ Cases_on ‘x'’ >> fs [] >>
+ Cases_on ‘q’ >> fs [] >>
+ Cases_on ‘u32_to_int r = 0’ >> fs [] >>
+ Cases_on ‘u32_sub r (int_to_u32 1)’ >> fs [] >>
+ fs [case_result_switch_eq] >>
+ (* Recursive call *)
+ disj2_tac >>
+ qexists ‘\g x. case case x of INL v => Fail Failure | INR x => Return x of
+ Return x => Return (INR x)
+ | Fail e => Fail e
+ | Diverge => Diverge’ >>
+ qexists ‘INL (l, a)’ >>
+ conj_tac
+ >-(pure_once_rewrite_tac [is_valid_fp_body_def] >> fs []) >>
+ gen_tac >>
+ (* Explore all paths *)
+ Cases_on ‘g (INL (l,a))’ >> fs [] >>
+ Cases_on ‘a'’ >> fs []
+QED
+
+Theorem nth_body_is_valid:
+ is_valid_fp_body (SUC (SUC 0)) nth_body
+Proof
+ irule nth_body_is_valid_aux
+QED
+
+(* We can now define ‘nth’ in terms of the fixed-point operator applied to ‘nth_body’.
+
+ Important points:
+ - we apply ‘fix’ to ‘fix_body’
+ - we wrap the input to take the proper branch (‘INL (ls, i)’)
+ - we extract the output to have a function with the proper output type
+
+ This definition satisfies the equation we want (see next theorem). *)
+val nth_raw_def = Define ‘
+ nth (ls : 't list_t) (i : u32) =
+ case fix nth_body (INL (ls, i)) of
+ | Fail e => Fail e
+ | Diverge => Diverge
+ | Return r =>
+ case r of
+ | INL _ => Fail Failure
+ | INR x => Return x
+’
+
+(* We finally prove the target unfolding equation for ‘nth’ *)
+Theorem nth_def:
+ ∀ls i. nth (ls : 't list_t) (i : u32) : 't result =
+ case ls of
+ | ListCons x tl =>
+ if u32_to_int i = (0:int)
+ then (Return x)
+ else
+ do
+ i0 <- u32_sub i (int_to_u32 1);
+ nth tl i0
+ od
+ | ListNil => Fail Failure
+Proof
+ rpt strip_tac >>
+ (* Expand the raw definition *)
+ pure_rewrite_tac [nth_raw_def] >>
+ (* Use the fixed-point equality - the rewrite must only be applied *on the left* of the equality, in the goal *)
+ pure_once_rewrite_left_tac [HO_MATCH_MP fix_fixed_eq nth_body_is_valid] >>
+ (* Expand the body definition *)
+ pure_rewrite_tac [nth_body_def] >>
+ (* Explore all the paths - maybe we can be smarter, but this is fast and really easy *)
+ fs [bind_def] >>
+ Cases_on ‘ls’ >> fs [] >>
+ Cases_on ‘u32_to_int i = 0’ >> fs [] >>
+ Cases_on ‘u32_sub i (int_to_u32 1)’ >> fs [] >>
+ Cases_on ‘fix nth_body (INL (l,a))’ >> fs [] >>
+ Cases_on ‘a'’ >> fs []
+QED
+
+(*=======================================================*
+ * Example 2: even, odd (mutually recursive definitions)
+ *=======================================================*)
+
+(* We consider the following group of mutually recursive definitions: *)
+
+val even_odd_qt = Defn.parse_quote ‘
+ (even (i : int) : bool result = if i = 0 then Return T else odd (i - 1)) /\
+ (odd (i : int) : bool result = if i = 0 then Return F else even (i - 1))
+’
+
+(* Similarly to ‘nth’, we need to introduce a body on which to apply the
+ fixed-point operator. Here, the body is slightly more complex because
+ we have mutually recursive functions.
+
+ In particular, the input/output types is a sum of 4 types:
+ input of even + output of even + input of odd + output of odd
+
+ That is: ‘: int + bool + int + bool’
+
+ As a consequence, the body is a case with 4 branches.
+ *)
+
+val even_odd_body_def = Define ‘
+ even_odd_body
+ (* The body takes a continuation - required by the fixed-point operator *)
+ (f : (int + bool + int + bool) -> (int + bool + int + bool) result)
+
+ (* The type of the input is:
+ input of even + output of even + input of odd + output of odd *)
+ (x : int + bool + int + bool) :
+
+ (* The output type is the same as the input type - this constraint
+ comes from limitations in the way we can define the fixed-point
+ operator inside the HOL logic *)
+ (int + bool + int + bool) result =
+
+ (* Case disjunction over the input, in order to figure out which
+ function from the group is actually called (even, or odd). *)
+ case x of
+ | INL i => (* Input of even *)
+ (* Body of even *)
+ if i = 0 then Return (INR (INL T))
+ else
+ (* Recursive calls are calls to the continuation f, wrapped
+ in the proper variant: here we call odd *)
+ (case f (INR (INR (INL (i - 1)))) of
+ | Fail e => Fail e
+ | Diverge => Diverge
+ | Return r =>
+ (* Extract the proper value from the enumeration: here, the
+ call is tail-call so this is not really necessary, but we
+ might need to retrieve the output of the call to odd, which
+ is a boolean, and do something more complex with it. *)
+ case r of
+ | INL _ => Fail Failure
+ | INR (INL _) => Fail Failure
+ | INR (INR (INL _)) => Fail Failure
+ | INR (INR (INR b)) => (* Extract the output of odd *)
+ (* Return: inject into the variant for the output of even *)
+ Return (INR (INL b))
+ )
+ | INR (INL _) => (* Output of even *)
+ (* We must ignore this one *)
+ Fail Failure
+ | INR (INR (INL i)) =>
+ (* Body of odd *)
+ if i = 0 then Return (INR (INR (INR F)))
+ else
+ (* Call to even *)
+ (case f (INL (i - 1)) of
+ | Fail e => Fail e
+ | Diverge => Diverge
+ | Return r =>
+ (* Extract the proper value from the enumeration *)
+ case r of
+ | INL _ => Fail Failure
+ | INR (INL b) => (* Extract the output of even *)
+ (* Return: inject into the variant for the output of odd *)
+ Return (INR (INR (INR b)))
+ | INR (INR (INL _)) => Fail Failure
+ | INR (INR (INR _)) => Fail Failure
+ )
+ | INR (INR (INR _)) => (* Output of odd *)
+ (* We must ignore this one *)
+ Fail Failure
+’
+
+(* We then prove that this body satisfies the validity condition *)
+Theorem even_odd_body_is_valid_aux:
+ is_valid_fp_body (SUC (SUC n)) even_odd_body
+Proof
+ pure_once_rewrite_tac [is_valid_fp_body_def] >>
+ gen_tac >>
+ (* Expand *)
+ fs [even_odd_body_def, bind_def] >>
+ (* Explore the body *)
+ Cases_on ‘x’ >> fs []
+ >-(
+ Cases_on ‘x' = 0’ >> fs [] >>
+ (* Recursive call *)
+ disj2_tac >>
+ qexists ‘\g x. case x of
+ | INL v => Fail Failure
+ | INR (INL v2) => Fail Failure
+ | INR (INR (INL v4)) => Fail Failure
+ | INR (INR (INR b)) => Return (INR (INL b))’ >>
+ qexists ‘INR (INR (INL (x' − 1)))’ >>
+ conj_tac
+ >-(pure_once_rewrite_tac [is_valid_fp_body_def] >> fs []) >>
+ fs []) >>
+ Cases_on ‘y’ >> fs [] >>
+ Cases_on ‘y'’ >> fs [] >>
+ Cases_on ‘x = 0’ >> fs [] >>
+ (* Recursive call *)
+ disj2_tac >>
+ qexists ‘\g x. case x of
+ INL v => Fail Failure
+ | INR (INL b) => Return (INR (INR (INR b)))
+ | INR (INR v3) => Fail Failure’ >>
+ qexists ‘INL (x − 1)’ >>
+ conj_tac
+ >-(pure_once_rewrite_tac [is_valid_fp_body_def] >> fs []) >>
+ fs []
+QED
+
+Theorem even_odd_body_is_valid:
+ is_valid_fp_body (SUC (SUC 0)) even_odd_body
+Proof
+ irule even_odd_body_is_valid_aux
+QED
+
+(* We finally introduce the definitions for even and odd *)
+val even_raw_def = Define ‘
+ even (i : int) =
+ case fix even_odd_body (INL i) of
+ | Fail e => Fail e
+ | Diverge => Diverge
+ | Return r =>
+ case r of
+ | INL _ => Fail Failure
+ | INR (INL b) => Return b
+ | INR (INR (INL b)) => Fail Failure
+ | INR (INR (INR _)) => Fail Failure
+’
+
+val odd_raw_def = Define ‘
+ odd (i : int) =
+ case fix even_odd_body (INR (INR (INL i))) of
+ | Fail e => Fail e
+ | Diverge => Diverge
+ | Return r =>
+ case r of
+ | INL _ => Fail Failure
+ | INR (INL _) => Fail Failure
+ | INR (INR (INL _)) => Fail Failure
+ | INR (INR (INR b)) => Return b
+’
+
+Theorem even_def:
+ ∀i. even (i : int) : bool result =
+ if i = 0 then Return T else odd (i - 1)
+Proof
+ gen_tac >>
+ (* Expand the definition *)
+ pure_once_rewrite_tac [even_raw_def] >>
+ (* Use the fixed-point equality *)
+ pure_once_rewrite_left_tac [HO_MATCH_MP fix_fixed_eq even_odd_body_is_valid] >>
+ (* Expand the body definition *)
+ pure_rewrite_tac [even_odd_body_def] >>
+ (* Expand all the definitions from the group *)
+ pure_rewrite_tac [even_raw_def, odd_raw_def] >>
+ (* Explore all the paths - maybe we can be smarter, but this is fast and really easy *)
+ fs [bind_def] >>
+ Cases_on ‘i = 0’ >> fs [] >>
+ Cases_on ‘fix even_odd_body (INR (INR (INL (i − 1))))’ >> fs [] >>
+ Cases_on ‘a’ >> fs [] >>
+ Cases_on ‘y’ >> fs [] >>
+ Cases_on ‘y'’ >> fs []
+QED
+
+Theorem odd_def:
+ ∀i. odd (i : int) : bool result =
+ if i = 0 then Return F else even (i - 1)
+Proof
+ gen_tac >>
+ (* Expand the definition *)
+ pure_once_rewrite_tac [odd_raw_def] >>
+ (* Use the fixed-point equality *)
+ pure_once_rewrite_left_tac [HO_MATCH_MP fix_fixed_eq even_odd_body_is_valid] >>
+ (* Expand the body definition *)
+ pure_rewrite_tac [even_odd_body_def] >>
+ (* Expand all the definitions from the group *)
+ pure_rewrite_tac [even_raw_def, odd_raw_def] >>
+ (* Explore all the paths - maybe we can be smarter, but this is fast and really easy *)
+ fs [bind_def] >>
+ Cases_on ‘i = 0’ >> fs [] >>
+ Cases_on ‘fix even_odd_body (INL (i − 1))’ >> fs [] >>
+ Cases_on ‘a’ >> fs [] >>
+ Cases_on ‘y’ >> fs []
+QED
+
+val _ = export_theory ()