sum
and psum
mRTheorem
mRTheorem
on plusLast time we saw that we can use refinement types on the data type definitions to specify invariants about the data types. Today we will use this invariant to specify sortedness of lists.
Here’s a type for sequences that mimics the classical list:
The Haskell type above does not state that the elements are in order of course, but we can specify that requirement by refining every element in tl
to be greater than hd
:
Refined Data Constructors Once again, the refined data definition is internally converted into a “smart” refined data constructor
-- Generated Internal representation
data IncList a where
Emp :: IncList a
(:<) :: hd:a -> tl:IncList {v:a | hd <= v} -> IncList a
which ensures that we can only create legal ordered lists.
It’s all very well to specify ordered lists. Next, let’s see how it’s equally easy to establish these invariants by implementing several textbook sorting routines.
First, let’s implement insertion sort, which converts an ordinary list [a]
into an ordered list IncList a
.
The hard work is done by insert
which places an element into the correct position of a sorted list. LiquidHaskell infers that if you give insert
an element and a sorted list, it returns a sorted list.
Question: What should be the definition of insert
?
Ideally, we would like to write a verified sorted function that works over Haskell’s lists. Of course, we cannot constraint all lists to be sorted. But, we can abstract the notion of sortedness over the data declaration.
Thus, instead of explicitly stating that the head should be less that the element of the tail:
{-@ data IncList a =
Emp
| (:<) { hd :: a, tl :: IncList {v:a | hd <= v}} @-}
We say that there exists a predicate p
that relates the head and all elements of the tail:
Refined Data Constructors The internal data constructors are refined to be parametric with respect to the predicate p
:
-- Generated Internal representation
data PncList a where
Nil :: IncList a
Cons :: forall a, p. hd:a -> tl:PList <p> {v:a | p hd} -> PList <p> a
Now this abstract predicate can be instantiated with different properties:
Question: Give refinement types to the above four lists.
So, now the same Haskell lists can be refined to be either decreasing, increasing or equal. For example, the code below inserts into an increasing list:
Question: Can you adjust it to insert
fointor decreasing lists?
Liquid Haskell comes by default with parametrized lists. So we can instantiate the refinements over Haskell’s lists directly.
With these definitions, we can verify insertion of elements into Haskell’s lists:
Question: Let’s also check if isort
preserves the len
of the list.
This abstraction, called Abstract Refinements is very powerful since it allows lists to turn from increasing to decreasing. Because of this flexibility, Liquid Haskell can automatically verify the below code, used as the official sorting function in Haskell, that very smartly sorts lists by collecting increasing and decreasing subsequences and merging them back together.
{-@ sort :: (Ord a) => [a] -> IList a @-}
sort :: (Ord a) => [a] -> [a]
sort xs = mergeAll (sequences xs)
where
sequences :: Ord a => [a] -> [[a]]
sequences (a:b:xs)
| a `compare` b == GT = descending b [a] xs
| otherwise = ascending b (a:) xs
sequences [x] = [[x]]
sequences [] = [[]]
descending :: Ord a => a -> [a] -> [a] -> [[a]]
descending a as (b:bs)
| a `compare` b == GT = descending b (a:as) bs
descending a as bs = (a:as): sequences bs
ascending :: Ord a => a -> ([a] -> [a]) -> [a] -> [[a]]
ascending a as (b:bs)
| a `compare` b /= GT = ascending b (\ys -> as (a:ys)) bs
ascending a as bs = as [a]: sequences bs
mergeAll [] = []
mergeAll [x] = x
mergeAll xs = mergeAll (mergePairs xs)
mergePairs :: Ord a => [[a]] -> [[a]]
mergePairs (a:b:xs) = merge1 a b: mergePairs xs
mergePairs [x] = [x]
mergePairs [] = []
merge1 :: Ord a => [a] -> [a] -> [a]
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
Liquid Haskell has one more build in abstraction for pairs. So, internally the pairs are representing using abstract refinements as :
data Pair a b < p :: a -> b> = P {fst :: a, snd :: b < p fst> }
The generated type is also parametric:
-- Generated Internal representation
(,) :: forall a b <p :: a -> b>. x:a -> b <p x> -> Pair a b <p>
This allows us to specify dependent pairs, where the second element depends on the first:
So, now we can use the syntax of dependent type theory pairs to write interesting properties:
The above code says that for each natural number x
, there exists one y
that is greater than x
, taking us to a theorem proving territory, that we will return soon.
Question: Is this property true for less than too?
We saw three ways to specify sortedness of lists:
The notion of abstract refinements is very powerful. In Liquid Haskell it is also used to encode dependent pairs and can be used in user defined data structures to specify various abstract dependencies.