View
215
Download
2
Embed Size (px)
Citation preview
ESC, JML, Spec#, …
• Need a different spec language:– “pure” boolean expressions: length(x)==42– “modeling” types (e.g., pure lists, sets, …)
• If the implementation is pure, can’t use it in the specs!
– x.f versus \old(x.f)
• What if Simplify can’t prove something?– Ignore (e.g., arith, modifies clause): unsound– Rewrite code?– Weaken spec?
• Not really modular: can’t write “app” or “map”
DML, ATS, Omega, …
• Introduce different spec language.– Again, a separate “pure” language
• To capture all properties of lists, you’d have to index them with well, lists.
• Can’t talk about properties of effectful computations (or limited capacity).
• ATS can build proofs, but it’s awkward.
Coq, PRL, Isabelle, …
• Ynot starts with Coq as the basic language:– [co]inductive definitions, h.o. functions,
polymorphism, h.o. predicates, proofs, …– Strong support for modularity
• e.g., can package up and abstract over terms, types, predicates, proofs, in a uniform fashion.
– can prove that, e.g., append is associative and rev(rev(x)) = x, etc. after defining it.
• But huge drawback:– No effects (non-term, IO, state, recursive types, etc.)– Not a strong phase separation
Quick Coq
• Set : (think * in Haskell)– nat, bool, functions from sets to sets, …– inductive set definitions (e.g., list)– co-inductive set definitions (e.g., stream)
• Prop :– Think of nat->Prop as a subset of nat.– Equality, /\, \/, etc.– [co-]inductive definitions (e.g., judgments)
Refinement
• Can form mixed products & sums• {n:nat | n >= 42} is a pair of a nat and
a proof that this nat is >= 42.• Array subscript: forall (A:Set)(n:nat) (v:vector n A) (j:nat), (j < n) -> A
• Can extract Ocaml or Haskell code.– “erase” Prop objects (really, replace with unit).
Purity
• We want to pull the Curry-Howard isomorphism to represent a proof of Prop P as a term with type P.– Reduce proof-checking to type-checking.– Should be no term with type False.
• If we added recursive functions, recursive types, exceptions, or refs, we could code up a term of type False.– So everyone forgoes these “features” in their type
theory.
Ynot and HTT
• We add a new type constructor, IO A– As in Haskell, encapsulate effectful
computations.– We’re not pretending that IO False is a
proof of false -- rather, it’s a computation which when run, if it terminates, then it produces a proof of False.
• Of course, it can’t terminate.
Ynot IO: Attempt #1
• IO : Set -> Set
• return: forall (A:Set), A -> IO A
• bind : forall (A B:Set), IO A -> (A -> IO B) -> IO B
• ffix : forall (A:Set), (IO A -> IO A) -> IO A
Reasoning about IO
• steps : IO A -> IO A -> Prop.• steps_ret_bnd : forall (A B:Set)(v:A)(f:A->IO B), steps (bind (ret v) f) (f v).
• steps_bnd_cong : forall (A B:Set)(c1:IO A)(f:A->IO B), (steps c1 c2) -> (steps (bind c1 f) (bind c2 f)).
• steps_ffix : forall (A:Set)(f:IO A->IO A), steps (ffix f) (f (ffix f))
Problem:
• We have added a way to prove False!• Sketch of problem (not quite right):• Define diverges(c:IO A):Prop
– Define stepsn c1 c2 n as c1 steps to c2 in no more than n steps.
– Define diverges c as there’s no n and v such that stepsn c (ret v) n.
• Define T := { f : nat -> IO nat | for some n, diverges(f n) }
Problem Continued
• Next, define: f(p:T):T = {g;q } where g n = if n = 0 then 0 else (fst p)(n-1)and q argues that for some n, g diverges: (snd p) provides a proof that for some m, (fst p) diverges, so pick n=m+1.
• Finally, take F := ffix(f)– snd(F) proves fst(F) diverges– but fst(F) does not!
How to Fix?
• One option: restrict IO to admissible types. – In essence, we need closure conditions to ensure
that fixed-points preserve typing.– Comprehensions (subsets of types) are
problematic in general.– Crary shows some sufficient syntactic criteria for
determining admissibility.
• Another option: don’t expose steps or any other axiom on IO terms.– Well, we can expose some (the monad laws.)
No Axioms?
• Can interpret IO A := unit.– ret v = tt, bind v f = tt, ffix f = tt
• Without any axioms, can’t tell the difference!• Allows us to establish consistency of logic.
– a trivial model.
• Aleks is then able to prove preservation and progress for the real operational semantics.
• But we have limited reasoning about computations within the system.
Extending IO
• We want to handle the awkward squad:– Refs, IO, exceptions, concurrency, …
• So need to scale IO A.– Today: refs, exceptions– Tomorrow: IO– Quite a ways off: concurrency?
Heaps & Refs in Ynot
We model heaps in Coq as follows:• loc : Set• loc_eq:(x y:loc)->{x=y}+{x<>y}
– can model locs as nats.
• dynamic := {T:Set; x:T}• heap := loc -> option dynamic
– NB: heaps aren’t “Set” w/out impredicative
IO Monad
• Pre := heap -> Prop• Post(A:Set) := A -> heap -> heap -> Prop
• IO: forall (A:Set), Pre -> Post A -> Post exn -> Set.
• Implicit Arguments IO [A].
Return & Throw
ret : forall (A:Set)(x:A), IO (fun h => True) (fun y old h => y=x /\ h=old)
(fun e old h => False)Implicit Arguments ret[A].
throw : forall (A:Set)(x:exn), IO (fun h => True) (fun y old h => False) (fun e old h => e=x /\ h=old)
Reading a Location
read : forall (A:Set)(x:loc), IO (fun h => exists v:A,mapsto h x v) (fun y old h => old = h /\ mapsto h x v) (fun e old h => False)
wheremapsto(A:Set)(h:heap)(x:loc)(v:A) := (h x) = Some(mkDynamic {A;v}}
Writing a Location
write : forall (A:Set)(x:loc)(v:A), IO (fun h => exists B, exists w:B, mapsto h x w) (fun y old h => y = tt /\
h = update old x A v) (fun e old h => False)
Implicit Arguments write[A]. whereupdate(h:heap)(x:loc)(A:Set)(v:A):heap :=
fun y => if (eq_loc x y) then Some(Dynamic{A,v})
else h y
Bind
bind : forall (A B:Set)(P1:Pre)(Q1:Post A)(E1:Post exn)
(P2:A->Pre)(Q2:A->Post B)(E2:A->Post exn), (IO A P1 Q1 E1) -> (A -> IO B P2 Q2 E2) ->
IO B (fun h => P1 h /\ (forall x m,(Q1 x h m) -> P2 m)) (fun y old m => exists x m, (Q1 x old m) /\ (Q2 y m h))
(fun e old m => (E1 e old m) \/ (exists x m, (Q1 x old m) /\ (E2 e m h)))Implicit Arguments bind [A B P1 Q1 E1 P2 Q2 E2].
Using Bind
Definition readThen := fun (A B:Set)(x:loc) (p:A->pre)(q:A->post B) (e:A->post exn) (c:forall y:A, IO (p y) (q y) (e y))=> bind (read A x) c.
Implicit Arguments readThen [A B p q e].
Example:
Definition swap :=
fun (A B:Set)(x y:loc) =>
(readThen x
(fun (xv:A) => readThen y
(fun (yv:B) => writeThen x yv
(writeThen y xv
(ret tt))))).
Type Inferred for Swapforall (A B : Set) (x y : loc 1), IO
(fun i : heap => (fun i0 : heap => exists v : A, mapsto i0 x v) i /\ (forall (x0 : A) (m : heap), (fun (y0 : A) (i0 m0 : heap) => mapsto i0 x y0 /\ m0 = i0) x0 i m -> (fun (xv : A) (i0 : heap) => (fun i1 : heap => exists v : B, mapsto i1 y v) i0 /\ (forall (x1 : B) (m0 : heap), (fun (y0 : B) (i1 m1 : heap) => mapsto i1 y y0 /\ m1 = i1) x1 i0 m0 -> (fun (yv : B) (i1 : heap) => (fun i2 : heap => exists B0 : Set, exists z : vector B0 1, mapsto_vec i2 x z) i1 /\ (forall (x2 : unit) (m1 : heap), (fun (_ : unit) (i2 m2 : heap) => m2 = update i2 x yv) x2 i1 m1 -> (fun (_ : unit) (i2 : heap) => (fun i3 : heap => exists B0 : Set, exists z : vector B0 1, mapsto_vec i3 y z) i2 /\ (forall (x3 : unit) (m2 : heap), (fun (_ : unit) (i3 m3 : heap) => m3 = update i3 y xv) x3 i2 m2 -> (fun _ : unit => nopre) x3 m2)) x2 m1)) x1 m0)) x0 m))
pre-conditiononly!
Do
do : forall (A:Set)(P1:Pre)(Q1:Post A)(E1:Post exn) (P2:Pre)(Q2:Post A)(E2:Post exn), (IO A P1 Q1 E1) -> (forall h,(P2 h) -> (P1 h)) -> (forall y old m, (p2 old) -> (Q1 y old m) -> (Q2 y old m)) -> (forall e old m, (p2 old) -> (E1 y old m) -> (E2 y old m)) -> IO A P2 Q2 E2.Implicit Arguments do [A P1 Q1 E1].
Essentially, the rule of consequence.
Ascribing a Spec to Swap
Program Definition swap_precise : forall (A B:Set)(x y:loc 1), IO (fun i => exists vx:A, exists vy:B,
mapsto i x vx /\ mapsto i y vy) (fun (_:unit) i m => exists vx:A, exists vy:B,
m = update (update i x vy) y vx) (fun _ _ _ => False) := fun A B x y => do (swap A B x y) _.
Followed by a long proof.(can be shortened with combination of key lemmas and tactics.)
Another Example:
Definition InvIO(A:Set)(I:Pre)
:= IO I (fun (_:A) _ m => I m) (fun (_:exn) _ m => I m).
Program Fixpoint mapIO(A B:Set)(I:pre)
(f:A -> B -> InvIO B I
(acc:B)(x:list A) {struct x} :
InvIO B I :=
match x with
| nil => do (ret acc) _
| cons h t =>
do (bind (f h acc)
(fun acc2 => mapIO A B p q e pf f acc2 t)) _
end.
Advantages
• For pure code:– Can use refinements a la DML/ATS– Or, can reason after the fact
• E.g., can prove append associative without having to tie it into the definition.
• Modeling language is serious– e.g., heaps are defined in the model.
• Abstraction over values, types, specifications, and proofs (i.e., compositional!)
• If you stick to simple types, no proofs.
Key Open Issues
• Proofs are still painful.– Need to adapt automation from ESC
• Need analogues to object invariants, ownership, etc. for mutable ADTs.– Separation logic seems promising (next time).
• IO and other effects– Need pre/post over worlds (heaps are just a part.)
• Better models?– Predicate transformers seem promising– Rasmus & Lars working on denotational model