Certified Programming with Dependent Types

2026/01/02

NOTE: I paused reading at chapter 6. Right now, this book is too advanced. I will read through Software Foundations before coming back. I ported the chapters 1-6 to Rocq 9.0.1.

2 Some Quick Examples

Given an overview of what is possible with Rocq for formal verification. A typed expression language is created with support for natural numbers and booleans, featuring type-indexed operations: addition, multiplication, equality comparison (for any type), and less-than comparison (for naturals). A compiler to a stack machine is created and proven to have correct translation for all possible programs. The compiler can be extracted to OCaml and executed with reasonable performance. The proofs introduce tactics for manual proving and automation techniques.

Type indexing allows encoding precise information directly in types. For example:

Inductive tbinop : type -> type -> type -> Set :=
| TPlus : tbinop Nat Nat Nat
| TTimes : tbinop Nat Nat Nat
| TEq : forall t, tbinop t t Bool
| TLt : tbinop Nat Nat Bool.

Here, TPlus takes two Nat operands and returns Nat, while TEq works on any type but requires both operands to match. This makes invalid programs like TBinop (TEq Nat) (TNConst 5) (TBConst true) impossible to construct. They’re rejected at compile time. While I was familiar with the concept of type indexing, this chapter deepened my understanding of its practical purpose.

The chapter explicitly states that not all concepts will be explained in detail, aiming instead to demonstrate what’s possible. Despite being unfamiliar with the Rocq proof assistant, I found the material approachable as key concepts and proof tactics were introduced progressively throughout the examples.

3 Introduction to Inductive Types

Proof Terms and Curry-Howard Correspondence

Coq unifies proofs and programs through the Curry-Howard correspondence: theorems are types, and proofs are programs that inhabit those types. The arrow -> represents both function types and logical implications. For example, fun x : True => x has type True -> True, making it simultaneously a function and a proof.

The type system distinguishes between:

Key distinction: bool values (true/false) are decidable and always evaluate, while Prop propositions (True/False) represent undecidable logical statements. Both Prop and Set are primitive built-ins in Coq’s type system, not defined within the language.

Simple Inductive Types

unit - Singleton type with one value tt (“trivial term”).

Empty_set - Type with no constructors, hence no values can exist.

bool - Two constructors: true and false. Pattern matching enables function definitions like negb (boolean negation). Proofs use case analysis (destruct) or induction.

More Inductive Types

Tactics

Core Proof Tactics:

Equality and Computation:

Constructor Reasoning:

Basic Goal Management:

4 Inductive Predicates

How to define logical properties as inductive types in Prop. Instead of data types like lists or numbers, we define judgments like “n is even” or “n equals zero” using inference rules. The key insight: use rule induction instead of regular induction on data. For example, when proving something about even numbers, induct on the proof that a number is even, not on the number itself. Covers basic logic connectives with automation (tauto, intuition), existential quantification, and recursive predicates. When using induction, variables that appear before what you’re inducting on stay fixed, while variables after it can vary.

Tactics:

5 Infinate Data and Proofs

In programming languages like Haskell (or any other lazy evaluated language) we can define infinite data structures. For example in a binary tree or list in python, a value is only evaluated when needed. This can lead to non-terminism which we do not have in formal verification. Rocq also supports lazy data structures.

Keywords

Recursive calls in a type must be guarded by the constructor of their type. This prevents infinite loops (WHY?). In practice any co-recursive definition must be able to evaluate to a finite result in finite time. You can not write a function filter because it might remove every element and never terminate.

Equality of co-recursive definitions can not use the normal eq predicate. Instead we define a new one which checks the equality of head and tail. We can check equality explicitly or implicit

CoInductive stream_eq_bool : stream bool -> stream bool -> Prop :=
  | Stream_eq_bool : forall (h1 h2 : bool) (t1 t2 : stream bool),
      h1 = h2 ->
      stream_eq_bool t1 t2
      -> stream_eq_bool (Cons h1 t1) (Cons h2 t2).

We reuse h for the head of both.

CoInductive stream_eq_bool : stream bool -> stream bool -> Prop :=
  | Stream_eq_bool : forall (h : bool) (t1 t2 : stream bool),
      stream_eq_bool t1 t2
      -> stream_eq_bool (Cons h t1) (Cons h t2).

The section continues with a proof of equality for two factorial steams and how to generalize these proofs (SHOULD REVISIT!). It ends with a motivation for co-inductive types. We define a simple imperative programming language and prove the correctness of an optimization (x + 0 = x).

The last third of this chapter was hard. I did not understand the generalizations of the stream equality proofs. The motivation of co-inductive types is very useful in the context of program verification.

Tactics:

6 Subset Types and Variations

This chapter introduces dependent types known from other proof assistants. The chapter motivates dependent types with the promise of easier program verification. Using the predecessor function, the default version in Rocq returns 0 for 0. In program verification however, we might be interested verifying that pred is not called on 0.

We reimplement dependent types using subset types from the standard library including defining syntactic sugar to arrive at a natural syntax. It is shown that proof code is erased in extraction to OCaml (just like in to F*).