Skip to content
Snippets Groups Projects
cast-induction.txt 8.59 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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.