It’s probably too late/too much to do this in this paper, but here’s an idea for how to make the proofs in GTT easier / a suggestion for what an "Abadi-Plotkin logic” for dynamism might include. First, suppose we have type variables in the program logic, where a judgement X type |- T(X) type implies monotonicity of T, i.e. T(A) <= T(B) if A <= B. (This is admissible in GTT, but not enforced for an “unknown" type constructor.) Second, suppose we have terms with free type variables, where X type |- E : T(X) implies that E satisfies a “fundamental theorem” E(A) <= E(B) : T(A) <= T(B) Next, suppose we have a value->value function type A => B (maybe this only needs to exist in the program logic, not in the programming language, as a way to talk about a term with a free value variable). Having congruence and beta for this implies that every complex-value-with-a-variable is monotone, because f <= g : A => B <= A’ => B' and a <= a' : A <= A’ implies f(a) <= g(a') : B <= B’, and we have beta for the applications as equidynamism. Then I think the following “path induction” principles are valid for proving dynamism by "replacing upcasts with the identity whenever that type checks". These are basically just packaging up using monotonicity, transitivity, and the universal property of the upcast, which says in this notation id_A <= <B <-< A> : A => A <= A => B <B <-< A> <= id_B : A => B <= B => B You have to be careful about (a) whether it’s the source or target of the function that’s “free”, and (b) whether the term with the cast is on the left or right of the term dynamism judgement, which gives: (1) right upcast induction X type, f : X => B |- E(X,f) : T(X) T(B) <= C [might not hold] E(B, id_B) <= F : T(B) <= C —————————————————— E(A, <B <-< A>) <= F : T(A) <= C [ use monotonicity to get E(A, upcast) <= E(B,id_B) : T(A) <= T(B) and then transitivity with the premise ] (2) left upcast induction Y type, f : A => Y |- E(Y,f) : T(Y) C <= T(A) [might not hold] F <= E(A, id_A) <= F : C <= T(A) —————————————————— F <= E(B, <B <-< A>) : C <= T(B) Here are some examples using them: (a) Suppose we have a List A type and a complex-value map (everything is positive) : (A => B) x List A => List B We want to show that map < B <- A > is an upcast, i.e. (a1) id_<List A> <= map < B <- A > : L(A) => L(A) <= L(A) => L(B) Note that Y type, f : A => Y |- map <Y <-< A> : L(A) => L(Y) and L(A) => L(A) <= L(A) => L(A). Therefore, by left upcast induction, it suffices to show id_<List A> <= map id_A, which is true by beta/eta. (a2) map < B <- A > <= id_<List B> : L(A) => L(B) <= L(B) => L(B) Note that X type, f : X => B |- map <B <-< X> : L(X) => L(B), and L(B) => L(B) <= L(B) => L(B). Therefore, by right upcast induction, it suffices to show map < id_B > <= id_<List B>, which is true by eta. (b) We want to show that u := \z.split z as (x,y) in (<A’ <-< A>x, <B’ <-< B> y) is an upcast A x B <= A’ x B’. (b1) To show: id_<A x B> <= u : A x B => A x B <= A x B => A’ x B’ First, note that Y type, f : A => Y |- \z.split z as (x,y) in (f x, <B’ <-< B> y) : A x B => Y x B and A x B => A x B <= A x B => A x B’. Therefore, by left upcast induction (with T(Y) = A x B => Y x B) , it suffices to show id_<A x B> <= \z.split z as (x,y) in (id_A x, <B’ <-< B> y) : A x B => A x B’ and by beta id_<A x B> <= \z.split z as (x,y) in (x, <B’ <-< B> y) : A x B => A x B’ Second, Y type, f : B => Y |- \z.split z as (x,y) in (x, f y) : A x B => A x Y and A x B => A x B <= A x B => A x B Therefore, by left upcast induction (with T(Y) = A x B => A x Y), it suffices to show id_<A x B> <= \z.split z as (x,y) in (x, id_B y) which is true by beta/eta. (b2) To show: u <= id_<A' x B'> : A x B => A' x B' <= A' x B' => A’ x B’ Since X type, f : X => A' |- \z.split z as (x,y) in (f x, <B’ <-< B> y) : X x B => A’ x B’ and A' x B => A’ x B’ <= A’ x B' => A’ x B’ it suffices to show \z.split z as (x,y) in (id_A' x, <B’ <-< B> y) <= id_<A' x B’> And since X type, f : X => B' |- \z.split z as (x,y) in (id_A’ x, f y) : A' x X => A’ x B’ <= A’ x B’ => A’ x B’ A' x B' => A’ x B’ <= A’ x B’ => A’ x B’ it suffices to show \z.split z as (x,y) in (id_A' x, id_B’ y) <= id_<A' x B’> which again holds by beta-eta. ---------------------------------------------------------------------- For (1) and (2), I was thinking that A => A’ and B -o B’ could exist only at the program logic level, not as part of the programming language. I.e. without changing GTT the programming language from what’s currently there, add a separate layer where we have a judgement like Psi | G | D |- E : T where Psi ::= Psi,X type | Psi,f : A1 => A2 | Psi,s : B1 -o B2 which act like a meta-language quantifier that we would currently say as “for any complex value from A to A’ ” or “for any stack from A -o A’ ”. And then a judgement f : A1 => A2 | G | D |- E : T means (1) for any complex value G, x : A1 |- V : A2, we have G | D |- E[x.V/f] : T, and (2) for related complex values G <= G, x : A1 <= x’ : A1’ |- V <= V' : A2 <= A2’, G <= G | D <= D |- E[x.V] <= E’[x’.V’] : T <= T Similarly, S : B1 -o B2 | G | D |- E : T means (1) for any stack G | B1 |- S : B2, G | D |- E[S/s] : T (2) for related stacks G <= G | B1 <= B1' |- S <= S’ : B2 <= B2’, G <= G’ | D <= D’ |- E[S/s] <= E’[S’/s] : B2 <= B2’ I.e. in the program logic the first-order quantifiers range over both the objects of the category and the maps. ---------------------------------------------------------------------- OK, I think that if we have type dynamism assumption, then the following variation works: (1) right type dynamism induction X type, X <= B |- E(X) : T(X) T(B) <= C [might not hold] E(B) <= F : T(B) <= C —————————————————— A type, A <= B |- E(A) <= F : T(A) <= C i.e. if you have a term that makes sense for any type below B, then if it has an upper bound at B, it has an upper bound everywhere (2) left dynamism induction Y type, A <= Y |- E(Y) : T(Y) C <= T(A) [might not hold] F <= E(A) : C <= T(A) —————————————————— B type, A <= B |- F <= E(B) : C <= T(B) (3) axioms <A <-< A> = id_A : A => A and <B <<- B> = id_B : B -o B (if type dynamism were not thin, E would depend on the derivation, and the identity would be substituted in in the premise). To see why this is right, assume A <= B. For the downcasts, the ordering is still id_A : A -o A <= <A <<- B> : B -o A <= <B <<- B> : B -o B just like for upcasts id_A : A => A <= <B <-< A> : A <= B <= <B <-< B> : B -o B i.e. the identity on the source of the type precision is less than the cast, which is less than the identity on the target; the only difference is the type in the middle. So, for example, if you have X type, X <= B |- <B <-< X> : X => B and a particular A for which A <= B then it makes sense that <B <-< A> <= <B <-< B> = id_B : A => B <= B => B but also that (if they’re computation types instead) X type, X <= B |- <X <<- B> : B -o X so <A <<- B> <= <B <<- B> = id_B : B -o A <= B -o B So here’s how one of the Kleisli upcasts goes: Suppose B1 <= B1’ and B2 <= B2’. To show: u := \x : U (B1 x B2). thunk {force(<U B1’ <-< U B1> thunk(fst(force x))), force(<U B2’ <-< U B2> thunk(fst(force x))) } is an upcast <U B1’xB2' <-< U B1xB2> (a) B1 <= B1’, B2 <= B2’ |- id_U (B1 x B2) <= u : U (B1 x B2) => U (B1 x B2) <= U (B1 x B2) => U (B1' x B2') Note that Y type, B1 <= Y |- \x : U (B1 x B2). thunk {force(<U Y <-< U B1> thunk(fst(force x))), force(<U B2’ <-< U B2> thunk(snd(force x))) } : U(B1 & B2) => U(Y & B2) so we can contract B1’ to B1, and it suffices to show id <= \x : U (B1 x B2). thunk {force(<U B1 <-< U B1> thunk(fst(force x))), ... } : U (B1 x B2) => U (B1 x B2) <= U (B1 x B2) => U (B1 x B2’) Then (secretly using the fact that U(id_B1) = id_U B1 : U B1 <= U B1), this is equal to \x : U (B1 x B2). thunk {force(thunk(fst(force x))), ... } by reducing an identity cast. Doing the same thing to contract B2’ to B2 and beta-reducing the identity, it suffices to show id <= \x : U (B1 x B2). thunk {force(thunk(fst(force x))), force(thunk(snd(force x))) } which is true by beta-reducing the force-thunk redex that killing the cast exposed, eta-reducing the (fst force x, snd force x) pair, and eta-reducing the thunk(force x) (b) a dual thing should work for B1 <= B1’, B2 <= B2’ |- u <= id_U (B1' x B2’) Because we’re showing an upper bound, use right dynamism induction to contract B1 to B1’ and B2 to B2’, and then beta/eta does it.