(** In this chapter, we consider the problem of executing $F_{<:}$ terms
as prescribed by the reduction semantics for this language. Such
executions are useful for testing that the semantics has the intended
behavior. This goal is listed as part 3 in the
POPLmark challenge. As we will see, our development will go one
step further and result in the production of an efficient and
provably correct interpreter for $F_{<:}$.
There are two approaches to executing dynamic semantics within Coq.
The first operates directly on a relational specification of the semantics,
either big-step or small-step like our [red] predicate from chapter 3.
The [eauto] Coq tactic, which build proofs by Prolog-style
resolution over a set of predeclared inference rules and lemmas, can
be abused to search and build derivation trees for a goal of the
form [exists b, red a b], therefore executing one reduction step from [a].
An example of this approach can be found in our work with A. Appel
on the list-machine benchmark %\cite{Appel-Leroy-listmachine-tr}%.
However, this approach is tricky to set up and very inefficient.
The other approach, which we follow in this chapter, is to specify
the operational semantics as functions rather than predicates.
While Coq has no efficient built-in execution mechanism for logic
programs (composed of inductively-defined predicates), it can natively
evaluate functional programs (composed of functions defined by recursion
and pattern-matching). Such functional reductions are actually
part of the logic of Coq, via the notion of conversion.
We therefore proceed in two steps. We will first define functions
that compute the one-step or $N$-step reduct of a $F_{<:}$ term,
and prove that they are correct and complete with respect to the
relational semantics. We will then use these functions to evaluate
terms within Coq and to extract efficient Caml code for an interpreter.
*)
Require Import Arith.
Require Import ZArith.
Require Import List.
Require Import extralibrary.
Require Import part1a.
Require Import part2a.
(** * Execution of one-step reductions *)
(** We first show that the [isvalue] predicate is decidable.
The lemma below will actually provides us with a decision procedure
that takes any term [a] and returns whether it is a value or not.
We can then use this decision procedure within function definitions. *)
Lemma isvalue_dec:
forall a, {isvalue a} + {~isvalue a}.
Proof.
destruct a; (left; constructor) || (right; red; intro H; inversion H).
Defined.
(** The [reduce] function maps a term [a] to either [Some b] if
[a] reduces in one step to [b], or to [None] if [a] does not reduce.
It is defined by structural recursion over [a] and case analysis
on whether subterms of [a] are values, or reduce, or are stuck. *)
Fixpoint reduce (a: term) : option term :=
match a with
| App b c =>
if isvalue_dec b then
if isvalue_dec c then
match b with Fun t d => Some (vsubst_term d 0 c) | _ => None end
else
match reduce c with Some c' => Some(App b c') | None => None end
else
match reduce b with Some b' => Some(App b' c) | None => None end
| TApp b t =>
if isvalue_dec b then
match b with TFun t' c => Some (vsubst_tety c 0 t) | _ => None end
else
match reduce b with Some b' => Some(TApp b' t) | None => None end
| _ => None
end.
(** We then show that this function is correct and complete with respect
to the reduction rules: [reduce a = Some b] if and only if [red a b]
holds. The proofs are routine inductions on the structure of [a]
for the ``only if'' part and on the derivation of [red a b] for the
``if'' part. *)
Lemma reduce_is_correct:
forall a a', reduce a = Some a' -> red a a'.
Proof.
induction a; simpl; intros; try discriminate.
destruct (isvalue_dec a1). destruct (isvalue_dec a2).
destruct a1; inversion H. apply red_appabs; auto.
destruct (reduce a2); inversion H. apply red_apparg; auto.
destruct (reduce a1); inversion H. apply red_appfun; auto.
destruct (isvalue_dec a).
destruct a; inversion H. apply red_tapptabs; auto.
destruct (reduce a); inversion H. apply red_tapp; auto.
Qed.
Lemma isvalue_dec_true:
forall a (T: Set) (b c: T), isvalue a -> (if isvalue_dec a then b else c) = b.
Proof.
intros. destruct (isvalue_dec a). auto. contradiction.
Qed.
Lemma isvalue_dec_false:
forall a a' (T: Set) (b c: T), red a a' -> (if isvalue_dec a then b else c) = c.
Proof.
intros. destruct (isvalue_dec a).
inversion i; subst a; inversion H.
auto.
Qed.
Lemma reduce_is_complete:
forall a a', red a a' -> reduce a = Some a'.
Proof.
induction 1; simpl.
apply isvalue_dec_true; auto.
auto.
rewrite (isvalue_dec_false a a'); auto. rewrite IHred; auto.
rewrite isvalue_dec_true; auto.
rewrite (isvalue_dec_false b b'); auto. rewrite IHred; auto.
rewrite (isvalue_dec_false a a'); auto. rewrite IHred; auto.
Qed.
(** * Execution of [N]-step reductions *)
(** The following function iterates the one-step reduction function
[compute] to obtain the normal form of a term. Since Coq functions
must always terminate, we need to bound the number of iterations
by the [n] parameter. If a normal form cannot be reached in [n]
steps, [compute] returns [None]. *)
Fixpoint compute (n: nat) (a: term) {struct n}: option term :=
match n with
| O => None
| S n' =>
match reduce a with
| Some a' => compute n' a'
| None => Some a
end
end.
(** We now show that [compute a], if it succeeds, returns a
reduct of [a] that is in normal form (irreducible). *)
Definition irreducible (a: term): Prop := forall b, ~red a b.
Inductive red_sequence: term -> term -> Prop :=
| red_sequence_0:
forall a, irreducible a -> red_sequence a a
| red_sequence_1: forall a b c,
red a b -> red_sequence b c -> red_sequence a c.
Lemma compute_correct:
forall n a a', compute n a = Some a' -> red_sequence a a'.
Proof.
induction n; intros until a'; simpl.
congruence.
caseEq (reduce a); intros.
apply red_sequence_1 with t. apply reduce_is_correct; auto. auto.
inversion H0. apply red_sequence_0.
intro; red; intro. generalize (reduce_is_complete _ _ H1). congruence.
Qed.
(** Conversely, if a term [a] has a normal form [a'], there exists
a number of iterations [n] such that [compute] returns [Some a']. *)
Lemma compute_complete:
forall a a', red_sequence a a' -> exists n, compute n a = Some a'.
Proof.
induction 1.
exists 1; simpl. caseEq (reduce a); intros.
generalize (reduce_is_correct _ _ H0). firstorder. auto.
destruct IHred_sequence as [n C]. exists (S n). simpl.
rewrite (reduce_is_complete _ _ H). auto.
Qed.
(** * Experiments *)
(** We can now use the Coq directives [Eval compute in (reduce a)]
and [Eval compute in (compute N a)] to display the results of
performing one or [N] reduction steps in [a]. *)
Definition F_poly_identity := TFun Top (Fun (Tvar 0) (Var 0)).
Definition F_top_identity := TApp F_poly_identity Top.
Definition F_delta := Fun (Arrow Top Top) (App (Var 0) (Var 0)).
Definition F_testprog := App F_delta F_top_identity.
Eval compute in (reduce F_testprog).
Eval compute in (compute 100 F_testprog).
(** The latter returns [Some (Fun Top (Var 0))], which is indeed
the value of the term [F_testprog]. For a larger example, here is
some arithmetic on Church integers. *)
Definition F_one : term :=
(TFun Top (TFun (Tvar 0) (TFun (Tvar 1)
(Fun (Arrow (Tvar 2) (Tvar 1))
(Fun (Tvar 0)
(App (Var 1) (Var 0))))))).
Definition F_nat : type :=
(Forall Top
(Forall (Tvar 0)
(Forall (Tvar 1)
(Arrow (Arrow (Tvar 2) (Tvar 1)) (Arrow (Tvar 0) (Tvar 1)))))).
Definition F_add : term :=
(Fun F_nat
(Fun F_nat
(TFun Top (TFun (Tvar 0) (TFun (Tvar 1)
(Fun (Arrow (Tvar 2) (Tvar 1))
(Fun (Tvar 0)
(App (TApp (TApp (TApp (Var 3) (Tvar 2)) (Tvar 1)) (Tvar 1))
(App (Var 1)
(App (TApp (TApp (TApp (Var 2) (Tvar 2)) (Tvar 1))
(Tvar 0))
(App (Var 1) (Var 0)))))))))))).
Eval compute in (compute 100 (App (App F_add F_one) F_one)).
(** Execution is nearly instantaneous. In Coq 8.1, we can also
use [Eval vm_compute] to request evaluation via compilation to
virtual machine code. This results in execution speed comparable
to that of bytecoded OCaml.
An alternate execution path is to generate (or ``extract''
in Coq's terminology) Caml code from the Coq definition of function
[compute]. This is achieved by the following command:
*)
Extraction "/tmp/fsub_eval.ml" compute.
(** The generated Caml code can be compiled with the OCaml native-code
compiler for even higher execution speed. More importantly, it can be
linked with a lexer, parser and printer hand-written in OCaml,
obtaining a stand-alone reference interpreter for $F_{<:}$ that can
execute non-trivial programs. *)