Abstract Refinements
---------------------
\begin{code}
module AbstractRefinements where
import Prelude hiding (max)
import Language.Haskell.Liquid.Prelude
{-@ LIQUID "--no-termination" @-}
\end{code}
Polymorphic Max Function
-----------------------
\begin{code} Consinder a polymorphic max function:
max :: a -> a -> a
max x y = if x >= y then x else y
\end{code}
\begin{code} We can instantiate `a` with `Odd`
max :: Odd -> Odd -> Odd
maxOdd :: Odd
maxOdd = max 3 7
\end{code}
Polymorphic Max in Haskell
-----------------------
\begin{code} In Haskell the type of max is
max :: Ord a => a -> a -> a
\end{code}
We could **ignore** the class constraints, and procced as before:
\begin{code} Instantiate `a` with `Odd`
max :: Odd -> Odd -> Odd
maxOdd :: Odd
maxOdd = max 3 7
\end{code}
Polymorphic Add in Haskell
-----------------------
\begin{code} But this can lead to **unsoundness**:
max :: Ord a => a -> a -> a
(+) :: Num a => a -> a -> a
\end{code}
So, **ignoring** class constraints allows us to:
\begin{code} instantiate `a` with `Odd`
(+) :: Odd -> Odd -> Odd
addOdd :: Odd
addOdd = 3 + 7
\end{code}
Polymorphism via Parametric Invariants
--------------------------------------
`max` returns *one of* its two inputs `x` and `y`.
- **If** *both inputs* satisfy a property
- **Then** *output* must satisfy that property
This holds, **regardless of what that property was!**
- That is, we can **abstract over refinements**
- Or, **parameterize** a type over its refinements.
Parametric Invariants
---------------------
\begin{code}
{-@ max :: forall
Prop>. Ord a => a
-> a
-> a
@-} max :: Ord a => a -> a -> a max x y = if x <= y then y else x \end{code} Where - `a
` is just an abbreviation for `{v:a | (p v)}`
This type states explicitly:
- **For any property** `p`, that is a property of `a`,
- `max` takes two **inputs** of which satisfy `p`,
- `max` returns an **output** that satisfies `p`.
Using Abstract Refinements
--------------------------
- **If** we call `max` with two arguments with the same concrete refinement,
- **Then** the `p` will be instantiated with that concrete refinement,
- **The output** of the call will also enjoy the concrete refinement.
\begin{code}
{-@ type Odd = {v:Int | (v mod 2) = 1} @-}
{-@ maxOdd :: Odd @-}
maxOdd :: Int
maxOdd = max 3 5
\end{code}
Abstract Refinements in Type Constructors
-----------------------------------------
Types cannot track information of monomorphic arguments:
\begin{code}
data F = F {w::Int}
\end{code}
The type `F` cannot give us information about the field `x`.
\begin{code}
foo = let f = F 0 in -- :: f :: F
case f of
F x -> liquidAssert (x >= 0)
\end{code}
Demo:
Lets solve this error using Abstract Refinements
Abstract Refinements in Type Constructors
-----------------------------------------
- Abstract over the refinement you care
\begin{code}
data G = G {y::Int{-
-}} \end{code} - Move it to the left-hand side \begin{code} {-@ data G
Prop> = G (y::Int
) @-} \end{code} - The type `G
` now describes the field `x`. \begin{code} bar = let f = G 0 in -- :: f :: G <{v = 0}> case f of G x -> liquidAssert (x >= 0) \end{code} Abstract Refinements in Lists ----------------------------------------- \begin{code} Remember increasing Lists? data IL a = N | C (x :: a) (xs :: L {v:a | x <= v}) \end{code} - Abstract over the refinement you care \begin{code} data L a = N | C {x :: a, xs :: L a {- v:a | p v x -}} \end{code} - Move it to the left-hand side \begin{code} {-@ data L a
a -> Prop> = N | C (x :: a) (xs :: L
a
) @-}
\end{code}
We can get back increasing Lists:
\begin{code}
{-@ type IncrL a = L <{\x v -> x <= v}> a @-}
\end{code}
Multiple Instantiations
-----------------------
\begin{code} Now increasing lists
type IncrL a = L <{\x v -> x <= v}> a
\end{code}
\begin{code} Co-exist with decreasing ones
type DecrL a = L <{\x v -> x >= v}> a
\end{code}
Ghc Sort
--------
We can now verify algorithms that use **both** increasing and decreasing lists
\begin{code}
{-@ type OList a = [a]<{\hd v -> hd <= v}> @-}
{-@ sort :: (Ord a) => [a] -> OList a @-}
sort :: (Ord a) => [a] -> [a]
sort = mergeAll . sequences
where
sequences (a:b:xs)
| a `compare` b == GT = descending b [a] xs
| otherwise = ascending b (a:) xs
sequences [x] = [[x]]
sequences [] = [[]]
descending a as (b:bs)
| a `compare` b == GT = descending b (a:as) bs
descending a as bs = (a:as): sequences bs
ascending a as (b:bs)
| a `compare` b /= GT = ascending b (\ys -> as (a:ys)) bs
ascending a as bs = as [a]: sequences bs
\end{code}
Ghc Sort : Helper Functions
---------------------------
\begin{code}
mergeAll [x] = x
mergeAll xs = mergeAll (mergePairs xs)
mergePairs (a:b:xs) = merge1 a b: mergePairs xs
mergePairs [x] = [x]
mergePairs [] = []
merge1 (a:as') (b:bs')
| a `compare` b == GT = b:merge1 (a:as') bs'
| otherwise = a:merge1 as' (b:bs')
merge1 [] bs = bs
merge1 as [] = as
\end{code}