5. Formal Specification
In this chapter, we discuss formal specification of software systems. We will use Alloy, a lightweight modeling language based on relational logic, to express our specifications. We focus on modeling data structures, defining constraints, specifying operations, and reasoning about system behavior using Alloy’s built-in analysis tools.
More details, including additional examples, are available in the book Formal Software Design with Alloy 6.
5.1. Introduction to Alloy
Alloy is a declarative language for specifying structural properties of systems. It is built on first-order relational logic and provides an automatic analysis engine, the Alloy Analyzer, to explore instances and verify properties.
These are Alloy’s key features:
Uses signatures (
sig) to define entities and their relationships.Defines constraints using facts, predicates, and assertions.
Allows instance exploration and counterexample generation.
Supports temporal reasoning with basic transition modeling.
Alloy 6 extends earlier versions with improved support for expressing operations and transitions.
5.2. Modeling Data Structures in Alloy: A Simple Queue
We begin by modeling a bounded queue to illustrate formal specification concepts using Alloy as a notation. A queue holds elements in a first-in, first-out (FIFO) order. We define a basic queue structure:
module Queue
sig Element {}
sig Queue {
elements: seq Element
}
Here, Element is a basic type representing items in the queue. The Queue signature has a relation elements using a sequence (seq), which ensures order.
5.3. Basic Constraints
We now define a fact to limit the queue’s size:
fact BoundedSize {
all q: Queue | #q.elements <= 5
}
This constraint ensures no queue contains more than five elements.
5.4. Exploring Instances
We can generate sample instances using the Alloy Analyzer:
run {} for 3 Queue, 5 Element
This command searches for valid queue instances under the given bounds.
5.5. Modeling operations: Enqueue and Dequeue
We now add operations to manipulate the queue. In Alloy, we define these using predicates:
pred enqueue[q: Queue, e: Element] {
#q.elements < 5
q.elements' = q.elements + e
}
pred dequeue[q: Queue, e: Element] {
some q.elements
e = first q.elements
q.elements' = rest q.elements
}
Here, enqueue adds an element (if space allows), and dequeue removes the first element.
5.6. Checking Properties
We verify queue behavior using assertions:
assert FIFO {
all q: Queue, e1, e2: Element |
enqueue[q, e1] and enqueue[q, e2] implies
dequeue[q, e1]
}
check FIFO for 5 Queue, 5 Element
If a counterexample exists, Alloy will generate one.
5.7. Basic Behavioral Specification
We now model state transitions in Alloy. We start with a state-based view of the queue:
sig State {
queues: Queue
}
pred step[s, s': State] {
some q: Queue |
enqueue[q, some Element] or dequeue[q, some Element]
}
5.8. Temporal Properties
We check sequences of operations:
pred always_not_empty {
all s: State | some s.queues.elements
}
check always_not_empty
This verifies that the queue is never empty across all states.
5.9. Conclusion
This chapter introduced Alloy 6 for modeling data structures, specifying operations, and verifying behaviors using constraints and assertions. The next topic extends these concepts to temporal logic and model checking.