%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 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.