**1.**Introduction {#intro}**1.1.**Well-Typed Programs Do Go Wrong {#gowrong}**1.2.**Refinement Types**1.3.**Audience**1.4.**Getting Started**1.5.**Sample Code**2.**Logic & SMT**2.1.**Syntax**2.2.**Semantics {#semantics}**2.3.**Verification Conditions**2.4.**Examples: Propositions**2.5.**Examples: Arithmetic**2.6.**Examples: Uninterpreted Function**2.7.**Recap**3.**Refinement Types**3.1.**Defining Types {#definetype}**3.2.**Errors**3.3.**Subtyping**3.4.**Writing Specifications**3.5.**Refining Function Types: Pre-conditions**3.6.**Refining Function Types: Post-conditions**3.7.**Testing Values: Booleans and Propositions {#propositions}**3.8.**Putting It All Together**3.9.**Recap**4.**Polymorphism {#polymorphism}**4.1.**Specification: Vector Bounds {#vectorbounds}**4.2.**Verification: Vector Lookup**4.3.**Inference: Our First Recursive Function**4.4.**Higher-Order Functions: Bottling Recursion in a`loop`

**4.5.**Refinements and Polymorphism {#sparsetype}**4.6.**Recap**5.**Refined Datatypes {#refineddatatypes}**5.1.**Sparse Vectors Revisited {#autosmart}**5.2.**Ordered Lists {#orderedlists}**5.3.**Ordered Trees {#binarysearchtree}**5.4.**Recap**6.**Boolean Measures {#boolmeasures}**6.1.**Partial Functions**6.2.**Lifting Functions to Measures {#usingmeasures}**6.3.**A Safe List API**6.4.**Recap**7.**Numeric Measures {#numericmeasure}**7.1.**Wholemeal Programming**7.2.**Specifying List Dimensions**7.3.**Lists: Size Preserving API**7.4.**Lists: Size Reducing API {#listreducing}**7.5.**Dimension Safe Vector API**7.6.**Dimension Safe Matrix API**7.7.**Recap**8.**Elemental Measures {#setmeasure}**8.1.**Talking about Sets**8.2.**Proving QuickCheck Style Properties {#quickcheck}**8.3.**Content-Aware List API {#listelems}**8.4.**Permutations**8.5.**Uniqueness**8.6.**Unique Zippers**8.7.**Recap**9.**Case Study: Okasaki's Lazy Queues {#lazyqueue}**10.**Case Study: Associative Maps**10.1.**Specifying Maps {#mapapi}**10.2.**Using Maps: Well Scoped Expressions**10.3.**Implementing Maps: Binary Search Trees {#lemmas}**10.4.**Recap**11.**Case Study: Pointers & Bytes {#case-study-pointers}**11.1.**HeartBleeds in Haskell**11.2.**Low-level Pointer API**11.3.**A Refined Pointer API**11.4.**Assumptions vs Guarantees**11.5.**ByteString API**11.6.**Application API**11.7.**Nested ByteStrings**11.8.**Recap: Types Against Overflows**12.**Case Study: AVL Trees {#case-study-avltree}

{-@ LIQUID "--short-names" @-}
{-@ LIQUID "--no-termination" @-}
module Tutorial_05_Datatypes
(
-- * Sparse: Data
Sparse (..)
-- * Sparse: Functions
, dotProd, dotProd', plus, fromList
-- * Sparse: Examples
, okSP, badSP, test1, test2
-- * OrdList: Data
, IncList (..)
-- * OrdList: Examples
, okList, badList
-- * OrdList: Functions
, insertSort, insertSort', mergeSort, quickSort
-- * BST: Data
, BST (..)
-- * BST: Functions
, mem, add, delMin, del, bstSort, toBST, toIncList
-- * BST: Examples
, okBST, badBST
)
where
import Prelude hiding (abs, length, min)
import Data.List (foldl')
import Data.Vector hiding (singleton, foldl', foldr, fromList, (++))
import Data.Maybe (fromJust)
dotProd, dotProd' :: Vector Int -> Sparse Int -> Int
test1 :: Sparse String
test2 :: Sparse Int
{-@ die :: {v:_ | false} -> a @-}
die msg = error msg
-- {-@ fail badSP @-}
-- {-@ fail test1 @-}
-- {-@ fail test2 @-}
-- {-@ fail badList @-}
-- {-@ ignore append @-}
-- {-@ fail badBST @-}
-- {-@ ignore delMin @-}

So far, we have seen how to refine the types of *functions*, to specify, for example, pre-conditions on the inputs, or post-conditions on the outputs. Very often, we wish to define *datatypes* that satisfy certain invariants. In these cases, it is handy to be able to directly refine the `data`

definition, making it impossible to create illegal inhabitants.

As our first example of a refined datatype, let’s revisit the sparse vector representation that we saw earlier. The `SparseN`

type alias we used got the job done, but is not pleasant to work with because we have no way of determining the *dimension* of the sparse vector. Instead, let’s create a new datatype to represent such vectors:

data Sparse a = SP { spDim :: Int
, spElems :: [(Int, a)] }

Thus, a sparse vector is a pair of a dimension and a list of index-value tuples. Implicitly, all indices *other* than those in the list have the value `0`

or the equivalent value type `a`

.

`Sparse`

vectors satisfy two crucial properties. First, the dimension stored in `spDim`

is non-negative. Second, every index in `spElems`

must be valid, i.e. between `0`

and the dimension. Unfortunately, Haskell’s type system does not make it easy to ensure that *illegal vectors are not representable*.^{1}

**Data Invariants** LiquidHaskell lets us enforce these invariants with a refined data definition:

{-@ data Sparse a = SP { spDim :: Nat
, spElems :: [(Btwn 0 spDim, a)]} @-}

Where, as before, we use the aliases:

{-@ type Nat = {v:Int | 0 <= v} @-}
{-@ type Btwn Lo Hi = {v:Int | Lo <= v && v < Hi} @-}

**Refined Data Constructors** The refined data definition is internally converted into refined types for the data constructor `SP`

:

```
-- Generated Internal representation
data Sparse a where
SP :: spDim:Nat
-> spElems:[(Btwn 0 spDim, a)]
-> Sparse a
```

In other words, by using refined input types for `SP`

we have automatically converted it into a *smart* constructor that ensures that *every* instance of a `Sparse`

is legal. Consequently, LiquidHaskell verifies:

okSP :: Sparse String
okSP = SP 5 [ (0, "cat")
, (3, "dog") ]

but rejects, due to the invalid index:

badSP :: Sparse String
badSP = SP 5 [ (0, "cat")
, (6, "dog") ]

**Field Measures** It is convenient to write an alias for sparse vectors of a given size `N`

. We can use the field name `spDim`

as a *measure*, like `vlen`

. That is, we can use `spDim`

inside refinements^{2}

{-@ type SparseN a N = {v:Sparse a | spDim v == N} @-}

Let’s write a function to compute a sparse product

{-@ dotProd :: x:Vector Int -> SparseN Int (vlen x) -> Int @-}
dotProd x (SP _ y) = go 0 y
where
go sum ((i, v) : y') = go (sum + (x ! i) * v) y'
go sum [] = sum

LiquidHaskell verifies the above by using the specification to conclude that for each tuple `(i, v)`

in the list `y`

, the value of `i`

is within the bounds of the vector `x`

, thereby proving `x ! i`

safe.

**Folded Product** We can port the `fold`

-based product to our new representation:

{-@ dotProd' :: x:Vector Int -> SparseN Int (vlen x) -> Int @-}
dotProd' x (SP _ y) = foldl' body 0 y
where
body sum (i, v) = sum + (x ! i) * v

As before, LiquidHaskell checks the above by automatically instantiating refinements for the type parameters of `foldl'`

, saving us a fair bit of typing and enabling the use of the elegant polymorphic, higher-order combinators we know and love.

**Exercise: (Sanitization): ** Invariants are all well and good for data computed *inside* our programs. The only way to ensure the legality of data coming from *outside*, i.e. from the “real world”, is to write a sanitizer that will check the appropriate invariants before constructing a `Sparse`

vector. Write the specification and implementation of a sanitizer `fromList`

, so that the following typechecks:

**Hint: **You need to check that *all* the indices in `elts`

are less than `dim`

; the easiest way is to compute a new `Maybe [(Int, a)]`

which is `Just`

the original pairs if they are valid, and `Nothing`

otherwise.

fromList :: Int -> [(Int, a)] -> Maybe (Sparse a)
fromList dim elts = undefined
{-@ test1 :: SparseN String 3 @-}
test1 = fromJust $ fromList 3 [(0, "cat"), (2, "mouse")]

**Exercise: (Addition): **Write the specification and implementation of a function `plus`

that performs the addition of two `Sparse`

vectors of the *same* dimension, yielding an output of that dimension. When you are done, the following code should typecheck:

plus :: (Num a) => Sparse a -> Sparse a -> Sparse a
plus x y = undefined
{-@ test2 :: SparseN Int 3 @-}
test2 = plus vec1 vec2
where
vec1 = SP 3 [(0, 12), (2, 9)]
vec2 = SP 3 [(0, 8), (1, 100)]

As a second example of refined data types, let’s consider a different problem: representing *ordered* sequences. Here’s a type for sequences that mimics the classical list:

data IncList a =
Emp
| (:<) { hd :: a, tl :: IncList a }
infixr 9 :<

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`

:

{-@ data IncList a =
Emp
| (:<) { hd :: a, tl :: IncList {v:a | hd <= v}} @-}

**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.

okList = 1 :< 2 :< 3 :< Emp -- accepted by LH
badList = 2 :< 1 :< 3 :< Emp -- rejected by LH

It’s all very well to *specify* ordered lists. Next, lets see how it’s equally easy to *establish* these invariants by implementing several textbook sorting routines.

First, lets implement insertion sort, which converts an ordinary list `[a]`

into an ordered list `IncList a`

.

insertSort :: (Ord a) => [a] -> IncList a
insertSort [] = Emp
insertSort (x:xs) = insert x (insertSort xs)

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.

insert :: (Ord a) => a -> IncList a -> IncList a
insert y Emp = y :< Emp
insert y (x :< xs)
| y <= x = y :< x :< xs
| otherwise = x :< insert y xs

**Exercise: (Insertion Sort): **Complete the implementation of the function below to use `foldr`

to eliminate the explicit recursion in `insertSort`

.

insertSort' :: (Ord a) => [a] -> IncList a
insertSort' xs = foldr f b xs
where
f = undefined -- Fill this in
b = undefined -- Fill this in

**Merge Sort** Similarly, it is easy to write merge sort, by implementing the three steps. First, we write a function that *splits* the input into two equal sized halves:

split :: [a] -> ([a], [a])
split (x:y:zs) = (x:xs, y:ys)
where
(xs, ys) = split zs
split xs = (xs, [])

Second, we need a function that *combines* two ordered lists

merge :: (Ord a) => IncList a -> IncList a -> IncList a
merge xs Emp = xs
merge Emp ys = ys
merge (x :< xs) (y :< ys)
| x <= y = x :< merge xs (y :< ys)
| otherwise = y :< merge (x :< xs) ys
merge _ _ = Emp

Finally, we compose the above steps to divide (i.e. `split`

) and conquer (`sort`

and `merge`

) the input list:

mergeSort :: (Ord a) => [a] -> IncList a
mergeSort [] = Emp
mergeSort [x] = x :< Emp
mergeSort xs = merge (mergeSort ys) (mergeSort zs)
where
(ys, zs) = split xs

**Exercise: (QuickSort): ** Why is the following implementation of `quickSort`

rejected by LiquidHaskell? Modify it so it is accepted.

**Hint: **Think about how `append`

should behave so that the `quickSort`

has the desired property. That is, suppose that `ys`

and `zs`

are already in *increasing order*. Does that mean that `append x ys zs`

are *also* in increasing order? No! What other requirement do you need? bottle that intuition into a suitable *specification* for `append`

and then ensure that the code satisfies that specification.

quickSort :: (Ord a) => [a] -> IncList a
quickSort [] = Emp
quickSort (x:xs) = append x lessers greaters
where
lessers = quickSort [y | y <- xs, y < x ]
greaters = quickSort [z | z <- xs, z >= x]
{-@ append :: x:a -> IncList a
-> IncList a
-> IncList a
@-}
append z Emp ys = z :< ys
append z (x :< xs) ys = x :< append z xs ys

As a last example of refined data types, let us consider binary search ordered trees, defined thus:

data BST a = Leaf
| Node { root :: a
, left :: BST a
, right :: BST a }

enjoy the property that each `root`

lies (strictly) between the elements belonging in the `left`

and `right`

subtrees hanging off the root. The ordering invariant makes it easy to check whether a certain value occurs in the tree. If the tree is empty i.e. a `Leaf`

, then the value does not occur in the tree. If the given value is at the root then the value does occur in the tree. If it is less than (respectively greater than) the root, we recursively check whether the value occurs in the left (respectively right) subtree.

Figure 1.1 shows a binary search tree whose nodes are labeled with a subset of values from `1`

to `9`

. We might represent such a tree with the Haskell value:

okBST :: BST Int
okBST = Node 6
(Node 2
(Node 1 Leaf Leaf)
(Node 4 Leaf Leaf))
(Node 9
(Node 7 Leaf Leaf)
Leaf)

**Refined Data Type** The Haskell type says nothing about the ordering invariant, and hence, cannot prevent us from creating illegal `BST`

values that violate the invariant. We can remedy this with a refined data definition that captures the invariant. The aliases `BSTL`

and `BSTR`

denote `BST`

s with values less than and greater than some `X`

, respectively.^{3}

{-@ data BST a = Leaf
| Node { root :: a
, left :: BSTL a root
, right :: BSTR a root } @-}
{-@ type BSTL a X = BST {v:a | v < X} @-}
{-@ type BSTR a X = BST {v:a | X < v} @-}

**Refined Data Constructors** As before, the above data definition creates a refined smart constructor for `BST`

```
data BST a where
Leaf :: BST a
Node :: r:a -> BST {v:a| v < r}
-> BST {v:a | r < v}
-> BST a
```

which *prevents* us from creating illegal trees

badBST = Node 66
(Node 4
(Node 1 Leaf Leaf)
(Node 69 Leaf Leaf)) -- Out of order, rejected
(Node 99
(Node 77 Leaf Leaf)
Leaf)

**Exercise: (Duplicates): **Can a `BST Int`

contain duplicates?

Lets write some functions to create and manipulate these trees. First, a function to check whether a value is in a `BST`

:

mem :: (Ord a) => a -> BST a -> Bool
mem _ Leaf = False
mem k (Node k' l r)
| k == k' = True
| k < k' = mem k l
| otherwise = mem k r

**Singleton** Next, another easy warm-up: a function to create a `BST`

with a single given element:

one :: a -> BST a
one x = Node x Leaf Leaf

**Insertion** Lets write a function that adds an element to a `BST`

.^{4}

add :: (Ord a) => a -> BST a -> BST a
add k' Leaf = one k'
add k' t@(Node k l r)
| k' < k = Node k (add k' l) r
| k < k' = Node k l (add k' r)
| otherwise = t

**Minimum** For our next trick, lets write a function to delete the *minimum* element from a `BST`

. This function will return a *pair* of outputs – the smallest element and the remainder of the tree. We can say that the output element is indeed the smallest, by saying that the remainder’s elements exceed the element. To this end, lets define a helper type: ^{5}

data MinPair a = MP { mElt :: a, rest :: BST a }

We can specify that `mElt`

is indeed smaller than all the elements in `rest`

via the data type refinement:

{-@ data MinPair a = MP { mElt :: a, rest :: BSTR a mElt} @-}

Finally, we can write the code to compute `MinPair`

delMin :: (Ord a) => BST a -> MinPair a
delMin (Node k Leaf r) = MP k r
delMin (Node k l r) = MP k' (Node k l' r)
where
MP k' l' = delMin l
delMin Leaf = die "Don't say I didn't warn ya!"

**Exercise: (Delete): **Use `delMin`

to complete the implementation of `del`

which *deletes* a given element from a `BST`

, if it is present.

del :: (Ord a) => a -> BST a -> BST a
del k' t@(Node k l r) = undefined
del _ Leaf = Leaf

**Exercise: (Safely Deleting Minimum): ** The function `delMin`

is only sensible for non-empty trees. Read ahead to learn how to specify and verify that it is only called with such trees, and then apply that technique here to verify the call to `die`

in `delMin`

.

**Exercise: (BST Sort): **Complete the implementation of `toIncList`

to obtain a `BST`

based sorting routine `bstSort`

.

bstSort :: (Ord a) => [a] -> IncList a
bstSort = toIncList . toBST
toBST :: (Ord a) => [a] -> BST a
toBST = foldr add Leaf
toIncList :: BST a -> IncList a
toIncList (Node x l r) = undefined
toIncList Leaf = undefined

**Hint: **This exercise will be a lot easier *after* you finish the `quickSort`

exercise. Note that the signature for `toIncList`

does not use `Ord`

and so you *cannot* (and *need not*) use a sorting procedure to implement it.

In this chapter we saw how LiquidHaskell lets you refine data type definitions to capture sophisticated invariants. These definitions are internally represented by refining the types of the data constructors, automatically making them “smart” in that they preclude the creation of illegal values that violate the invariants. We will see much more of this handy technique in future chapters.

One recurring theme in this chapter was that we had to create new versions of standard datatypes, just in order to specify certain invariants. For example, we had to write a special list type, with its own *copies* of nil and cons. Similarly, to implement `delMin`

we had to create our own pair type.

**This duplication** of types is quite tedious. There should be a way to just slap the desired invariants on to *existing* types, thereby facilitating their reuse. In a few chapters, we will see how to achieve this reuse by abstracting refinements from the definitions of datatypes or functions in the same way we abstract the element type `a`

from containers like `[a]`

or `BST a`

.

The standard approach is to use abstract types and smart constructors but even then there is only the informal guarantee that the smart constructor establishes the right invariants.↩︎

Note that

*inside*a refined`data`

definition, a field name like`spDim`

refers to the value of the field, but*outside*it refers to the field selector measure or function.↩︎We could also just

*inline*the definitions of`BSTL`

and`BSTR`

into that of`BST`

but they will be handy later.↩︎While writing this exercise I inadvertently swapped the

`k`

and`k'`

which caused LiquidHaskell to protest.↩︎This helper type approach is rather verbose. We should be able to just use plain old pairs and specify the above requirement as a

*dependency*between the pairs’ elements. Later, we will see how to do so using abstract refinements.↩︎