LiquidHaskell

Well then, time to let in the liquid one — as in Terminator 2, where a robot of liquid metal appears. The type Int\mathtt{Int} guarantees the shape but says nothing about the content: that a number is non-zero, that a list is non-empty, that an index stays in bounds. LiquidHaskell hangs logical predicates onto ordinary Haskell types — refinement types — and checks them at compile time with an SMT solver (Z3). The “decorator” annotations live in special comments {-@    @-}\mathtt{\{\text{-}@\;\ldots\;@\text{-}\}}, so the code remains plain Haskell and builds with plain GHC. The decorator syntax:

{v:τp(v)}\{\, v : \tau \mid p(v) \,\}
(2.1)

— “values of type τ\tau for which the predicate pp holds”. The basic example is a division that cannot be called with zero:

{-@ type NonZero = {v:Int | v /= 0} @-}

{-@ safeDiv :: Int -> NonZero -> Int @-}
safeDiv :: Int -> Int -> Int
safeDiv x y = x `div` y

ok  = safeDiv 10 2    -- passes: the solver sees for itself that 2 /= 0
bad = safeDiv 10 0    -- compile error: 0 does not fit into NonZero

Under the hood this is subtyping — a comparison of two refined types, each with its own predicate: pp on the argument, qq on the parameter. The type {v:τp}\{\, v:\tau \mid p \,\} is a subtype of {v:τq}\{\, v:\tau \mid q \,\} when every value satisfying pp satisfies qq as well — and this implication is exactly what the SMT solver checks. For the call safeDiv  10  2\mathtt{safeDiv}\;10\;2 the argument has pp (v=2)\equiv (v = 2) — the singleton type of the literal — the parameter NonZero\mathtt{NonZero} has qq (v0)\equiv (v \neq 0), and the implication vv =2= 2 v\Rightarrow v 0\neq 0 holds:

v.  p(v)q(v){v:τp}    {v:τq}\frac{\forall v.\; p(v) \Rightarrow q(v)}{\{\, v:\tau \mid p \,\} \;\preceq\; \{\, v:\tau \mid q \,\}}
(2.2)

Predicates can use measures — simple functions over data that LiquidHaskell knows how to unfold in the logic. The classic is head\mathtt{head}, which stops being partial:

{-@ measure len @-}
len :: [a] -> Int
len []     = 0
len (_:xs) = 1 + len xs

{-@ head' :: {v:[a] | len v > 0} -> a @-}
head' :: [a] -> a
head' (x:_) = x    -- no [] branch needed: the type rules it out

LiquidHaskell also checks recursion for termination — it looks for a decreasing metric (by default, the first numeric argument). The type Nat\mathtt{Nat} in the signature is LiquidHaskell’s built-in alias {v:Intv\{\, v:\mathtt{Int} \mid v 0}\geq 0 \,\}, declared just like our NonZero\mathtt{NonZero}. The factorial from the section on the Y combinator is total here:

{-@ fac :: Nat -> Nat @-}
fac :: Int -> Int
fac 0 = 1
fac n = n * fac (n - 1)   -- n decreases and never drops below zero — the solver is satisfied

In reflection mode, decorators prove genuine theorems: the property is written as a type, the proof as a program. This is the Curry–Howard correspondence:

{-@ LIQUID "--reflection" @-}
import Language.Haskell.Liquid.ProofCombinators

{-@ reflect double @-}
double :: Int -> Int
double x = x + x

{-@ doubleIsTwice :: x:Int -> { double x == 2 * x } @-}
doubleIsTwice :: Int -> Proof
doubleIsTwice x = double x === x + x === 2 * x *** QED

This is a step toward types that depend on terms, but not full dependent types as in Agda: the predicates are restricted to SMT-decidable theories (linear arithmetic, equality, sets) — in return, everything is checked automatically, with no manual proofs.