Enforcing static invariants using Haskell's type checker

Lucian Mogoșanu

27.08.2014

A bit about what I do

Research (UPB/ACS)

A bit about what I do

Development (VirtualMetrix)

A bit about what I do

Teaching assistant (UPB/ACS) for the following courses:

Designing software using Haskell

Haskell is:

Haskell's type system has the following particularities:

Algebraic data types

Every Haskell type is

Example 1: primitive types

3 :: Int
120894192912541294198294982 :: Integer
3.14 :: Float
'a' :: Char
"The cake is a lie" :: String
True :: Bool

Example 2: sum types

Example 2 in C

typedef enum {
    Apples = 1,
    Pears = 2,
    Oranges = 3,
    Tomatoes = 4
} fruit_e;

Now we can say:

fruit_e fr = Tomatoes;

Example 2 in C

typedef enum {
    Apples = 1,
    Pears = 2,
    Oranges = 3,
    Tomatoes = 4
} fruit_e;

But we can also say:

fruit_e fr = 5;

Example 2 in Haskell

data Fruit =
    Apples
  | Pears
  | Oranges
  | Tomatoes

Example 2 in Haskell

data Fruit =
    Apples
  | Pears
  | Oranges
  | Tomatoes
> :t Apples
Apples :: Fruit
> 5 :: Fruit

<interactive>
  No instance for (Num Fruit) ..

Example 2 in Haskell

data Fruit =
    Apples
  | Pears
  | Oranges
  | Tomatoes

Example 3: product types

Example 3

Possible approach: use built-in pairs

> :t (,)
(,) :: a -> b -> (a, b)
> (,) Pears 2 :: (Fruit, Int)
(Pears,2)
> (Pears, 2) -- sugar
(Pears,2)

Example 3

data Bucket = MkBucket Fruit Int
> :t MkBucket
MkBucket :: Fruit -> Int -> Bucket

Example 3

data Bucket a = MkBucket a Int

Example 3

data Bucket a = MkBucket
  { bucketObject  :: a
  , bucketQty     :: Int
  }
> let myBucket = MkBucket Apples 10
> bucketQty myBucket
10

Example 3

data Bucket a = MkBucket
  { bucketObject  :: a
  , bucketQty     :: Int
  }

Example 4: MultiBuckets

> MkBucket (Pears, Apples) 50

Example 4

type MultiBucket a = [Bucket a]

Example 4

type MultiBucket a = [Bucket a]

Extracting all the Apples from a MultiBucket

> let mb = [MkBucket Apples 20,
  MkBucket Pears 30]
> head $
  filter (\ b -> bucketObject b == Apples ) mb
MkBucket {bucketObject = Apples, bucketQty = 20}

Example 4

type MultiBucket a = [Bucket a]

What if there are no apples in the MultiBucket?

> let mb = [MkBucket Pears 30]
> head $
  filter (\ b -> bucketObject b == Apples ) mb
*** Exception: Prelude.head: empty list

Example 5: representing failure

data Maybe a = Nothing | Just a

Example 5

getBucket :: Fruit -> MultiBucket Fruit
                   -> Maybe (Bucket Fruit)
getBucket obj mb = case filter isEqual mb of
  []      -> Nothing
  (x : _) -> Just x
  where isEqual x = bucketObject x == obj

Example 5

> let mb = [MkBucket Pears 30]
> getBucket Apples mb
Nothing
> getBucket Pears mb
Just (MkBucket
  {bucketObject = Pears, bucketQty = 30})

Example 5

We can make getBucket polymorphic:

getBucket :: Eq a => a -> MultiBucket a
                       -> Maybe (Bucket a)

Example 5

> :t (==)
(==) :: Eq a => a -> a -> Bool

Example 6: seL4 kernel objects

 data KernelObject 
     = KOEndpoint  Endpoint
     | KOAEndpoint AsyncEndpoint
     | KOKernelData
     | KOUserData
     | KOTCB       TCB
     | KOCTE       CTE
     | KOArch      ArchKernelObject

Example 6

data TCB = Thread {
        tcbCTable :: CTE,
        tcbVTable :: CTE,
        tcbReply :: CTE,
        tcbCaller :: CTE,
        tcbIPCBufferFrame :: CTE,
        tcbDomain :: Domain,
        tcbState :: ThreadState,
        ..
        tcbFaultHandler :: CPtr,
        tcbIPCBuffer :: VPtr,
        tcbContext :: UserContext }
    deriving Show

Example 7: natural numbers

Buckets can have non-sensical values:

> :t MkBucket Apples (-2)
MkBucket Apples (-2) :: Bucket Fruit

Example 7

Possible approach: smart constructors

newtype Nat1 = Nat1 { fromNat1 :: Int }

Example 7

Possible approach: smart constructors

mkNat :: Int -> Nat1
mkNat n = if n < 0
  then error "Only positive numbers are permitted"
  else Nat1 n

Example 8: more naturals

Possible approach: Peano naturals

data Nat2 = Z | S Nat2
> :t Z -- 0
Z :: Nat2
> :t S $ S $ S Z -- 3
S $ S $ S Z :: Nat2

Example 8

Possible approach: Peano naturals

data Nat2 = Z | S Nat2

Example 9: even more naturals

Possible approach: type-level numbers

data Z
data S n

Digression: kinds

> :k Z
Z :: *
> :k S
S :: * -> *
> :k (S (S Z))
(S (S Z)) :: *

Digression: type classes

class MyClass a where
  myMethod :: a -> Int

instance MyClass Int where
  myMethod = (+ 1) . fromIntegral
> myMethod (42 :: Int)
42

Example 9

First we define cardinalities on type-level naturals

class Card n where

instance Card Z where
instance (Card n) => Card (S n) where

Example 9

Next, we define a class for bound checking

class (Card n) => InBounds n where

instance InBounds (S Z) where
instance InBounds (S (S Z)) where
instance InBounds (S (S (S Z))) where

Example 9

Finally, we define a concrete type and a smart constructor

data MaxThree n = MaxThree
  deriving Show

maxthree :: InBounds n => n -> MaxThree n
maxthree _ = MaxThree

Example 9

Some useful auxiliary functions

incr :: Card n => n -> S n
incr = undefined

d0 = undefined :: Z
d1 = incr d0
d2 = incr d1
d3 = incr d2
d4 = incr d3

Example 9

Testing

> maxthree d4

<interactive>
  No instance for (InBounds Z) ..
> maxthree d1
MaxThree

Example 9: conclusion

Haskell extensions for static checking

Extra example: non-empty lists

head and tail are unsafe

> let xs = []
> head xs
*** Exception: Prelude.head: empty list
> tail xs
*** Exception: Prelude.tail: empty list

Extra example

We can make them safe by encoding non-emptiness in the program logic

data NonEmpty a = a :| [a] -- Data.List.NonEmpty
> let xs = 1 :| [2,3,4]
> -- provably non-empty
> head xs
1

Extra example

Generalization using type-level numbers: mono-traversable

cadr :: MinLen (Succ (Succ nat)) [a] -> a
cadr = ML.head . ML.tailML
> let xs = mlcons 3 $ mlcons 2 $ toMinLenZero []
> cadr xs
2

Extra example

Generalization using type-level numbers: mono-traversable

-- this fails at compile-time!
> let xs = mlcons 2 $ toMinLenZero []
> cadr xs

<interactive>
  Couldn't match type `Zero' with `Succ nat0'
  Expected type: MinLen (Succ (Succ nat0)) .. 
  Actual type: MinLen (Succ Zero) ..