comparison src/StackMachine.v @ 15:d8c81e19e7d3

Source language semantics
author Adam Chlipala <adamc@hcoop.net>
date Wed, 03 Sep 2008 15:00:56 -0400
parents 8aed4562b62b
children 4d8ca6845e67
comparison
equal deleted inserted replaced
14:8aed4562b62b 15:d8c81e19e7d3
155 (* end hide *) 155 (* end hide *)
156 156
157 (** Though a pencil-and-paper proof might clock out at this point, writing "by a routine induction on [e]," it turns out not to make sense to attack this proof directly. We need to use the standard trick of %\textit{%#<i>#strengthening the induction hypothesis#</i>#%}%. We do that by proving an auxiliary lemma: 157 (** Though a pencil-and-paper proof might clock out at this point, writing "by a routine induction on [e]," it turns out not to make sense to attack this proof directly. We need to use the standard trick of %\textit{%#<i>#strengthening the induction hypothesis#</i>#%}%. We do that by proving an auxiliary lemma:
158 *) 158 *)
159 159
160 Lemma compileCorrect' : forall e s p, progDenote (compile e ++ p) s = 160 Lemma compileCorrect' : forall e p s, progDenote (compile e ++ p) s = progDenote p (expDenote e :: s).
161 progDenote p (expDenote e :: s).
162 161
163 (** After the period in the [Lemma] command, we are in %\textit{%#<i>#the interactive proof-editing mode#</i>#%}%. We find ourselves staring at this ominous screen of text: 162 (** After the period in the [Lemma] command, we are in %\textit{%#<i>#the interactive proof-editing mode#</i>#%}%. We find ourselves staring at this ominous screen of text:
164 163
165 [[ 164 [[
166 1 subgoal 165 1 subgoal
167 166
168 ============================ 167 ============================
169 forall (e : exp) (s : stack) (p : list instr), 168 forall (e : exp) (p : list instr) (s : stack),
170 progDenote (compile e ++ p) s = progDenote p (expDenote e :: s) 169 progDenote (compile e ++ p) s = progDenote p (expDenote e :: s)
171 ]] 170 ]]
172 171
173 Coq seems to be restating the lemma for us. What we are seeing is a limited case of a more general protocol for describing where we are in a proof. We are told that we have a single subgoal. In general, during a proof, we can have many pending subgoals, each of which is a logical proposition to prove. Subgoals can be proved in any order, but it usually works best to prove them in the order that Coq chooses. 172 Coq seems to be restating the lemma for us. What we are seeing is a limited case of a more general protocol for describing where we are in a proof. We are told that we have a single subgoal. In general, during a proof, we can have many pending subgoals, each of which is a logical proposition to prove. Subgoals can be proved in any order, but it usually works best to prove them in the order that Coq chooses.
174 173
175 Next in the output, we see our single subgoal described in full detail. There is a double-dashed line, above which would be our free variables and hypotheses, if we had any. Below the line is the conclusion, which, in general, is to be proved from the hypotheses. 174 Next in the output, we see our single subgoal described in full detail. There is a double-dashed line, above which would be our free variables and hypotheses, if we had any. Below the line is the conclusion, which, in general, is to be proved from the hypotheses.
473 472
474 (** In this section, we will build on the initial example by adding additional expression forms that depend on static typing of terms for safety. *) 473 (** In this section, we will build on the initial example by adding additional expression forms that depend on static typing of terms for safety. *)
475 474
476 (** ** Source language *) 475 (** ** Source language *)
477 476
477 (** We define a trivial language of types to classify our expressions: *)
478
478 Inductive type : Set := Nat | Bool. 479 Inductive type : Set := Nat | Bool.
480
481 (** Now we define an expanded set of binary operators. *)
479 482
480 Inductive tbinop : type -> type -> type -> Set := 483 Inductive tbinop : type -> type -> type -> Set :=
481 | TPlus : tbinop Nat Nat Nat 484 | TPlus : tbinop Nat Nat Nat
482 | TTimes : tbinop Nat Nat Nat 485 | TTimes : tbinop Nat Nat Nat
483 | TEq : forall t, tbinop t t Bool 486 | TEq : forall t, tbinop t t Bool
484 | TLt : tbinop Nat Nat Bool. 487 | TLt : tbinop Nat Nat Bool.
485 488
489 (** The definition of [tbinop] is different from [binop] in an important way. Where we declared that [binop] has type [Set], here we declare that [tbinop] has type [type -> type -> type -> Set]. We define [tbinop] as an %\textit{%#<i>#indexed type family#</i>#%}%. Indexed inductive types are at the heart of Coq's expressive power; almost everything else of interest is defined in terms of them.
490
491 ML and Haskell have indexed algebraic datatypes. For instance, their list types are indexed by the type of data that the list carries. However, compared to Coq, ML and Haskell 98 place two important restrictions on datatype definitions.
492
493 First, the indices of the range of each data constructor must be type variables bound at the top level of the datatype definition. There is no way to do what we did here, where we, for instance, say that [TPlus] is a constructor building a [tbinop] whose indices are all fixed at [Nat]. %\textit{%#<i>#Generalized algebraic datatypes (GADTs)#</i>#%}% are a popular feature in GHC Haskell and other languages that removes this first restriction.
494
495 The second restriction is not lifted by GADTs. In ML and Haskell, indices of types must be types and may not be %\textit{%#<i>#expressions#</i>#%}%. In Coq, types may be indiced by arbitrary Gallina terms. Type indices can live in the same universe as programs, and we can compute with them just like regular programs. Haskell supports a hobbled form of computation in type indices based on multi-parameter type classes, and recent extensions like type functions bring Haskell programming even closer to "real" functional programming with types, but, without dependent typing, there must always be a gap between how one programs with types and how one programs normally.
496 *)
497
498 (** We can define a similar type family for typed expressions. *)
499
486 Inductive texp : type -> Set := 500 Inductive texp : type -> Set :=
487 | TNConst : nat -> texp Nat 501 | TNConst : nat -> texp Nat
488 | TBConst : bool -> texp Bool 502 | TBConst : bool -> texp Bool
489 | TBinop : forall arg1 arg2 res, tbinop arg1 arg2 res -> texp arg1 -> texp arg2 -> texp res. 503 | TBinop : forall arg1 arg2 res, tbinop arg1 arg2 res -> texp arg1 -> texp arg2 -> texp res.
490 504
505 (** Thanks to our use of dependent types, every well-typed [texp] represents a well-typed source expression, by construction. This turns out to be very convenient for many things we might want to do with expressions. For instance, it is easy to adapt our interpreter approach to defining semantics. We start by defining a function mapping the types of our languages into Coq types: *)
506
491 Definition typeDenote (t : type) : Set := 507 Definition typeDenote (t : type) : Set :=
492 match t with 508 match t with
493 | Nat => nat 509 | Nat => nat
494 | Bool => bool 510 | Bool => bool
495 end. 511 end.
512
513 (** It can take a few moments to come to terms with the fact that [Set], the type of types of programs, is itself a first-class type, and that we can write functions that return [Set]s. Past that wrinkle, the definition of [typeDenote] is trivial, relying on the [nat] and [bool] types from the Coq standard library.
514
515 We need to define a few auxiliary functions, implementing our boolean binary operators that do not appear with the right types in the standard library. They are entirely standard and ML-like, with the one caveat being that the Coq [nat] type uses a unary representation, where [O] is zero and [S n] is the successor of [n].
516 *)
496 517
497 Definition eq_bool (b1 b2 : bool) : bool := 518 Definition eq_bool (b1 b2 : bool) : bool :=
498 match b1, b2 with 519 match b1, b2 with
499 | true, true => true 520 | true, true => true
500 | false, false => true 521 | false, false => true
512 match n1, n2 with 533 match n1, n2 with
513 | O, S _ => true 534 | O, S _ => true
514 | S n1', S n2' => lt n1' n2' 535 | S n1', S n2' => lt n1' n2'
515 | _, _ => false 536 | _, _ => false
516 end. 537 end.
538
539 (** Now we can interpret binary operators: *)
517 540
518 Definition tbinopDenote arg1 arg2 res (b : tbinop arg1 arg2 res) 541 Definition tbinopDenote arg1 arg2 res (b : tbinop arg1 arg2 res)
519 : typeDenote arg1 -> typeDenote arg2 -> typeDenote res := 542 : typeDenote arg1 -> typeDenote arg2 -> typeDenote res :=
520 match b in (tbinop arg1 arg2 res) return (typeDenote arg1 -> typeDenote arg2 -> typeDenote res) with 543 match b in (tbinop arg1 arg2 res) return (typeDenote arg1 -> typeDenote arg2 -> typeDenote res) with
521 | TPlus => plus 544 | TPlus => plus
523 | TEq Nat => eq_nat 546 | TEq Nat => eq_nat
524 | TEq Bool => eq_bool 547 | TEq Bool => eq_bool
525 | TLt => lt 548 | TLt => lt
526 end. 549 end.
527 550
551 (** This function has just a few differences from the denotation functions we saw earlier. First, [tbinop] is an indexed type, so its indices become additional arguments to [tbinopDenote]. Second, we need to perform a genuine %\textit{%#<i>#dependent pattern match#</i>#%}% to come up with a definition of this function that type-checks. In each branch of the [match], we need to use branch-specific information about the indices to [tbinop]. General type inference that takes such information into account is undecidable, and Coq avoids pursuing special heuristics for the problem, instead asking users to write annotations, like we see above on the line with [match].
552
553 The [in] annotation restates the type of the term being case-analyzed. Though we use the same names for the indices as we use in the type of the original argument binder, these are actually fresh variables, and they are %\textit{%#<i>#binding occcurrences#</i>#%}%. Their scope is the [return] clause. That is, [arg1], [arg2], and [arg3] are new bound variables bound only within the return clause [typeDenote arg1 -> typeDenote arg2 -> typeDenote res]. By being explicit about the functional relationship between the type indices and the match result, we regain decidable type inference.
554
555 The same tricks suffice to define an expression denotation function in an unsurprising way:
556 *)
557
528 Fixpoint texpDenote t (e : texp t) {struct e} : typeDenote t := 558 Fixpoint texpDenote t (e : texp t) {struct e} : typeDenote t :=
529 match e in (texp t) return (typeDenote t) with 559 match e in (texp t) return (typeDenote t) with
530 | TNConst n => n 560 | TNConst n => n
531 | TBConst b => b 561 | TBConst b => b
532 | TBinop _ _ _ b e1 e2 => (tbinopDenote b) (texpDenote e1) (texpDenote e2) 562 | TBinop _ _ _ b e1 e2 => (tbinopDenote b) (texpDenote e1) (texpDenote e2)