Istari

Bar (simulated partial types)

This library simulates much of the partial type mechanism using future and guarded recursive types. Partial types over A (here called bar A) are simulated by a recursive type that either returns an A now or tries again one step in the future.

The simulated version supports much of the functionality of partial types, including fixpoint induction, but notably it cannot support a halting predicate. Since one cannot express halting in the first place, it is also impossible to convert partial objects that halt into total objects.

Note that fixpoint induction over simulated partial types does not have an admissibility requirement, unlike what happens with true partial types. It follows that it is fundamentally impossible to implement a halting predicate; were halting implementable, one could implement Smith’s paradox (which requires a halting predicate) and derive a contradiction. (One can implement predicates that may naively appear to be a halting predicate, but they are true too often.)

Thus there seems to be a tradeoff: simulated partial types avoid the complication of admissibiity but they cannot talk about termination, while true partial types provide the opposite.


Pause is just the identity, but we insert it in various places to break up redices so that accidental ill-typed terms don’t result in runaway reduction:

pause : intersect (i : level) (a : U i) . a -> a
      = fn v0 . v0

The general Y combinator is not well typed (if it were, every proposition would be provable), but it can be made type correct using the future modality:

ffix : intersect (i : level) (a : U i) . (future a -> a) -> a
     = fn f .
          pause
            (fn x . f (let next x' = x in next (pause x' x)))
            (next (fn x . f (let next x' = x in next (pause x' x))))

Note that ffix provides an induction principle over time. If a is true now assuming a is true one step in the future (and therefore one step closer to the semantics’ step-indexed time horizon), then a is true always.

The reduction for ffix is:

ffix f --> f (next (ffix f))

It appears in the registry under the name Bar.unroll_ffix. It can also be used through the unroll tactic.

The simulated partial type bar A provides an A now, or sometime later:

bar : intersect (i : level) . U i -> U i
    = fn a . rec t . a % future t

bar_unroll : forall (i : level) (a : U i) . bar a = (a % future (bar a)) : type

The type acts as a monad. Its unit is now:

now : intersect (i : level) (a : U i) . a -> bar a
    = fn x . inl x

Another intro form is laterf:

laterf : intersect (i : level) (a : U i) . future (bar a) -> bar a
       = fn x . inr x

The laterf form is very often used with next, so we define:

later : intersect (i : level) (a : U i) . bar a -> bar a
      = fn x . laterf (next x)

The monadic bind is bindbar:

bindbar : intersect (i : level) (a b : U i) . bar a -> (a -> bar b) -> bar b

`bindbar (now x) f --> f x ;
`bindbar (laterf x) f --> let next y = x in later (`bindbar y f)
`bindbar (later x) f --> later (`bindbar x f)

The syntactic sugar bindbar x = m in n is accepted for `bindbar m (fn x . n).

The monad laws are respected:

bindbar_id_left : forall (i : level) (a b : U i) (e : a) (f : a -> bar b) .
                     (bindbar x = now e in f x) = f e : bar b

bindbar_id_right : forall (i : level) (a : U i) (m : bar a) .
                      (bindbar x = m in now x) = m : bar a

bindbar_assoc : forall
                   (i : level)
                   (a b c : U i)
                   (e : bar a)
                   (f : a -> bar b)
                   (g : b -> bar c) .
                   (bindbar y = (bindbar x = e in f x) in g y)
                     = (bindbar x = e in bindbar y = f x in g y)
                     : bar c

Observe that bindbar always produces an element of some bar type. A variation on it, bindbart, produces a type instead:

bindbart : intersect (i : level) (a : U i) . bar a -> (a -> U i) -> U i

`bindbart (now x) t --> t x ;
`bindbart (laterf x) t --> let next y = x in future (`bindbart y t) ;
`bindbart (later x) t --> future (`bindbart x t) ;

The syntactic sugar bindbart x = m in b is accepted for `bindbart m (fn x . b).

The monad laws for bindbart:

bindbart_id_left : forall (i : level) (a : U i) (e : a) (t : a -> U i) .
                      bindbart x = now e in t x = t e : U i

bindbart_assoc : forall (i : level) (a b : U i) (e : bar a) (f : a -> bar b) (t : b -> U i) .
                    (bindbart y = (bindbar x = e in f x) in t y)
                      = (bindbart x = e in bindbart y = f x in t y)
                      : U i

Bar is covariant:

bar_subtype : forall (i : level) (a b : U i) . a <: b -> bar a <: bar b

Finally we can define a fixpoint operator on simulated partial objects:

bfix : intersect (i : level) (a : U i) . (bar a -> bar a) -> bar a
     = fn f . ffix (fn x . f (laterf x))

The reduction for bfix is:

bfix f --> f (later (bfix f))

It appears in the registry under the name Bar.unroll_bfix. It can also be used through the unroll tactic.

We can iterate over simulated partial objects using the bar_iter iterator. The cases are now and later:

bar_iter : intersect (i : level) (a : U i) (P : bar a -> U i) .
              (forall (x : a) . P (now x))
              -> (forall (xf : future (bar a)) . let next x = xf in future (P x) -> P (later x))
              -> forall (x : bar a) . P x

bar_iter hn _ (now x) --> hn x
bar_iter hn hl (laterf x) --> let next y = x in hl (next y) (next (bar_iter P hn hl y))
bar_iter hn hl (later x) --> hl (next x) (next (bar_iter P hn hl x))

This is employed automatically by the induction tactic on hypotheses of bar type.

A corollary of induction says we can map a function through bindbart:

bindbart_map : forall (i : level) (a : U i) (b c : a -> U i) (e : bar a) .
                  (forall (x : a) . b x -> c x)
                  -> (bindbart x = e in b x)
                  -> bindbart x = e in c x