A Deep Dive into VDM
Formal specification through a sorting example — describing what output is acceptable, not how to compute it
What is VDM?
VDM (the Vienna Development Method) is one of the longest-established model-oriented formal methods, with roots in programming-language semantics research at IBM's Vienna Laboratory in the 1970s. Its specification language VDM-SL is an ISO standard (ISO/IEC 13817-1:1996), and the method has grown into VDM++ (object-oriented) and VDM-RT (distributed, real-time).
VDM's great practical strength is that specifications can also be written in an executable form. While mathematically rigorous, a specification written in the executable style (the explicit style introduced below) can be run in an interpreter to observe its behavior (specification animation), so errors in the specification itself can be found before any implementation exists. Industrial track records include large-scale IC-card firmware development in Japan.
Representative tools: Overture (IDE), VDMJ (command-line processor), ViennaTalk
Worked example: sorting as a specification
"Sort a list into ascending order" — what happens when we write this familiar operation as a formal specification? The crucial point is that the specification of sorting never mentions the algorithm — neither merge sort nor quicksort appears anywhere. What a specification must define is exactly one thing: for a given input, what output is acceptable.
In VDM, this "what output is acceptable" is written as a postcondition. Defining a function only by its pre- and postconditions, with no body (no algorithm), is called an implicit specification — the fundamental style of specification in VDM.
Sort (l : seq of int) r : seq of int post IsPermutation(r, l) and IsOrdered(r);
In words: "Sort takes a sequence of integers l and returns a sequence of integers r, such that r is a permutation of l and r is in ascending order." That is the entire specification of sorting. Note that there is no precondition (pre) — every sequence of integers is acceptable as input.
IsOrdered : seq of int -> bool IsOrdered(l) == forall i, j in set inds l & i > j => l(i) >= l(j); IsPermutation : seq of int * seq of int -> bool IsPermutation(l1, l2) == forall e in set (elems l1 union elems l2) & card {i | i in set inds l1 & l1(i) = e} = card {i | i in set inds l2 & l2(i) = e};
The two predicates in the postcondition are themselves defined as VDM functions. IsOrdered states that a later element is never smaller than an earlier one (ascending order); IsPermutation states that every value occurs the same number of times in both sequences — the output is a rearrangement of the input.
The critical clause: forgetting the permutation condition
If the postcondition were only IsOrdered(r), implementations that always return the empty list — or always return [1, 2, 3] — would satisfy the specification. It is IsPermutation that guarantees the implementation cannot silently drop, invent, or alter elements. Thinking about what breaks when a clause is missing matters as much as deciding what to write.
Implicit vs. explicit specifications
A VDM function can be written in two styles: an implicit specification, which fixes only the properties to satisfy via pre- and postconditions, and an explicit specification, which supplies a body (a way to compute). Development typically starts by pinning down the properties implicitly, then refines step by step toward explicit definitions and implementations.
| Aspect | Implicit specification | Explicit specification |
|---|---|---|
| What is written | Properties to satisfy (pre/postconditions) | A computation procedure (an algorithm) |
| Execution | Not directly executable | Runs in the interpreter (specification animation) |
| Algorithm | Left open (implementer's choice) | Fixed to one |
| Main role | Definition of correctness; a contract | Prototype; executable model |
VDM also lets you keep the explicit form while leaving the body undefined, using "is not yet specified". This records that a function's contents are deliberately not decided yet, in a form that still type-checks.
Sort : seq of int -> seq of int Sort(l) == is not yet specified;
An implicit specification cannot be executed — and that is not a flaw but the whole point. Precisely because no algorithm is fixed, any implementation — insertion sort, merge sort, quicksort, or anything else — is judged "correct" as long as it satisfies the postcondition. The implicit specification works as a neutral criterion of correctness, independent of any particular implementation.
Explicit specifications: one specification, three implementations
The official Overture example "sort" (Peter Gorm Larsen, 2010) collects several explicit specifications of the same sorting problem. All of them are executable — and all of them satisfy the postcondition of the implicit specification above.
1. Running the specification (almost) as-is: choose a sorted permutation
Sort : seq of int -> seq of int Sort(l) == let r in set Permutations(l) be st IsOrdered(r) in r;
"From the permutations of l, pick one that is ordered" — an explicit specification that mirrors the structure of the postcondition, written with the nondeterministic choice let ... be st ... (choose one such that). Permutations is an auxiliary function returning the set of all permutations of l (definition omitted). With n! permutations it is far too slow for practical use, but it shows what the halfway point between a specification and an implementation looks like.
2. Insertion sort
DoSorting : seq of int -> seq of int DoSorting(l) == if l = [] then [] else InsertSorted(hd l, DoSorting(tl l)); InsertSorted : int * seq of int -> seq of int InsertSorted(i, l) == cases true : (l = []) -> [i], (i <= hd l) -> [i] ^ l, others -> [hd l] ^ InsertSorted(i, tl l) end;
A recursive definition: sort the tail, then insert the head element at its correct position.
3. Merge sort
MergeSorter : seq of int -> seq of int MergeSorter(l) == cases l : [], [-] -> l, others -> let l1 ^ l2 in set {l} be st abs (len l1 - len l2) < 2 in Merge(MergeSorter(l1), MergeSorter(l2)) end; Merge : seq of int * seq of int -> seq of int Merge(l1, l2) == cases mk_(l1, l2) : mk_([], l), mk_(l, []) -> l, others -> if hd l1 <= hd l2 then [hd l1] ^ Merge(tl l1, l2) else [hd l2] ^ Merge(l1, tl l2) end;
A divide-and-conquer definition: split the list roughly in half, sort each part, and merge them. Note let ... be st ... appearing again — this time to "choose a split whose length difference is less than 2".
Running it
Explicit specifications run as-is in the VDMJ / Overture interpreter. You can observe behavior at the specification stage, before writing any implementation — this is specification animation.
> print DoSorting([3, 1, 66, 11, 5, 3, 99]) = [1, 3, 3, 5, 11, 66, 99]
Permutation choice, insertion sort, merge sort — all three explicit specifications satisfy the postcondition of the same implicit specification. The implicit specification defines what correct sorting is; which algorithm to choose is a question of performance and context, not of correctness. This separation of the what from the how is the essence of formal specification.
From specification to verification
A postcondition is not mere documentation — it is a verification device. In VDM the same pre- and postconditions can also be attached to an explicit definition, and VDMJ checks them automatically at runtime — so if an explicit specification (or an implementation derived from it) violates the properties, the violation is caught on the spot.
Moreover, the question "does this explicit specification really satisfy the postcondition?" can itself be generated mechanically as a proof obligation and then proved. Execution and testing can only show correctness for the inputs you tried; a proof guarantees correctness for all inputs.
The implicit-specification mindset — fix only the input/output contract, never the algorithm — carries over directly to defining contracts between cooperating AI agents in multi-agent development. IID's formal-agent-contracts plugin writes and verifies inter-agent contracts in VDM-SL in exactly this style.
The code on this page is adapted for exposition from the VDM++ model "sort" in the official Overture examples collection (Peter Gorm Larsen, 2010), rewritten in VDM-SL style (class structure and measure clauses omitted).
Source: Overture Examples — sort (VDM++) ↗