{-# LANGUAGE TupleSections #-}
{-@ LIQUID "--no-warnings" @-}
{-@ LIQUID "--short-names" @-}
{- LIQUID "--diff" @-}
module Termination where
import Prelude hiding (reverse, sum, repeat)
ack :: Int -> Int -> Int
range :: Int -> Int -> [Int]
fastSum :: Int -> Int -> Int
-- {-@ split :: xs:[a] -> Halves a xs @-}
-- {-@ type Halves a Xs = {v: (Half a Xs, Half a Xs) | len (fst v) + len (snd v) == len Xs} @-}
-- {-@ type Half a Xs = {v:[a] | (len Xs > 1) => (len v < len Xs)} @-}
Code is meant to be run...
msort :: (Ord a) => [a] -> [a]
msort [] = []
msort xs = let (xs1, xs2) = split xs in
merge (msort xs1) (msort xs2)
split :: [a] -> ([a], [a])
split (x:y:zs) = let (xs, ys) = split zs in
(x:xs, y:ys)
split xs = (xs, [])
merge :: (Ord a) => [a] -> [a] -> [a]
merge xs [] = xs
merge [] ys = ys
merge (x:xs) (y:ys)
| x <= y = x : merge xs (y:ys)
| otherwise = y : merge (x:xs) ys
Code is meant to be run...
Code is meant to be run...and finish!
λ> :l msort.hs
[1 of 1] Compiling Main ( msort.hs, interpreted )
Ok, modules loaded: Main.
λ> msort []
[]
λ> msort [1,3,2]
Avoiding Infinite Loops
Example: Proving Termination of sum
sum :: Int -> Int
sum 0 = 0
sum n = n + sum (n - 1)
Exercise: (Why) does this terminate?
Proving Termination
Proving Termination
Termination: A well founded metric decreases at each recursive call
Proving Termination
Termination: A well founded metric decreases at each recursive call
Metric gets strictly smaller
Metric has a lower bound
Proving Termination
Termination: A well founded metric decreases at each recursive call
Metric gets strictly smaller and has a lower-bound
e.g. 99 bottles, 98 bottles, ..., 0 bottles!
Example: Proving Termination of sum
sum' :: Int -> Int
sum' 0 = 0
sum' n = n + sum (n - 1)
Default Metric
First Int
parameter
Exercise: Always gets smaller, but what's the lower bound ?
User Specified Termination Metrics
The first Int
need not always be decreasing!
User Specified Termination Metrics
The first Int
need not always be decreasing!
{-@ fastSum :: Int -> Int -> Int @-}
fastSum total 0 = total
fastSum total n = fastSum (total + n) (n - 1)
Exercise: Can you specify metric as an expression over the inputs?
User Specified Termination Metrics
Specify metric as an expression over the inputs
{-@ range :: lo:Int -> hi:Int -> [Int] @-}
range lo hi
| lo < hi = lo : range (lo + 1) hi
| otherwise = []
Exercise: What metric proves range
terminates?
Proving Termination
Type Checker Verifies
A well founded metric decreases at each recursive call.
Either first Int
parameter (default)
or
User specified metric
Lexicographic Termination
Why does Ackermann Function terminate?
{-@ ack :: m:Int -> n:Int -> Int @-}
ack 0 n = n + 1
ack m 0 = ack (m - 1) 1
ack m n = ack (m - 1) (ack m (n - 1))
First argument m
decreases or second argument n
decreases.
Specify lexicographically ordered sequence of termination metrics [m, n]
How About Data Types?
Why does append
terminate?
append :: [a] -> [a] -> [a]
append [] ys = ys
append (x:xs) ys = x : append xs ys
Recursive Calls on Smaller Lists
Default Metric: First parameter with associated size
User specified metrics on Data Types
Why does merge
terminate?
{-@ merge :: xs:[a] -> ys:[a] -> [a] @-}
merge (x:xs) (y:ys)
| x < y = x : merge xs (y : ys)
| otherwise = y : merge ys (x : xs)
merge xs [] = xs
merge [] ys = ys
Exercise: The default is insufficient. What is a suitable metric?
Termination Can be Tricky
Exercise: What's going on with this merge-sort
? Can you fix it?
sort :: Ord a => [a] -> [a]
sort [] = []
sort xs = let (ys, zs) = split xs
in
merge (sort ys) (sort zs)
split :: [a] -> ([a], [a])
split (x:y:zs) = let (xs, ys) = split zs in
(x:xs, y:ys)
split xs = (xs, [])
Avoiding Infinite Loops
Type Check Termination
Some well founded metric decreases at each recursive call.
Avoiding Infinite Loops
Type Check Termination
Some well founded metric decreases at each recursive call.
First Int
or sized parameter (default), or
Avoiding Infinite Loops
Type Check Termination
Some well founded metric decreases at each recursive call.
First Int
or sized parameter (default), or
User specified (lexicographic) metric, or
Avoiding Infinite Loops
Type Check Termination
Some well founded metric decreases at each recursive call.
First Int
or sized parameter (default), or
User specified (lexicographic) metric, or
The function is marked lazy
(non-terminating).
Avoiding Infinite Loops is Easy (in Practice)
Avoiding Infinite Loops is Easy (in Practice)
XMonad.StackSet
256
74
27s
Data.List
814
46
26s
Data.Set.Splay
149
27
27s
Data.Vector.Algorithms
1219
76
61s
Data.Map.Base
1396
125
68s
Data.Text
3128
305
231s
Data.Bytestring
3505
307
136s
Total
11512
977
574s
Avoiding Infinite Loops is Easy (in Practice)
503
Recursive Functions
67%
via default metrics vs 30%
user given metrics
1
metric per 100
LOC
Avoiding Infinite Loops is Easy (in Practice)
503
Recursive Functions
67%
via default metrics vs 30%
user given metrics
1
metric per 100
LOC
20
functions not proven to terminate
12
do not terminate (e.g. top-level IO
loops)
8
currently outside scope of type checker