Sorting is one of the most thoroughly studied problems in computer science, making it an excellent subject for exploring formal specification. The informal description is compact — a sort function takes a list and returns a list that is an ordered permutation of its input — yet formalising it correctly turns out to require care. This example demonstrates that a seemingly reasonable first specification can be subtly wrong and unimplementable, and walks through the refinements needed to arrive at a correct one. It also introduces inductive predicates as an alternative to recursive definitions.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/paulch42/lean-spec/llms.txt
Use this file to discover all available pages before exploring further.
A Naive First Attempt
Ordered₁
A natural recursive predicate for “a list is sorted in ascending order” is:[LT α] requires the element type α to support a less-than relation.
Permutation₁
A predicate for “two lists are permutations of each other” based on membership:Sort₁
Combining these predicates:Sort₁ takes a list of elements with an order relation and returns a list that is both ordered and a permutation of its input. This looks correct. It isn’t.
The Corrected Specification
Ordered₂
The fix forOrdered₁ is to replace the strict less-than < with the non-strict less-than-or-equal ≤:
Ordered₁ but uses ≤ (with the corresponding type class LE), which allows equal consecutive elements and therefore handles duplicate values correctly.
Strictly speaking,
LE alone is insufficient — it only guarantees that some binary predicate exists on the type, not that it behaves like the familiar “less than or equal to”. The predicate should be a partial order (reflexive, antisymmetric, transitive). A fully rigorous treatment would require partial orders from mathlib4, but for the purposes of this example LE is adequate.Permutation₂
The fix forPermutation₁ is to count occurrences using List.count rather than testing membership:
List.count is defined in std4 and counts how many times a given element appears in a list. Two lists are permutations of each other precisely when they contain the same number of occurrences of every element. The element type must support decidable equality (BEq α) for counting to work.
You can inspect the type of List.count in Lean:
Sort₂
The corrected sort specification uses both updated predicates:Sort₁, but the combination of Ordered₂ (using ≤) and Permutation₂ (counting occurrences) makes this specification correct and implementable.
Inductive Predicates: An Alternative Approach
The predicateOrdered₂ is typical of the traditional recursive style, with the distinction that it produces a Prop rather than a Bool. Dependently typed languages like Lean offer a complementary technique: inductive predicates. An inductive predicate is an inductive definition of a proposition rather than of a data type, and a member of the resulting type is a proof of the proposition.
The Ordered predicate expressed inductively is:
Ordered₂:
emptyis a proof that the empty list is ordered.singleton ais a proof that any singleton list[a]is ordered.twoplus a b as hab hbasis a proof that the lista::b::asis ordered, given thathabis evidencea ≤ bandhbasis a proof thatb::asis already ordered.
Ordered₂ and Ordered₃ is exact — they define the same predicate, one recursively and the other inductively. Inductive predicates are often preferred when working with Lean’s proof automation, since their constructors make it straightforward to build and decompose proofs.
The Lean standard library also provides List.Chain', which generalises this pattern:
Summary
The journey fromSort₁ to Sort₂ illustrates several important lessons in formal specification:
- Specification bugs are real. A type-correct specification can still be wrong or unimplementable.
- Strict vs non-strict ordering matters. Using
<instead of≤silently excludes inputs with duplicates. - Membership is not the same as counting. Permutation must account for multiplicity.
- Multiple styles are available. Recursive definitions and inductive predicates express the same concepts in different ways, and the best choice depends on context.
Exercises
Exercises
-
Specify a function that, given a natural number, returns the prime factors of the number. Use the
Primeproperty from the last exercise. -
Using
Permutation₂, specify a function that returns all permutations of an input list. -
The Lean standard library
std4defines an inductive propositionList.Chain'. SpecifySortusingChain'.