%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % You can define new types just like you would in ML or Haskell % by first declaring the type name (in this case nat) and then % by declaring the constructors as appropriate. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% nat : type. % name nat N. z : nat. s : nat -> nat. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % You can also define relations (e.g., functions) between values % of a given type. Here, I am defining the less-than relation % for naturals. The mode declaration is useful for being % able to to specify that we expect to provide all of the % inputs to lt when we want to treat this like a logic programming % environment. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% lt : nat -> nat -> type. %mode lt +N1 +N2. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % The only way we can build an object with type "lt N1 N2" is % using one of the two constructors below. The first one % corresponds to the proof case (or inference rule) when N1 % is zero. The second one corresponds to the proof case when % we construct a proof that (s N1) < (s N2) out of a proof % that N1 < N2. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% lt_z : lt z (s N). lt_s : lt (s N1) (s N2) <- lt N1 N2. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % If you feed this to the twelf-server, it claims that lt_z % and lt_s have the following types: % % lt_z : {N:nat} lt z (s N). % lt_s : {N1:nat} {N2:nat} lt N1 N2 -> lt (s N1) (s N2). % % You should read the type of lt_z as "for all nats N, I'm % a proof that z < (s N). You should read the type of % lt_s as "for all nats N1 and N2, if you give me a proof % that N1 < N2, then lt_s will give you a proof that % s(N1) < s(N2). % % Twelf implicitly universally quantifies the N1 and N2, % just as ML or Haskell will implicitly universally quantify % the free type variables in a type. You can write out the % quantification explicitly as I've done above, but then you're % also responsible for explicitly instantiating the parameters. % % Technically, the type of lt_s is really something like this: % % lt_s : {N1:nat} {N2:nat} {D:lt N1 N2} (lt (s N1) (s N2)). % % The "D" isn't in what Twelf prints because there's no % dependency on it. That is, {X:T1} T2 is abbreviated as % T1 -> T2 when X does not occur free in T2. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % The next two definitions are used to (a) close off the world % -- in essence tell Twelf that there are no more ways to build % lt objects, and then a way to determine that lt terminates. % In particular, the argument is based on structural induction % of the first argument to lt. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %worlds() (lt _ _). %terminates N1 (lt N1 _). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % This next section attempts to prove the theorem: % for all nats N. N < s(N) % which seems like it should be pretty easy to establish. % % The way this is done is to first declare a type constructor % that encodes the theorem we're trying to establish. That's % what n_lt_sn does. The mode in this case specifies that % the natural number N is an input, and the output is a % derivation D that shows N < s(N). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% n_lt_sn : {N:nat} (lt N (s N)) -> type. %mode n_lt_sn +N -D. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % The next two constructors are the ways we can build a n_lt_sn % object. I'm using degenerate names for the constructors % because I don't expect to ever have to use them elsewhere. % The first one instantiates the "N" in n_lt_stn with z so we % also need to provide a derivation D with type "lt N s(N)" % or after substituting z for N, something of type "lt z s(Z)". % It turns out that lt_z has this type (after being suitably % instantiated.) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -z : n_lt_sn z (lt_z). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % You can read the definition below as backwards -- it says % that if you give me an "n_lt_sn N D" object, where N is % a natural, and D is a proof that N < s(N), then I'll give % you back a proof P that s(N) < s(s(N)). How will I do this? % P has to end with a use of lt_s. Twelf implicitly figures % out that we're instantiating lt_s to be an object of type % (N < s(N)) -> (s(N) < s(s(N))). But we have to provide % the proof that N < s(N) to lt_s in order to get out % an s(N) < s(s(N)) object. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -s : n_lt_sn (s N) (lt_s D) <- n_lt_sn N D. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % These next bits are used to convince Twelf that indeed, % n_lt_sn is a total function: That is, give n_lt_sn any % natural number N and it will kick out a proof that % N < s(N). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %worlds() (n_lt_sn _ _). %terminates N (n_lt_sn N _). %covers n_lt_sn +N -D. %total N (n_lt_sn N _). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Here's another proof -- this time of the transitivity of % less-than. I find it useful to see the full LF types after % feeding things into Twelf. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% lt_trans : lt N1 N2 -> lt N2 N3 -> lt N1 N3 -> type. %mode lt_trans +D1 +D2 -D3. -z : lt_trans lt_z _ lt_z. -s : lt_trans (lt_s D12) (lt_s D23) (lt_s D13) <- lt_trans D12 D23 D13. %worlds () (lt_trans _ _ _). %total D12 (lt_trans D12 _ _). %covers lt_trans +D1 +D2 -D3. %total D1 (lt_trans D1 _ _). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Suppose we want to prove that it is never the case that % lt N N. The trick is to use an encoding of reductio % ad absurbum. In particular, we define an empty relation % (one with no constructors). Then we define our "false" % proposition as something that yields absurd. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% absurd : type. %name absurd _|_. %freeze absurd. lt_self : lt N N -> absurd -> type. %mode lt_self +D -A. -s : lt_self (lt_s D) A <- lt_self D A. %worlds () (lt_self _ _). %total D (lt_self D _). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Now suppose we want to define inequality for nats and argue % that it is never the case that neq N N. We end up carrying % the absurd through... %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% neq : nat -> nat -> type. %mode neq +N1 +N2. neq_l : neq N M <- lt N M. neq_r : neq N M <- lt M N. neq_self : neq N N -> absurd -> type. %mode neq_self +D -A. -l : neq_self (neq_l D) A <- lt_self D A. -r : neq_self (neq_r D) A <- lt_self D A. %worlds () (neq_self _ _). %total D (neq_self D _). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % addition %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% add : nat -> nat -> nat -> type. %mode (add +N1 +N2 -N3). add_z : add z X X. add_s : add (s Y) X (s Z) <- add Y X Z. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Syntax for IMP %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% exp : type. %name exp E. % expressions com : type. %name com C. % commands var_e : nat -> exp. nat_e : nat -> exp. plus_e : exp -> exp -> exp. skip_c : com. assign_c : nat -> exp -> com. seq_c : com -> com -> com. if_c : exp -> com -> com -> com. while_c : exp -> com -> com. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Stores -- we represent stores as lists of pairs of naturals % % Why don't we represent stores as nat->nat (i.e., as Twelf % functions from nat to nat)? That would make certainly seem % to be easier. However, Twelf's function space does not % include a form of conditional. The reason for this is that % it's not clear what the normal form for conditionals should % be. Consider, for instance: % F1 = \x.if (x == 0) then 1 else if (x == 1) then 0 else 2 % F2 = \x.if (x == 1) then 0 else if (x == 0) then 1 else 2 % Both functions are extensionally equivalent (give the same % results when we apply them to the same arguments) but clearly % they are not syntactically equal. To support something like % a conditional, Twelf would have to find some unique normal % form for these conditionals which is extremely hard to do % in the general case (I'm not even sure it can be done.) % % So why are normal forms so damned important for Twelf? The % reason is that Twelf's functions are really intended to % build derivations (i.e., serve as inference rules.) And % the primary thing that we want to do with a derivation is % use inversion to argue. That is, we want to make sure that % we understand what possible rules could've been used to make % up the last step in a proof so that we can argue based on % the antecedents that some property holds. To support this % kind of reasoning, it's extremely important that our % derivations have normal forms. % % So the up-shot is that Twelf deliberately picks a very, very % weak notion of "function" --- essentially something that's % purely parametric (just like our inference rules.) That means % that its functions are really only good for representing % a small class of object-level items in a shallow fashion. % % On the other hand, we can always encode some *syntax* that % represents our own function space, and define our own % notion of function evaluation (and function equality). % So that's effectively what we're doing here. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% store : type. %name store S. zero_s : store. % the store that maps all values to 0 ifeq_s : nat -> nat -> store -> store. % ifeq_s N1 N2 S maps N1 to N2 and % maps all other N to S(N) read : store -> nat -> nat -> type. % reading the store %mode (read +S +N1 -N2). read_zero_s : read zero_s N z. read_ifeq_t : read (ifeq_s N1 N2 S) N1 N2. read_ifeq_f : read (ifeq_s N1 _ S) N2 N3 <- neq N1 N2 <- read S N2 N3. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Of course, we're going to eventually have to set up and % prove that two stores are equivalent and other properties % about functions that we take for granted... %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Small-Step Evaluation Relation for Expressions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% step_exp : store -> exp -> store -> exp -> type. %mode (step_exp +S1 +E1 -S2 -E2). step_var : step_exp S (var_e N) S (nat_e M) <- read S N M. step_plus : step_exp S (plus_e (nat_e N1) (nat_e N2)) S (nat_e N) <- add N1 N2 N. step_plus_l : step_exp S (plus_e E1 E2) S'(plus_e E1' E2) <- step_exp S E1 S' E1'. step_plus_r : step_exp S (plus_e (nat_e N) E) S' (plus_e (nat_e N) E') <- step_exp S E S' E'. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Small-Step Evaluation Relation for Commands %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% step_com : store -> com -> store -> com -> type. %mode (step_com +S1 +C1 -S2 -C2). step_assign1 : step_com S (assign_c N1 (nat_e N2)) (ifeq_s N1 N2 S) skip_c. step_assign2 : step_com S (assign_c N E) S' (assign_c N E') <- step_exp S E S' E'. step_seq1 : step_com S (seq_c skip_c C) S C. step_seq2 : step_com S (seq_c C1 C2) S' (seq_c C1' C2) <- step_com S C1 S' C1'. step_if1 : step_com S (if_c (nat_e z) C1 C2) S C1. step_if2 : step_com S (if_c (nat_e (s _)) C1 C2) S C2. step_if3 : step_com S (if_c E C1 C2) S' (if_c E' C1 C2) <- step_exp S E S' E'. step_while : step_com S (while_c E C) S (if_c E (seq_c C (while_c E C)) skip_c). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % That was the easy part (defining the model). The hard part % is now proving something about the model. For example, we % might like to prove that for all S and E, if % step_exp S E S' E' then S = S'. As another example, we might % like to prove that evaluation is deterministic. That is, % if we can show step_com S C S1 C1 and step_com S C S2 C2 % then C1 = C2 and S1 = S2. % % Homework: % % 1) define a big-step semantics for IMP. % % 2) try to prove that if an evaluates to a natural n % using the big-step semantics, then ->* using % the small-step semantics. (Do this on paper first!) % % 3) modify the small-step semantics to add support for % a) parallel commands C1 || C2 % b) simple input/output % % Extra: try to establish that the step_com for the original % sequential version of the language is in fact a partial % function.