Abstract Refinements {#data} ============================ Recap ----- **So far** Decouple invariants from *functions*
**Next** Decouple invariants from *data structures*
Decouple Invariants From Data {#vector} ======================================= Example: Vectors ----------------
Implemented as maps from `Int` to `a`
\begin{code} data Vec a = V (Int -> a) \end{code}
Abstract *Domain* and *Range* ----------------------------- Parameterize type with *two* abstract refinements:
\begin{code} {-@ data Vec a < dom :: Int -> Prop, rng :: Int -> a -> Prop > = V {a :: i:Int -> a } @-} \end{code}
- `dom`: *domain* on which `Vec` is *defined* - `rng`: *range* and relationship with *index* Abstract *Domain* and *Range* ----------------------------- Diverse `Vec`tors by *instantiating* `dom` and `rng`
A quick alias for *segments* between `I` and `J`
\begin{code} {-@ predicate Seg V I J = (I <= V && V < J) @-} \end{code}
Ex: Identity Vectors -------------------- Defined between `[0..N)` mapping each key to itself:
\begin{code} {-@ type IdVec N = Vec <{\v -> (Seg v 0 N)}, {\k v -> v=k}> Int @-} \end{code}
Ex: Identity Vectors -------------------- Defined between `[0..N)` mapping each key to itself:
\begin{code} {-@ idv :: n:Nat -> (IdVec n) @-} idv n = V (\k -> if 0 < k && k < n then k else liquidError "eeks") \end{code}
Demo:Whats the problem? How can we fix it?
Ex: Zero-Terminated Vectors --------------------------- Defined between `[0..N)`, with *last* element equal to `0`:
\begin{code} {-@ type ZeroTerm N = Vec <{\v -> (Seg v 0 N)}, {\k v -> (k = N-1 => v = 0)}> Int @-} \end{code}
Ex: Fibonacci Table ------------------- A vector whose value at index `k` is either - `0` (undefined), or, - `k`th fibonacci number \begin{code} {-@ type FibV = Vec <{\v -> true}, {\k v -> (v = 0 || v = (fib k))}> Int @-} \end{code} Accessing Vectors ----------------- Next: lets *abstractly* type `Vec`tor operations, *e.g.*
- `empty` - `set` - `get` Ex: Empty Vectors ----------------- `empty` returns Vector whose domain is `false`
\begin{code} {-@ empty :: forall

a -> Prop>. Vec <{v:Int|false}, p> a @-} empty = V $ \_ -> error "empty vector!" \end{code}

Demo: What would happen if we changed `false` to `true`?
Ex: `get` Key's Value --------------------- - *Input* `key` in *domain* - *Output* value in *range* related with `k` \begin{code} {-@ get :: forall a Prop, r :: Int -> a -> Prop>. key:Int -> vec:Vec a -> a @-} get k (V f) = f k \end{code} Ex: `set` Key's Value --------------------- -
Input `key` in *domain*
-
Input `val` in *range* related with `key`
-
Input `vec` defined at *domain except at* `key`
-
Output domain *includes* `key`
Ex: `set` Key's Value --------------------- \begin{code} {-@ set :: forall a Prop, r :: Int -> a -> Prop>. key: Int -> val: a -> vec: Vec<{v:Int| v /= key},r> a -> Vec a @-} set key val (V f) = V $ \k -> if k == key then val else f key \end{code}
Demo: Help! Can you spot and fix the errors?
Using the Vector API -------------------- Memoized Fibonacci ------------------ Use `Vec` API to write a *memoized* fibonacci function
\begin{code} Using the fibonacci table: type FibV = Vec <{\v -> true}, {\k v -> (v = 0 || v = (fib k))}> Int \end{code}

But wait, what is `fib` ?
Specifying Fibonacci -------------------- `fib` is *uninterpreted* in the refinement logic
\begin{code} {-@ measure fib :: Int -> Int @-} \end{code}
Specifying Fibonacci -------------------- We *axiomatize* the definition of `fib` in SMT ... \begin{code}
predicate AxFib I = (fib I) == if I <= 1 then 1 else fib(I-1) + fib(I-2) \end{code} Specifying Fibonacci -------------------- Finally, lift axiom into LiquidHaskell as *ghost function*
\begin{code} {-@ axiom_fib :: i:_ -> {v:_|((Prop v) <=> (AxFib i))} @-} \end{code}
**Note:** Recipe for *escaping* SMT limitations 1. *Prove* fact externally 2. *Use* as ghost function call
Fast Fibonacci -------------- An efficient fibonacci function
\begin{code} {-@ fastFib :: n:Int -> {v:_ | v = (fib n)} @-} fastFib n = snd $ fibMemo (V (\_ -> 0)) n \end{code}
- `fibMemo` *takes* a table initialized with `0` - `fibMemo` *returns* a table with `fib` values upto `n`.
Memoized Fibonacci ------------------ \begin{code} fibMemo t i | i <= 1 = (t, liquidAssume (axiom_fib i) 1) | otherwise = case get i t of 0 -> let (t1,n1) = fibMemo t (i-1) (t2,n2) = fibMemo t1 (i-2) n = liquidAssume (axiom_fib i) (n1+n2) in (set i n t2, n) n -> (t, n) \end{code} Memoized Fibonacci ------------------ - `fibMemo` *takes* a table initialized with `0` - `fibMemo` *returns* a table with `fib` values upto `n`.
\begin{code} {-@ fibMemo :: FibV -> i:Int -> (FibV,{v:Int | v = (fib i)}) @-} \end{code} Recap ----- Created a `Vec` container Decoupled *domain* and *range* invariants from *data*
Previous, special purpose program analyses - [Gopan-Reps-Sagiv, POPL 05](link) - [J.-McMillan, CAV 07](link) - [Logozzo-Cousot-Cousot, POPL 11](link) - [Dillig-Dillig, POPL 12](link) - ... Encoded as instance of abstract refinement types!