Termination Checking
Why termination Checking?
By default, LH checks that functions terminate!
Most functions should terminate.
For soundness w.r.t. laziness [ICFP 14]
Example: Termination of fib
Q: Why is there an error?
Proving Termination ...
... is difficult (Halting Problem = NP-Complete).
Proving Termination
Two main ways to prove termnination:
Syntactic: Recursive calls only happen on smaller inputs.
Semantic: (Finite Chains) A value is descreasing at each resursive call.
This value (called well founded metric) cannot get negative!
Proving Termination
Liquid Haskell Checks:
Some well founded metric decreases at each recursive call.
Heuristic Metric:
The first Int
parameter.
Example: Termination of fib
Automatically Proved Terminating
User Specified Termination Metrics
The first Int
need not always be decreasing!
Specify metric as an expression over the inputs
User Specified Termination Metrics
The first Int
need not always be decreasing!
Excercise: Fill in metric that proves range
terminates.
Proving Termination
Liquid Haskell Checks:
Some well founded metric decreases at each recursive call.
Either first Int
parameter (default)
or
User specified metric
Termination via refinements
Q: Can you refine mod
to prove gcd terminating?
Lexicographic Termination
Let's generalize gcd to any Nat
.
Q: Does gcd'
still terminates?
Lexicographic Termination
Why does Ackermann Function terminate?
First argument m
decreases or second argument n
decreases.
Specify lexicographically ordered sequence of termination metrics [m, n]
How About Data Types?
Why does map
terminate?
Recursive Calls on Smaller Lists.
Use first parameter with associated size
... as default metric.
User specified metrics on ADTs
What does merge
terminate?
Q: The default is insufficient here; can you fill in a suitable metric?
Mutually Recursive Functions
Same idea generalizes to mutual recursion.
Q: Can you find the correct metric?
Liquid Haskell does not even attempt to guess it...
Diverging Functions
Some functions do not terminate!
lazy
annotation deactivates termination checking.
Proving Termination
Liquid Haskell Checks
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
.
Termination is Easy in Practice
Library | LOC | Specs | Time |
---|---|---|---|
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 |
Termination is Easy in Practice
503
Recursive Functions
67%
via default metrics30%
user given metrics1
metric per100
LOC
20
functions not proven to terminate
12
do not terminate (e.g. top-levelIO
loops)8
currently outside scope of LiquidHaskell
Recap
Refinements: | Types + Predicates |
Specification: | Refined Input/Output Types |
Verification: | SMT-based Predicate Subtyping |
Measures: | Specify Properties of Data |
Termination: | Well-founded Metrics |
Next: | Programs are Proofs |