small-step-typechecking000700 001370 000000 00000000000 10547407225 015604 5ustar00olegwheel000000 000000 small-step-typechecking/lxi0-calc.elf000600 001370 000000 00000067400 10612055475 020137 0ustar00olegwheel000000 000000 % Small-step evaluation and type-checking:
% Simply typed lambda-calculus with shift0
%
% The code accompanying the paper
% `A Substructural Type System for Delimited Continuations'
% Joint work with Chung-chieh Shan.
%
%
% Both dynamic _and_ static semantics (typing rules) are small step.
% We use equivalence rules for focusing.
% Static evaluation rules are different from dynamic ones: for one,
% in static semantics, lambda is not a `value' and can be further evaluated
% (i.e., type-checked).
%
% To make the type-checking deterministic, we require all bound variables
% (that is, variables bound by lambda and xshift0) to be annotated with
% types. This is the usual requirement in the typed lambda-calculus.
% Incidentally, the annotation requirement is not an onerous one:
% the term to eventually type-check, the input to %query, may indeed
% contain uninstantiated logic variables, and Twelf will guess them.
% It is the type-checking rules that are fully deterministic functions
% from their input to their output arguments. The fresh logic variables
% during the type-checking are used only for parameter passing and never
% for guessing.
% Types
pure: type. %name pure U.
tp: type. %name tp T.
cotp: type. %name cotp S.
int: pure. % one single primitive type for now.
=>: pure -> tp -> pure. %infix right 200 =>.
pr: pure -> tp. %prefix 150 pr.
=v: cotp -> tp -> tp. %infix right 200 =v.
=^: pure -> tp -> cotp. %infix right 200 =^.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Expressions
value: type. %name value V v.
term: type. %name term E x.
% primitive values: natural literals and the increment function
nat: type. %name nat N.
z: nat.
s: nat -> nat.
num : nat -> value. % two constants
inc : value.
% first argument is the type of the bound variable
lam: pure -> (value -> term) -> value.
% The following `abstract value' isn't present in the
% regular terms (at run-time). It is only used for
% abstract interpretation.
% The abstract value is the `value' in the sense it is
% not inspectable and not traversable.
avalue: pure -> value.
% Terms
% all values are terms
vl: value -> term. %prefix 400 vl.
% application
@: term -> term -> term. %infix left 300 @.
% inverse context constructors... aka context zippers. For each
% term constructor, we have its zippified versions.
% A binary constructor has two zippified equivalents.
% Plus we have the `top' zipper.
% In the dynamic universe, we treat values as atomic, and so we don't
% have the co-term constructors for value constructors.
coterm: type. %name coterm C k.
% An abstract co-term of the specified co-type.
% used in the abstract interpretation.
acoterm: cotp -> coterm.
% control operators deal with co-terms as first-class
$: coterm -> term -> term. %infix right 200 $.
% we define a special class of terms: manifestly effectful terms. xshift0
% is one of those
xterm : type.
% all xterms are terms
xt: xterm -> term. %prefix 400 xt.
% first argument is the cotype of the bound covariable
xshift0: cotp -> (coterm -> term) -> xterm.
% abstract xterm. It always has the S =v T type.
axt: cotp -> tp -> xterm.
#: coterm. % The top.
;: coterm -> value -> coterm. %infix right 250 ;.
,: term -> coterm -> coterm. %infix right 250 ,.
% convenient abbreviations
reset = [t:term] # $ t.
shift0 = [ct] [body] xt (xshift0 ct body).
% Now $ is a term constructor: we need the corresponding zippers
% It seems better just to define meta-continuation as a stack of
% coterms (double-zipper).
cocoterm: type. %name cocoterm G.
$0: cocoterm. % The bottom of the stack
$;: cocoterm -> coterm -> cocoterm. %infix left 250 $;.
% some of the terms are statements. there should be at least one plug!
stmt: type.
st: cocoterm -> coterm -> term -> stmt.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Term and context equivalences. Needed for the focusing
% This is all purely structural.
% All terms are classified to values, manifestly effectful terms (xterm)
% and applications. The first two are not focusable into (treated as
% atomic by the focusing operation).
%{
equiv: term -> term -> type. %name equiv Q.
equiv,1: equiv (C $ F @ E) (E , C $ F).
equiv,2: equiv (E , C $ F) (C $ F @ E).
equiv;1: equiv (C $ vl V @ E) (cl C ; V $ E).
equiv;2: equiv (cl C ; V $ E) (C $ vl V @ E).
equiv#1: equiv (cl # $ vl V) (vl V).
equiv#2: equiv (vl V) (cl # $ vl V).
}%
% focusing: convert a statement into a focused statement.
% The following deterministic predicate decides what it means for
% a statement to be focused:
stmt-focused: cocoterm -> coterm -> term -> type.
%mode stmt-focused +G +C +E.
sf-val: stmt-focused G # (vl V).
sf-app: stmt-focused G (C ; VF) (vl V).
sf-shi: stmt-focused G C (xt X).
sf-plu: stmt-focused G (acoterm CT) (vl V). % abstract plug, used only
% at abstract interpretation.
%worlds () (stmt-focused _ _ _).
focus: stmt -> stmt-focused G C E -> type. %name focus F.
%mode focus +M1 -M2.
-: focus (st G # (vl V)) (sf-val: stmt-focused G # (vl V)).
-: focus (st G C (xt X)) (sf-shi: stmt-focused G C (xt X)).
-: focus (st G (C ; VF) vl V) (sf-app: stmt-focused G (C ; VF) vl V).
-: focus (st G (acoterm CT) vl V) (sf-plu: stmt-focused G (acoterm CT) vl V).
% simplified way, suggested by Ken
% Rotation: apply equiv,2 followed by equiv;1
-: focus (st G (E , C) vl V) R <- focus (st G (C ; V) E) R.
-: focus (st G C (F @ E)) R <- focus (st G (E , C) F) R.
-: focus (st G C1 (C2 $ E)) R <- focus (st (G $; C1) C2 E) R.
%worlds () (focus _ _).
% Alas, Twelf can't see the termination. It requires complex reasoning
% on the number of commas. The number of commas plus the number of term
% constructors decrease.
%% terminates (M) (focus M _).
%covers focus +M1 -M2.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Dynamic semantics
% It disregards the type annotations on binders in lam and shift.
% Dynamic semantics is untyped. The eval function does not deal with
% abstract avalues, acoterms, etc. -- well, the eval function is partial,
% isn't it?
eval: stmt -> stmt -> type.
%mode eval +M1 -M2.
eval1: stmt-focused G C E -> stmt -> type.
%mode eval1 +M1 -M2.
-: eval1 (sf-app: stmt-focused G (C ; inc) (vl (num N)))
(st G C vl (num (s N))).
-: eval1 (sf-app: stmt-focused G (C ; lam _ F) (vl V))
(st G C (F V)).
-: eval1 (sf-shi: stmt-focused (G $; C1) C (shift0 _ E))
(st G C1 (E C)).
-: eval1 (sf-val: stmt-focused (G $; C) # (vl V))
(st G C vl V).
-: eval P R
<- focus P PF
<- eval1 PF R.
% Big step: transitive closure of eval*: just to be able to run examples
eval* : stmt -> stmt -> type.
%mode eval* +M1 -M2.
eval1* : eval* (st $0 # vl V) (st $0 # vl V).
eval2* : eval* M M'' <- eval M M' <- eval* M' M''.
e1 = vl (lam TF [f] (vl lam TX [x] (vl f @ vl x))).
e2 = (e1 @ vl inc @ vl (num z)).
%query 1 2 eval* (st $0 # e2) (st $0 # (vl num (s z))).
e3 = reset (vl inc @ (shift0 S [_] (vl num z))).
%query 1 2 eval* (st $0 # e3) (st $0 # (vl num z)).
% e4 = reset ((shift0 S [k] (k $ (vl (num z))))).
e4 = reset (vl inc @ (shift0 S [k] (k $ (vl num z)))).
%query 1 2 eval* (st $0 # e4) (st $0 # (vl num (s z))).
e5 = reset ((shift0 S [k] (k $ vl inc)) @ (vl (num z))).
%query 1 2 eval* (st $0 # e5) (st $0 # (vl num (s z))).
e6 = reset (vl inc @ (shift0 S [k] (k $ (k $ (vl (num z)))))).
%query 1 2 eval* (st $0 # e6) (st $0 # (vl num (s (s z)))).
% the analogue of Danvy/Filinski's test
+2 = lam T [x] (vl inc @ (vl inc @ vl x)).
e7 = vl inc @ reset (vl +2 @ (shift0 S [k] (vl inc @ (k $ (k $ (vl num z)))))).
%query 1 2 eval* (st $0 # e7) % should be 6
(st $0 # (vl num (s (s (s (s (s (s z))))))) ).
% now test that we really have shift0
e81 = reset (vl inc @ (shift0 S [_] (shift0 S [_] vl num z))).
%query 0 2 eval* (st $0 # e81) R. % should be no solutions: get stuck
e82 = reset (vl inc @ (reset (shift0 S [_] (shift0 S [_] vl num z)))).
%query 1 2 eval* (st $0 # e82) (st $0 # (vl num z)).
e83 = reset (vl inc @ (reset (reset (shift0 S [_] (shift0 S [_] vl num z))))).
%query 1 2 eval* (st $0 # e83) (st $0 # (vl num (s z))).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Static semantics: type-checking evaluation
%
% Because now we are allowed to traverse under lambda, we need
% to define new (co)context constructors (new zippers).
L$: cocoterm -> coterm -> pure -> cocoterm.
S$: cocoterm -> coterm -> cotp -> cocoterm.
%{
% and we need the refined focusing relation. It seems we'd rather use
% teval...
focus-more: stmt-focused G1 C1 E1 -> stmt-focused G2 E2 C2 -> type.
%mode focus-more +F1 -F2.
-: focus-more (sf-val: stmt-focused G # (vl inc))
(sf-val: stmt-focused G # (vl inc)).
-: focus-more (sf-val: stmt-focused G # (vl (avalue T)))
(sf-val: stmt-focused G # (vl (avalue T))).
-: focus-more (sf-val: stmt-focused G # (vl (lam U E))) R2
<- focus (st (L$ G # U) # (E (avalue U))) R1
<- focus-more R1 R2.
-: focus-more (sf-plu: stmt-focused G (acoterm CT) (vl avalue T))
(sf-plu: stmt-focused G (acoterm CT) (vl avalue T)).
-: focus-more (sf-app: stmt-focused G (C ; VF) (vl inc))
(sf-app: stmt-focused G (C ; VF) (vl inc)).
-: focus-more (sf-shi: stmt-focused G C (xt (xshift0 S E))) R2
<- focus (st (S$ G C S) # (E (acoterm S))) R1
<- focus-more R1 R2.
-: focus-more (sf-shi: stmt-focused G C (xt axt S T))
(sf-shi: stmt-focused G C (xt axt S T)).
%worlds () (focus-more _ _).
%% %covers focus-more +F1 -F2.
% useful for debugging:
deep-focus: stmt -> cocoterm -> coterm -> term -> type.
%mode deep-focus +M -G -C -E.
-: deep-focus M G C E <- focus M R
<- focus-more R (_:stmt-focused G C E).
}%
% The deterministic predicate that decides subtyping.
% subtype T1 T2 asserts that T1 <= T2.
% Note that we don't explicitly assert T <= T for arbitrary T, since this
% is derivable from the rules below. There is no need to add more
% `choice points'.
subtype : tp -> tp -> type.
%mode subtype +T1 +T2.
% the following two declarations are for the sake of running %queries.
%deterministic subtype.
%tabled subtype.
% Primitive terms are the subtypes of themselves
-: subtype (pr int) (pr int).
% A pure type can always be converted to an impure one:
% a term that has no effect can be over-approximated to have (any)
% effect at all. In other words, U can be generalized to (U =^ T) =v T
% which is essentially the consequence that 'x' can be generalized to
% shift k. k x
% The following rule is a bit more general
-: subtype (pr U1) ((U2 =^ T1) =v T2)
<- subtype (pr U1) (pr U2)
<- subtype T1 T2.
% standard co-variance/contra-variance.
-: subtype (pr U1 => T1) (pr U2 => T2)
<- subtype T1 T2
<- subtype (pr U2) (pr U1).
% subtyping of impure terms. The over-arching idea: if a function
% or a continuation are prepared to deal with the type T2, one can always
% pass a `smaller' type T1.
% The reason for T2 <= T1 below: (U2 =^ T2) is what the body
% of the shift assumed of its context. The shift that assumes the worst
% of the context (the context is exceptional: has the bigger type)
% is always substitutable for the more optimistic shift.
-: subtype ((U1 =^ T1) =v TR1) ((U2 =^ T2) =v TR2)
<- subtype TR1 TR2
<- subtype (pr U1) (pr U2)
<- subtype T2 T1.
%worlds () (subtype _ _).
%covers subtype +T1 -T2.
% ----------
% One-step abstract interpretation
teval: stmt -> stmt -> type.
%mode teval +M1 -M2.
teval1: stmt-focused G C E -> stmt -> type.
%mode teval1 +M1 -M2.
-: teval M R
<- focus M MF
<- teval1 MF R.
% Big step: transitive closure of teval*
teval* : stmt -> stmt -> type.
%mode teval* +M1 -M2.
teval1* : teval* (st $0 # vl avalue U) (st $0 # vl avalue U).
teval2* : teval* E E'' <- teval E E' <- teval* E' E''.
of : stmt -> pure -> type.
%mode of +M -U.
-: of P U <- teval* P (st $0 # vl avalue U).
% Verify the type of a co-term. We treat co-term as a lambda, which
% is what it is (in CPS).
% For modality reasons, we represent cotp (U =^ T) as its two separate
% components, U and T.
coof: coterm -> pure -> tp -> type.
%mode coof +C +U -T.
-: coof C U T <- of (st (L$ $0 # U) C vl avalue U) (U => T).
% Lambda-rules. We evaluate under lambda. We use the cocoterm constructor
% L$ to show we enter the hypothetical reasoning.
% This is also the application of the lambda rule, read upwards.
-: teval1 (_: stmt-focused G C (vl (lam U E)))
(st (L$ G C U) # (E (avalue U))).
% This is like the let rule
-: teval1 (sf-app:stmt-focused G (C ; lam U2 E) vl avalue U1)
(st G C (E (avalue U2)))
<- subtype (pr U1) (pr U2).
% The shift rule is quite akin to the lambda-rule, given that shift
% is dual to lambda
-: teval1 (sf-shi: stmt-focused G C (xt (xshift0 S E)))
(st (S$ G C S) # (E (acoterm S))).
% The main application rule
-: teval1 (sf-app:stmt-focused G (C ; (avalue (U2 => pr UR))) vl avalue U1)
(st G C (vl avalue UR))
<- subtype (pr U1) (pr U2).
% Applying a function whose body is effectful
-: teval1 (sf-app:stmt-focused G (C ; (avalue (U2 => (S =v T2)))) vl avalue U1)
(st G C (xt axt S T2))
<- subtype (pr U1) (pr U2).
% delta-rules...
-: teval1 (sf-app:stmt-focused G (C ; inc) vl avalue int)
(st G C vl (avalue int)).
% the lambda rule, top-to-bottom.
-: teval1 (sf-val:stmt-focused (L$ G C U1) # vl avalue U2)
(st G C vl (avalue (U1 => pr U2))).
% Type-checking partial co-term. From my message:
% The upshot: if E : (U =^ T2) =v T, then
% if C[U] : U3, then C[E] : (U3 =^ T2) =v T
% if C[U] : S4 =v T2, then C[E] : S4 =v T
teval1-case-cu: tp -> tp -> cotp -> type.
%mode teval1-case-cu +TCU +T2 -S.
-: teval1-case-cu (pr U3) T2 (U3 =^ T2).
-: teval1-case-cu (S4 =v T2P) T2 S4
<- subtype T2P T2. % Produced by context T2P must be no bigger than T2
-: teval1 (sf-shi:stmt-focused (L$ G C1 U0) C (xt (axt (U =^ T2) T)))
(st G C1 vl (avalue (U0 => (S =v T))))
<- coof C U TCU
<- teval1-case-cu TCU T2 S.
% the shift-rule, top-to-bottom.
-: teval1 (sf-val:stmt-focused (S$ G C S) # vl avalue U2)
(st G C xt (axt S (pr U2))).
-: teval1 (sf-shi:stmt-focused (S$ G C1 S0) C (xt (axt (U =^ T2) T)))
(st G C1 xt (axt S0 (S =v T)))
<- coof C U TCU
<- teval1-case-cu TCU T2 S.
% =v elimination rule. Now we have the side conditions
-: teval1 (sf-shi:stmt-focused (G $; C1) C (xt (axt (US =^ TS) pr U)))
(st G C1 vl (avalue U))
<- coof C US TSP
<- subtype TSP TS. % Produced TSP is smaller than required TS
-: teval1 (sf-shi:stmt-focused (G $; C1) C (xt (axt (US =^ TS) (S1 =v T1))))
(st G C1 xt axt S1 T1)
<- coof C US TSP
<- subtype TSP TS. % Produced TSP is smaller than required TS
% The continuation application rule, or =^-I rule, bottom-up
-: teval1 (sf-plu:stmt-focused G (acoterm (U2 =^ pr UR)) vl avalue U1)
(st G # vl avalue UR)
<- subtype (pr U1) (pr U2).
-: teval1 (sf-plu:stmt-focused G (acoterm (U2 =^ (S =v T))) vl avalue U1)
(st G # xt axt S T)
<- subtype (pr U1) (pr U2).
% reset rule
-: teval1 (sf-val:stmt-focused (G $; C) # vl avalue U)
(st G C vl avalue U).
% Perform the abstraction over the primitive values.
-: teval1 (sf-val:stmt-focused G # vl (num N)) (st G # vl (avalue int)).
-: teval1 (sf-val:stmt-focused G # vl inc)
(st G # vl (avalue (int => pr int))).
-: teval1 (sf-app:stmt-focused G (C ; V) vl (num N))
(st G (C ; V) vl (avalue int)).
-: teval1 (sf-app:stmt-focused G (C ; V) vl inc)
(st G (C ; V) vl (avalue (int => pr int))).
-: teval1 (sf-plu:stmt-focused G (acoterm S) vl (num N))
(st G (acoterm S) vl (avalue int)).
-: teval1 (sf-plu:stmt-focused G (acoterm S) vl inc)
(st G (acoterm S) vl (avalue (int => pr int))).
% one can see that teval* and of are all terminating.
% This is because each teval1 step eliminates one term constructor.
% Actually, the existence of coof, which is recursive, makes the
% argument more subtle. We see that constructors like lam, xshift0,
% num and inc are constantly eliminated. The constructors like
% avalue and acoterm may be duplicated when we substitute under
% lambda or shift. However, the corresponding rules also eliminate
% that very lambda/shift, so that replication cannot go forever.
% Finally we need to show that L$ and S$ (introduced in several rules
% including coof) are eliminated too.
%query 1 2 of (st $0 # vl inc) (int => (pr int)).
% %query 1 7 deep-focus (st $0 # vl (lam int [x] vl inc @ vl x)) G C E.
%query 1 7 teval (st $0 # vl (lam int [x] vl inc @ vl x)) R.
%query 1 7 of (st $0 # vl (lam U [x] vl inc @ vl x)) R.
% With sub-typing, multiple solutions of e1 are now possible.
%query 2 2 of (st $0 # e1) R.
%query 1 1 of (st $0 # e2) int.
% the following does not type-check, as expected. Actually,
% when subtyping is present, we can no longer `guess' U!
% %query 0 1 of (st $0 # vl (lam U [x] (vl x @ vl x))) R.
%% The following is non-terminating term, but it's type is inferred anyway:
%% U1 (any type).
%% query 1 2 of (cl # $ tfix @ vl (num z)) R.
%% evaluation diverges
%% %query 1 2 eval* (cl # $ tfix @ vl (num z)) R.
% type-checking shifts...
%query 1 2 of (st $0 # (reset (vl num z))) int.
% consecutive typechecking of reset (shift0 f 3)
%query 1 1 teval (st $0 # (reset (shift0 S ([_] vl num z)))) R.
%query 1 1 teval (st (S$ ($0 $; #) # S) # (vl avalue int)) R.
%query 1 1 teval (st ($0 $; #) # (xt axt S (pr int))) R.
%query 1 1 of (st $0 # (reset (shift0 S ([_] vl num z)))) R.
%query 0 1 of (st $0 # (shift0 S ([_] vl num z))) R.
% Type-checking Ken's examples
% \x:int. inc (shift0 k. 0)
%query 1 2 of (st $0 # vl lam int [x] vl inc @ (shift0 S [k] vl num z)) R.
%% R = int => (int =^ T1) =v (pr int);
%% S = int =^ T1.
% \x:int. inc (shift0 k. (shift0 k2 . 0))
%query 1 2 of (st $0 # vl lam int [x] vl inc @
(shift0 S [k] (shift0 S2 [k2] vl num z))) R.
%% R = int => (int =^ T1) =v (U1 =^ T2) =v (pr int);
%% S2 = U1 =^ T2;
%% S = int =^ T1.
% subtyping may cause multiple solutions
%query 1 2 of (st $0 # e3) int.
%query 2 2 of (st $0 # e4) int.
%query 1 1 of (st $0 # e5) R. % will diverge when trying 2nd solution
%query 2 2 of (st $0 # e6) R.
% Danvy-Filinski test
%query 2 2 of (st $0 # e7) int.
% The following is stuck -- and it doesn't type-check either
% But we need to specify the type S precisely...
%query 0 2 of (st $0 # e81) R.
% Indeed, the inferred type of lambda's body is impure
%query 1 2 of (st $0 # vl lam int [x] e81) R.
%% R = int => (U1 =^ T1) =v (pr int).
%query 1 2 of (st $0 # e82) int.
%query 1 2 of (st $0 # e83) int.
% checking shift f f.
% It finds two solutions! It can find more as more exist.
%query 2 2 of (st $0 # vl lam T [x] (shift0 S [k] (k $ vl x))) R.
% other complex examples
% Multiple solutions exist, for different purity of the context to be
% bound to f...
%query 2 2 of (st $0 # vl lam T [x]
(shift0 S1 [f] vl num z) @ (shift0 S2 [f] vl num (s z))) R.
% (lambda (x) (shift f (f inc)) (shift f 2))
% Now, there is only one solution
% But subtyping increases the number: S2's answer type can be
% exceptional too..
%query 2 2 of (st $0 # vl lam T [x]
(shift0 S1 [f] (f $ vl inc)) @ (shift0 S2 [f] vl num (s z))) R.
% Do the same with shift, control, control0.
% lambda with a shift enclosed in reset: \x. # $ shift k. 3
% It has a pure type.
% Multiple solutions because k is not applied, and so the typechecker
% can make S bigger and bigger (more and more exceptional)
% But if we make subtyping deterministic, we get back to the one solution.
%query 1 2 of (st $0 # vl lam T [x] (reset (shift0 S [k] vl num z))) R.
% (lambda (x) shift f. x (f 1))
% again, multiple solutions exist for the different degrees of purity of T
%query 2 2 of (st $0 # vl lam T [x] (shift0 S [k] (vl x @ (k $ vl num z)))) R.
% The example for subtyping: type-checking
% \x. if x then abort 1 else 2
% we Church encode the conditional
etrue = vl lam T1 [v1] vl lam T1 [v2] (vl v1 @ vl num z).
efalse = vl lam T1 [v1] vl lam T1 [v2] (vl v2 @ vl num z).
econd = vl lam ((int => T) => (pr (int => T) => T)) [x]
% econd = vl lam T [x]
(vl x @
% then branch
% (vl lam int [_] (vl num z)) @
(vl lam int [_] (shift0 S [_] (vl num z))) @
% else branch
(vl lam int [_] vl num (s z))
).
% Indeed, more than one solutions is possible: when the result is
% pure, and various degrees of impurity.
%query 2 2 of (st $0 # etrue) R.
%query 1 2 of (st $0 # econd) R.
% The following won't type-check: because it is an effectful term!
% I spent hours trying to understand why not. The typechecker was
% right: (econd @ etrue) has no pure type...
%query 0 1 of (st $0 # (econd @ etrue)) R.
% The following works great...
%query 1 1 of (st $0 # (reset (econd @ etrue))) R.
%query 1 1 eval* (st $0 # (reset (econd @ etrue))) R.
% This works without reset, but it won't type-check without it!
% Great!
%query 1 1 eval* (st $0 # (econd @ efalse)) R.
% But why the following reports R int => (pr U1)? It can't be pure?
% perhaps there are some unresolved constraints, which are not
% printed... I think this is some artifact of defined abbreviations.
%query 2 2 of (st $0 # vl lam T [x] (econd @ etrue)) R.
% The following correctly infers the impure type...
%query 1 2 of (st $0 # vl lam int [y]
(vl lam ((int => T) => (pr (int => T) => T)) [x]
(vl x @
% then branch
% (vl lam int [_] (vl num z)) @
(vl lam int [_] (shift0 S [_] (vl num z))) @
% else branch
(vl lam int [_] vl num (s z))
))
@ etrue) R.
% Another example, by Ken
% ``We need the subtyping because the non-terminating system already allows
% things like "(\f. (f inc) + (f (\x. abort 3))) (\g. g 0)", I believe.'
%query 0 1 of (st $0 # (
(vl lam int [y]
(vl lam T [f] (shift0 S1 [k] (vl f @ vl inc)) @
(shift0 S2 [k] (vl f @ (vl lam int [x]
(shift0 S3 [k] vl num z) ))))
%{ @ (vl lam T2 [g] (vl g @ vl num z)) }% ))) R.
% It does type: but we must specify the type of T precisely now...
%query 1 1 of (st $0 # (
(vl lam int [y]
(vl lam ((int => (int =^ (pr int)) =v (pr int)) => (pr int)) [f] (shift0 S1 [k] (vl f @ vl inc)) @
(shift0 S2 [k] (vl f @ (vl lam int [x]
(shift0 S3 [k] vl num z) ))))
%{ @ (vl lam T2 [g] (vl g @ vl num z)) }% ))) R.
%{
%query 1 1 of (st $0 # (
(vl lam int [y]
(vl lam ((int => (int =^ (pr int)) =v (pr int)) => (pr int)) [f] (shift0 S1 [k] (vl f @ vl inc)) @
(shift0 S2 [k] (vl f @ (vl lam int [x]
(shift0 S3 [k] vl num z) ))))
@ (vl lam (int => (int =^ (pr int)) =v (pr int)) [g] (vl g @ vl num z)) ))) R.
% It does type! But if we leave the type of the second lambda as
% an uninstantiated type variable, it diverges. Now we really have
% to specify the types of bound variables, because Twelf's search strategy
% is incomplete.
%query 1 1 of (st $0 # (
(vl lam int [y]
(vl lam ((int => (int =^ (pr int)) =v (pr int)) => (pr int)) [f] (shift0 ((int => (pr U1)) =^ (pr int)) [k] (vl f @ vl inc)) @
(shift0 (int =^ T1) [k] (vl f @ (vl lam int [x]
(shift0 (int =^ (pr int)) [k] vl num z) ))))
@ (vl lam (int => (int =^ (pr int)) =v (pr int)) [g] (vl g @ vl num z)) ))) R.
}%
% A simpler example...
%query 1 2 of (st $0 # vl lam int [x]
(vl lam T [f] vl f @ (vl lam int [_] vl num z ))
@ (vl lam (int => (int =^ (pr int)) =v (pr int)) [g] vl g @ vl num z)) R.
% Another kind of shift f f
%query 2 2 of (st $0 # vl lam T [dummy] shift0 S [k] vl lam U [x] k $ vl x) R.
% (reset ((shift f (f inc)) (shift f (f 1))))
% This should fail to type-check with ordinary shift?
%query 1 1 of (st $0 #
(reset ((shift0 S1 [f] (f $ vl inc)) @
(shift0 S2 [f] (f $ vl num z))))) R.
%% R = int;
%% S2 = int =^ (pr int);
%% S1 = (int => (pr int)) =^ (int =^ (pr int)) =v (pr int).
% Here, two resets are really necessary.
%query 1 1 of (st $0 #
(reset (reset ((shift0 S1 [f] ((vl lam T [x] (f $ vl inc)) @
(shift0 S2 [f] (f $ vl num z)))) @ vl num z
)))) R.
%% R = int;
%% S2 = int =^ (pr int);
%% T = int;
%% S1 = (int => (pr int)) =^ (pr int).
% but does typecheck with the emulated shift...
shift = [S] [f] shift0 S [g] (reset (f g)).
%query 1 1 of (st $0 #
(reset ( ((shift S1 [f] ((vl lam T [x] (f $ vl inc)) @
(shift S2 [f] (f $ vl num z)))) @ vl num z
)))) int.
% This is a better example. Two resets are needed. Moreover, this
% example does not typecheck with shift given the type system of
% Gunter, Remy and Riecke.
%query 1 1 of (st $0 #
(reset (reset ((shift0 S1 [f] ((vl lam T [x] (f $ vl inc)) @
(shift0 S2 [f] (vl inc)))) @ vl num z
)))) R.
%% R = int => (pr int);
%% S2 = int =^ (pr int);
%% T = int;
%% S1 = (int => (pr int)) =^ (pr int).
%% The above was one of the possible types. There are others, with S2
%% being `more effectful'
% but it does typecheck with the emulated shift...
%query 1 1 of (st $0 #
((reset ((shift S1 [f] ((vl lam T [x] (f $ vl inc)) @
(shift S2 [f] (vl inc)))) @ vl num z
)))) R.
% (reset (let ((f (reset ((shift f f) (shift f (f 1)))))) (f inc)))
% Should fail type-checking with shift and shift0,
% should type-check with control and control0.
%query 0 1 of (st $0 #
(reset ((vl lam T [f] (vl f @ vl num z)) @
(reset ((vl lam T1 [x] (shift0 S1 [k] (k $ vl x))) @
(shift0 S2 [k] (k $ vl num z))))))) R.
% but this works. The way S2 is being guessed is a bit non-intuitive...
%query 1 1 of (st $0 #
(reset ((vl lam T1 [x] (shift0 S1 [k] (k $ vl x))) @
(shift0 S2 [k] (vl inc))))) R.
%query 1 1 of (st $0 #
(reset ((vl lam T1 [x] (shift0 S1 [k] (k $ vl x))) @
(shift0 ((int => pr int) =^ S2) [k] (vl inc))))) R.
%query 2 2 of (st $0 #
(reset ((vl lam T [f] (vl f @ vl num z)) @
(reset ((vl lam T1 [x] (shift0 S1 [k] (k $ vl x))) @
(shift0 ((int => pr int) =^ S2) [k] vl inc)))))) int.
%query 1 0 of (st $0 #
(reset ((vl lam T [f] (vl f @ vl num z)) @
(reset ((vl lam T1 [x] (shift0 S1 [k] (k $ vl x))) @
(shift0 S2 [k] (k $ vl inc))))))) R.
% I guess the following needs explicit type specification...
%query 1 0 of (st $0 #
(reset ((vl lam T1 [x] (shift0 S1 [k] (k $ vl x))) @
(shift0 S2 [k] (k $ vl inc))))) R.
% Check that abort 0 cannot be assigned a pure type...
% OK, this fails as expected.
%query 0 1 of (st $0 # (shift0 S [k] (vl num z))) R.
% Now, try Ken's example and explicitly assign the type to S
% Still none...
%query 0 1 of (st $0 # (shift0 ((A => B) =^ C) [k] (vl num z))) R.
%query 0 1 of (st $0 # (shift0 S [k] (vl num z))) (int => T).
%query 1 2 of (st $0 # vl lam int [_]
(shift0 ((A => B) =^ C) [k] (vl num z))) R.
%% R = int => ((A => B) =^ C) =v (pr int);
% But ((abort 0) 1) should typecheck, under lambda...
%query 2 2 of (st $0 # vl lam int [_]
((shift0 S [k] (vl num z)) @ (vl num (s z)))) R.
%% R = int => (U1 =^ T1) =v (pr int);
%% S = (int => (pr U1)) =^ T1.
%% R = int => (U2 =^ T2) =v (pr int);
%% S = (int => (U2 =^ T2) =v (pr int)) =^ (pr int).
% In this small-step typechecker, from the fact E 0 has some type T,
% we never infer that E has the type int -> T. This is because we always
% do the typechecking bottom-up: we typecheck the operand and the
% operator of the application before we check the type of the application.
% If the operator or the operand are impure, that is the end of the
% story and the whole application is impure (and we switch to a
% different typechecking path: an impure expression in a context).
% The example suggested by the TLCA reviewer.
% \x:int shift0(\k.1) (shift-0(\k.true))
% we replace `true' with inc because we don't have booleans and replace 1
% with 0, for brevity.
%query 2 2 of (st $0 # vl lam int [_]
((shift0 S1 [k1] (vl num z)) @ (shift0 S2 [k2] (vl inc))))
R.
%% ---------- Solution 1 ----------
%% R = int => (U1 =^ T1) =v (pr int);
%% S2 = int =^ T1;
%% S1 = (int => (pr U1)) =^ ((int => (pr int)) =^ (pr int)) =v (pr int).
%% ---------- Solution 2 ----------
%% R = int => (U2 =^ T2) =v (pr int);
%% S2 = int =^ (pr int);
%% S1 =
%% (int => (U2 =^ T2) =v (pr int)) =^ ((int => (pr int)) =^ (pr int))
%% =v (pr int).
small-step-typechecking/lfix-calc.elf000600 001370 000000 00000024646 10547404277 020237 0ustar00olegwheel000000 000000 % Small-step evaluation and type-checking:
% Simply typed lambda-calculus with fixpoint
%
% The code accompanying the paper
% `A Substructural Type System for Delimited Continuations'
% Joint work with Chung-chieh Shan.
%
%
% Both dynamic _and_ static semantics (typing rules) are small step.
% We use equivalence rules for focusing.
% Static evaluation rules are different from dynamic ones: for one,
% in static semantics, lambda is not a `value' and can be further evaluated
% (i.e., type-checked). However, the static evaluation context is a
% refinement of the dynamic evaluation context (see focus-more).
% For the sake of modes (to be able to assign the first argument of
% the focusing and evaluation relations the input mode) we avoid `guessing'
% of the type of the bound variable in the lambda term. We require all
% binders to be annotated with types. That makes the type-checking
% deterministic.
% Incidentally, the annotation requirement is not an onerous one:
% the term to eventually type-check, the input to %query, may indeed
% contain uninstantiated logic variables, and Twelf will guess them.
% It is the type-checking rules that are fully deterministic functions
% from their input to their output arguments. The fresh logic variables
% during the type-checking are used only for parameter passing and never
% for guessing.
% Without the input mode annotation however, Twelf
% does not compute much: it's evaluator does not print out the constraints,
% so with no mode annotations, Twelf has no incentive to resolve the
% constraints and so keeps assigning expressions fresh logic variables.
% Types
pure: type. %name pure U.
cotp: type. %name cotp S.
int: pure. % one single primitive type for now.
=>: pure -> pure -> pure. %infix right 200 =>.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Expressions
value: type. %name value V v.
term: type. %name term E x.
% primitive values: natural literals and the increment function
nat: type. %name nat N.
z: nat.
s: nat -> nat.
num : nat -> value. % two constants
inc : value.
% first argument is the type of the bound variable
lam: pure -> (value -> term) -> value.
% we want non-termination in the dynamic semantics
% The first two arguments TA and TR to self determine
% the type of the `self' value TA => TR
% (see the examples below).
fix: pure -> pure -> (value -> value -> term) -> value.
% The following `abstract value' isn't present in the
% regular terms (at run-time). It is only used for
% abstract interpretation. Alternatively, we could define
% abstract trees that are isomorphic to the regular terms,
% with the corresponding conversion (aka, `abstraction'
% operation).
% The abstract value is the `value' in the sense it is
% not inspectable and not traversable.
avalue: pure -> value.
% Terms
% all values are terms
vl: value -> term. %prefix 400 vl.
% application
@: term -> term -> term. %infix left 300 @.
% inverse context constructors... aka context zippers. For each
% term constructor, we have its zippified versions.
% A binary constructor has two zippified equivalents.
% Plus we have the `top' zipper.
% In the dynamic universe, we treat values as atomic, and so we don't
% have co-term constructors for value constructors.
fcoterm: type. %name fcoterm Y k.
coterm: type. %name coterm C k.
#: fcoterm. % The top.
;: coterm -> value -> fcoterm. %infix right 250 ;.
% all fcoterms are coterms
cl: fcoterm -> coterm. %prefix 210 cl.
,: term -> coterm -> coterm. %infix right 250 ,.
% we define a class fcoterms to be used in the abstract interpretation
% (aka static semantics)
afcoterm: type. %name afcoterm Z k.
al: afcoterm -> fcoterm.
stmt: type.
$: coterm -> term -> stmt. %infix right 200 $.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Term and context equivalences. Needed for the focusing
% This is all purely structural.
equiv: stmt -> stmt -> type. %name equiv Q.
equiv,1: equiv (C $ F @ E) (E , C $ F).
equiv,2: equiv (E , C $ F) (C $ F @ E).
equiv;1: equiv (C $ vl V @ E) (cl C ; V $ E).
equiv;2: equiv (cl C ; V $ E) (C $ vl V @ E).
% focusing. Given (C $ E) derive, using equivalences (Y $ V).
% That means we prefer equiv;1 over equiv,1: we try the latter only
% if the former does not apply.
stmt-focused: type.
$f: fcoterm -> value -> stmt-focused. %infix right 200 $f.
focus: stmt -> stmt-focused -> type. %name focus F.
%mode focus +P1 -P2.
-: focus (cl Y $ vl V) (Y $f V).
% Rotation: apply equiv,2 followed by equiv;1
-: focus (E , C $ vl V) R <- focus (cl C ; V $ E) R.
-: focus (C $ vl VF @ E) R <- focus (cl C ; VF $ E) R.
-: focus (C $ (F1 @ F2) @ E) R <- focus (E , C $ F1 @ F2) R.
%worlds () (focus _ _).
% Alas, Twelf can't see the termination. It requires complex reasoning
% on the number of commas. The number of commas plus the number of term
% constructors decrease.
%% terminates (M) (focus M _).
%covers focus +M1 -M2.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Dynamic semantics
% It disregards the type annotations on binders in lam and fix.
% Dynamic semantics is untyped.
eval: stmt -> stmt -> type.
%mode eval +M1 -M2.
eval1: stmt-focused -> stmt -> type.
%mode eval1 +M1 -M2.
-: eval1 ((C ; inc) $f (num N))
(C $ vl (num (s N))).
-: eval1 ((C ; (lam _ F)) $f V)
(C $ (F V)).
-: eval1 ((C ; (fix TA TR F)) $f V)
(C $ (F (fix TA TR F) V)).
-: eval P R
<- focus P PF
<- eval1 PF R.
% Big step: transitive closure of eval*: just to be able to run
% the examples
eval* : stmt -> stmt -> type.
%mode eval* +M1 -M2.
eval1* : eval* (cl # $ vl V) (cl # $ vl V).
eval2* : eval* M M'' <- eval M M' <- eval* M' M''.
e1 = vl (lam TF [f] (vl lam TX [x] (vl f @ vl x))).
e2 = (e1 @ vl inc @ vl (num z)).
%query 1 2 eval* (cl # $ e2) R.
% define these polymorphically, for the sake of the future type-checking.
tetrue : {T:pure} term = [T:pure]
vl (lam (int => T) [e1] (vl lam (int => T) [e2] (vl e1 @ vl num z))).
tefalse : {T:pure} term = [T:pure]
vl (lam (int => T) [e1] (vl lam (int => T) [e2] (vl e2 @ vl num z))).
% Just instantiate with some dumb types. The evaluator doesn't care about
% types anyway.
etrue = tetrue int.
efalse = tefalse int.
% We just assign some random types to the binders.
erec = vl (fix int int [self] [v] (vl v @ (vl lam int [_] (vl self @ efalse))
@ etrue)).
%query 1 2 eval* (cl # $ (erec @ etrue)) R.
% the following won't terminate, as expected.
erec1 = vl (fix int int [self] [v] (vl v @ (vl lam int [_] (vl self @ etrue))
@ etrue)).
% %query 1 1 eval* (cl # $ (erec1 @ etrue)) R.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Static semantics: type-checking evaluation
%
% We have two choices: add the variant of `abstract values' to the
% terms we already have -- or define a whole new data type of
% abstract terms. The latter are related to the regular ones by the
% abstraction operation. Perhaps the second choice is cleaner -- yet
% more laborous as we need to define all the term constructors
% such as application, etc. So, we use the first approach for now.
% Actually, the second approach has a problem: how to abstract `lam'
% (where `value' is used in the contra-variant position).
% And the first approach has the advantage that we automatically get
% the property that static focusing is the refined version of the
% dynamic one.
% So, the static (abstract interpretation) focusing goes deeper.
% Because now we are allowed to traverse under lambda, we need
% to define new context constructors (new zippers).
;L: fcoterm -> pure -> afcoterm. %infix right 250 ;L.
f;: fcoterm -> pure -> pure -> afcoterm.
% and we need the refined focusing relation.
focus-more: stmt-focused -> stmt-focused -> type.
%mode focus-more +F1 -F2.
-: focus-more (C $f inc) (C $f inc).
-: focus-more (C $f num N) (C $f num N).
-: focus-more (C $f avalue T) (C $f avalue T).
-: focus-more (C $f lam T E) R2
<- focus (cl (al (C ;L T)) $ E (avalue T)) R1
<- focus-more R1 R2.
-: focus-more (C $f fix TA TR E) R2
<- focus (cl (al (f; C TA TR)) $ E (avalue (TA => TR)) (avalue TA)) R1
<- focus-more R1 R2.
%worlds () (focus-more _ _).
%covers focus-more +F1 -F2.
% ----------
% `static' one-step evaluation function: abstract interpretation transitions
teval: stmt -> stmt -> type.
%mode teval +M1 -M2.
teval1: stmt-focused -> stmt -> type.
%mode teval1 +M1 -M2.
-: teval M R
<- focus M MF
<- focus-more MF MFF
<- teval1 MFF R.
% The main application rule
-: teval1 (C ; (avalue (T1 => T2)) $f avalue T1)
(C $ vl (avalue T2)).
% => intro rule. There is no longer a need for hacks.
-: teval1 (al (C ;L T1) $f avalue T2)
(cl C $ vl (avalue (T1 => T2))).
-: teval1 (al (f; C TA TR) $f avalue TR)
(cl C $ vl (avalue (TA => TR))).
-: teval1 (C ; inc $f avalue int)
(C $ vl (avalue int)).
% This is like the let rule
-: teval1 (C ; lam T E $f avalue T)
(C $ E (avalue T)).
-: teval1 (C ; fix TA TR E $f avalue TA)
(C $ E (avalue (TA => TR)) (avalue TA)).
% Perform the abstraction over the primitive values.
-: teval1 (C $f (num N)) (cl C $ vl (avalue int)).
-: teval1 (C $f inc) (cl C $ vl (avalue (int => int))).
% Big step: transitive closure of teval*
teval* : stmt -> stmt -> type.
%mode teval* +M1 -M2.
teval1* : teval* (cl # $ vl avalue T) (cl # $ vl avalue T).
teval2* : teval* E E'' <- teval E E' <- teval* E' E''.
of : stmt -> pure -> type.
%mode of +M -U.
-: of P T <- teval* P (cl # $ vl avalue T).
% one can see that teval* and of are all terminating.
% This is because each teval1 step eliminates one term constructor.
%query 1 1 of (cl # $ vl inc) R.
% %query * 3 focus-more (# $f (lam [x] vl inc @ vl x)) R1.
%query 1 7 teval (cl # $ vl (lam int [x] vl inc @ vl x)) R.
%query 1 7 of (cl # $ vl (lam U [x] vl inc @ vl x)) R.
%query 1 1 of (cl # $ e1) R.
%query 1 1 of (cl # $ e2) R.
% the following does not typecheck, as expected.
%query 0 1 of (cl # $ vl (lam U [x] (vl x @ vl x))) R.
tfix = vl (fix U1 U2 [self] [v] (vl self @ vl v)).
%query 1 2 of (cl # $ tfix) R.
% The following is non-terminating term, but it's type is inferred anyway:
% U1 (any type).
%query 1 2 of (cl # $ tfix @ vl (num z)) R.
% evaluation diverges
% %query 1 2 eval* (cl # $ tfix @ vl (num z)) R.