Systems and
Formalisms Lab

Untangling mechanized proofs

An introduction to Alectryon

Alectryon (named after the Greek god of chicken) is a collection of tools for writing technical documents that mix Coq code and prose, in a style sometimes called literate programming.

Coq proofs are hard to understand in isolation: unlike pen-and-paper proofs, proof scripts only record the steps to take (induct on x, apply a theorem, …), not the states (goals) that these steps lead to. As a result, plain proof scripts are essentially incomprehensible without the assistance of an interactive interface like CoqIDE or Proof General.

The most common approach these days for sharing proofs with readers without forcing them to run Coq is to manually copy Coq's output into source code comments — a tedious, error-prone, and brittle process. Any text that accompanies the proof is also embedded in comments, making for a painful editing experience.

Alectryon does better: it automatically captures Coq's output and interleaves it with proof scripts to produce interactive webpages, and it lets you toggle between prose- and code-oriented perspectives on the same document so that you can use your favorite text editing mode for writing prose and your favorite Coq IDE for writing proofs.

I have a talk about this at SLE2020 on November 16th; you should join!

Below, you can see three examples: in the first one I asked Alectryon to record the result of a single Check command. In the second, I recorded an error message printed by Coq. In the third, I recorded a simple proof script — try hovering or clicking on Coq sentences. In the last, I've used hidden Show Proof commands to show how each tactic participates in constructing a proof term.

bool_ind : forall P : bool -> Prop, P true -> P false -> forall b : bool, P b
The term "true" has type "bool" while it is expected to have type "nat".
A: Type
l: list A

rev (rev l) = l
A: Type
l: list A

rev (rev l) = l
A: Type

rev (rev nil) = nil
A: Type
a: A
l: list A
IHl: rev (rev l) = l
rev (rev (a :: l)) = a :: l
A: Type

rev (rev nil) = nil
A: Type

nil = nil
reflexivity.
A: Type
a: A
l: list A
IHl: rev (rev l) = l

rev (rev (a :: l)) = a :: l
A: Type
a: A
l: list A
IHl: rev (rev l) = l

rev (rev l ++ a :: nil) = a :: l
A: Type
a: A
l: list A
IHl: rev (rev l) = l

rev (a :: nil) ++ rev (rev l) = a :: l
A: Type
a: A
l: list A
IHl: rev (rev l) = l

rev (a :: nil) ++ l = a :: l
A: Type
a: A
l: list A
IHl: rev (rev l) = l

a :: l = a :: l
reflexivity. Qed.
Context (classical: forall A, A \/ ~ A).
classical: forall A : Prop, A \/ ~ A

forall A : Prop, ~ ~ A -> A
classical: forall A : Prop, A \/ ~ A

forall A : Prop, ~ ~ A -> A
classical: forall A0 : Prop, A0 \/ ~ A0
A: Prop
notnot_A: ~ ~ A

A
(fun (A : Prop) (notnot_A : ~ ~ A) => ?Goal)
classical: forall A0 : Prop, A0 \/ ~ A0
A: Prop
notnot_A: ~ ~ A
_A: A

A
classical: forall A0 : Prop, A0 \/ ~ A0
A: Prop
notnot_A: ~ ~ A
not_A: ~ A
A
(fun (A : Prop) (notnot_A : ~ ~ A) => let o : A \/ ~ A := classical A in match o with | or_introl _A => ?Goal | or_intror not_A => ?Goal0 end)
classical: forall A0 : Prop, A0 \/ ~ A0
A: Prop
notnot_A: ~ ~ A
_A: A

A
assumption.
(fun (A : Prop) (notnot_A : ~ ~ A) => let o : A \/ ~ A := classical A in match o with | or_introl _A => _A | or_intror not_A => ?Goal end)
classical: forall A0 : Prop, A0 \/ ~ A0
A: Prop
notnot_A: ~ ~ A
not_A: ~ A

A
elim (notnot_A not_A).
(fun (A : Prop) (notnot_A : ~ ~ A) => let o : A \/ ~ A := classical A in match o with | or_introl _A => _A | or_intror not_A => False_ind A (notnot_A not_A) end)
Qed.

By the way, I wrote an academic paper at SLE2020 on Alectryon. It has plenty of cool visualizations and examples, it goes into much more depth than this intro, and importantly it has all the related work, including lots of stuff on procedural vs declarative proofs. This blog post, the paper, and the talk were all built with Alectryon.

If this is your first time on this blog, you might want to check the very short tutorial on navigating these visualizations before proceeding with the rest of this post.

A quick tour of Alectryon

The library was written with two scenarios in mind:

There's no support for attaching bits of documentation to specific bits of code, like definitions, axioms, variables, etc. As I've written in the past, I think this is a different job (“docstrings”), ideally to be handled by Coq itself (similar to how it tracks the body and location of definitions). Alectryon also doesn't support hyperlink Coq terms to their definitions like coqdoc can, but I plan to implement this eventually.

Generating webpages

Alectryon's main purpose is to record Coq's outputs and interleave them with the corresponding inputs to create an interactive webpage:


l : list nat, length (rev l) = length l

l : list nat, length (rev l) = length l
l: list nat

length (rev l) = length l

length (rev nil) = length nil
n: nat
l': list nat
IHl': length (rev l') = length l'
length (rev (n :: l')) = length (n :: l')

length (rev nil) = length nil
reflexivity.
n: nat
l': list nat
IHl': length (rev l') = length l'

length (rev (n :: l')) = length (n :: l')
n: nat
l': list nat
IHl': length (rev l') = length l'

length (rev l' ++ n :: nil) = S (length l')
n: nat
l': list nat
IHl': length (rev l') = length l'

length (rev l') + length (n :: nil) = S (length l')
n: nat
l': list nat
IHl': length (rev l') = length l'

length (n :: nil) + length (rev l') = S (length l')
n: nat
l': list nat
IHl': length (rev l') = length l'

S (length (rev l')) = S (length l')
n: nat
l': list nat
IHl': length (rev l') = length l'

S (length l') = S (length l')
reflexivity. Qed.
rev_length : l : list nat, length (rev l) = length l

Because this is an interactive webpage, we can apply all sorts of post-processing to the output, like using MathJax to make a math proof a bit more readable:

\(\newcommand{\ccQ}{\mathbb{Q}}\) \(\newcommand{\ccNat}{\mathbb{N}}\) \(\newcommand{\ccSucc}[1]{\mathrm{S}\:#1}\) \(\newcommand{\ccFrac}[2]{\frac{#1}{#2}}\) \(\newcommand{\ccPow}[2]{{#1}^{#2}}\) \(\newcommand{\ccNot}[1]{{\lnot #1}}\) \(\newcommand{\ccEvar}[1]{\textit{\texttt{#1}}}\) \(\newcommand{\ccForall}[2]{\forall \: #1. \; #2}\) \(\newcommand{\ccNsum}[3]{\sum_{#1 = 0}^{#2} #3}\)

\ccForall{n : \ccNat{}}{2 \times \ccNsum{i}{n}{i} = n \times (n + 1)}

\ccForall{n : \ccNat{}}{2 \times \ccNsum{i}{n}{i} = n \times (n + 1)}

2 \times 0 = 0 \times (0 + 1)
n: \ccNat{}
IHn: 2 \times \ccNsum{i}{n}{i} = n \times (n + 1)
2 \times (\ccSucc{ n} + \ccNsum{i}{n}{i}) = \ccSucc{ n} \times (\ccSucc{ n} + 1)

2 \times 0 = 0 \times (0 + 1)
reflexivity.
n: \ccNat{}
IHn: 2 \times \ccNsum{i}{n}{i} = n \times (n + 1)

2 \times (\ccSucc{ n} + \ccNsum{i}{n}{i}) = \ccSucc{ n} \times (\ccSucc{ n} + 1)

2 \times \ccSucc{ n} + 2 \times \ccNsum{i}{n}{i} = \ccSucc{ n} \times (\ccSucc{ n} + 1)

2 \times \ccSucc{ n} + n \times (n + 1) = \ccSucc{ n} \times (\ccSucc{ n} + 1)
ring. Qed.

… or using the browser's native support for vector graphics to render Game of Life boards encoded as lists of Booleans into small images:

Definition glider := [[0;1;0;0;0];
                      [0;0;1;0;0];
                      [1;1;1;0;0];
                      [0;0;0;0;0];
                      [0;0;0;0;0]].
= [[[0; 1; 0; 0; 0]; [0; 0; 1; 0; 0]; [1; 1; 1; 0; 0]; [0; 0; 0; 0; 0]; [0; 0; 0; 0; 0]]; [[0; 0; 0; 0; 0]; [1; 0; 1; 0; 0]; [0; 1; 1; 0; 0]; [0; 1; 0; 0; 0]; [0; 0; 0; 0; 0]]; [[0; 0; 0; 0; 0]; [0; 0; 1; 0; 0]; [1; 0; 1; 0; 0]; [0; 1; 1; 0; 0]; [0; 0; 0; 0; 0]]; [[0; 0; 0; 0; 0]; [0; 1; 0; 0; 0]; [0; 0; 1; 1; 0]; [0; 1; 1; 0; 0]; [0; 0; 0; 0; 0]]; [[0; 0; 0; 0; 0]; [0; 0; 1; 0; 0]; [0; 0; 0; 1; 0]; [0; 1; 1; 1; 0]; [0; 0; 0; 0; 0]]; [[0; 0; 0; 0; 0]; [0; 0; 0; 0; 0]; [0; 1; 0; 1; 0]; [0; 0; 1; 1; 0]; [0; 0; 1; 0; 0]]; [[0; 0; 0; 0; 0]; [0; 0; 0; 0; 0]; [0; 0; 0; 1; 0]; [0; 1; 0; 1; 0]; [0; 0; 1; 1; 0]]; [[0; 0; 0; 0; 0]; [0; 0; 0; 0; 0]; [0; 0; 1; 0; 0]; [0; 0; 0; 1; 1]; [0; 0; 1; 1; 0]]; [[0; 0; 0; 0; 0]; [0; 0; 0; 0; 0]; [0; 0; 0; 1; 0]; [0; 0; 0; 0; 1]; [0; 0; 1; 1; 1]]] : list (list (list bool))

… or using a graph library to draw visualizations that makes it clearer what happens when one builds a red-black tree with Coq.MSets.MSetRBT.

Module RBT := MSets.MSetRBT.Make Nat_as_OT.
Definition build_trees (leaves: list nat) :=
  List.fold_left (fun trs n =>
        RBT.add n (hd RBT.empty trs) :: trs)
    leaves [] |> List.rev.

= [{ 'tree': { 'kind':'node'; 'color': 'Black'; 'value': '1'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } }}; { 'tree': { 'kind':'node'; 'color': 'Black'; 'value': '1'; 'left': { 'kind': 'leaf' }; 'right': { 'kind':'node'; 'color': 'Red'; 'value': '2'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } } }}; { 'tree': { 'kind':'node'; 'color': 'Black'; 'value': '2'; 'left': { 'kind':'node'; 'color': 'Black'; 'value': '1'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } }; 'right': { 'kind':'node'; 'color': 'Black'; 'value': '3'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } } }}; { 'tree': { 'kind':'node'; 'color': 'Black'; 'value': '2'; 'left': { 'kind':'node'; 'color': 'Black'; 'value': '1'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } }; 'right': { 'kind':'node'; 'color': 'Black'; 'value': '3'; 'left': { 'kind': 'leaf' }; 'right': { 'kind':'node'; 'color': 'Red'; 'value': '4'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } } } }}; { 'tree': { 'kind':'node'; 'color': 'Black'; 'value': '2'; 'left': { 'kind':'node'; 'color': 'Black'; 'value': '1'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } }; 'right': { 'kind':'node'; 'color': 'Red'; 'value': '4'; 'left': { 'kind':'node'; 'color': 'Black'; 'value': '3'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } }; 'right': { 'kind':'node'; 'color': 'Black'; 'value': '5'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } } } }}] : list RBT.t
= [{ 'tree': { 'kind':'node'; 'color': 'Black'; 'value': '2'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } }}; { 'tree': { 'kind':'node'; 'color': 'Black'; 'value': '2'; 'left': { 'kind':'node'; 'color': 'Red'; 'value': '1'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } }; 'right': { 'kind': 'leaf' } }}; { 'tree': { 'kind':'node'; 'color': 'Black'; 'value': '2'; 'left': { 'kind':'node'; 'color': 'Red'; 'value': '1'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } }; 'right': { 'kind':'node'; 'color': 'Red'; 'value': '4'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } } }}; { 'tree': { 'kind':'node'; 'color': 'Black'; 'value': '3'; 'left': { 'kind':'node'; 'color': 'Black'; 'value': '2'; 'left': { 'kind':'node'; 'color': 'Red'; 'value': '1'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } }; 'right': { 'kind': 'leaf' } }; 'right': { 'kind':'node'; 'color': 'Black'; 'value': '4'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } } }}; { 'tree': { 'kind':'node'; 'color': 'Black'; 'value': '3'; 'left': { 'kind':'node'; 'color': 'Black'; 'value': '2'; 'left': { 'kind':'node'; 'color': 'Red'; 'value': '1'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } }; 'right': { 'kind': 'leaf' } }; 'right': { 'kind':'node'; 'color': 'Black'; 'value': '4'; 'left': { 'kind': 'leaf' }; 'right': { 'kind':'node'; 'color': 'Red'; 'value': '6'; 'left': { 'kind': 'leaf' }; 'right': { 'kind': 'leaf' } } } }}] : list RBT.t

Do these visualizations really help? You be the judge: here's how the red-black tree example looks with plain-text output:

= [Node Black Leaf 1 Leaf; Node Black Leaf 1 (Node Red Leaf 2 Leaf); Node Black (Node Black Leaf 1 Leaf) 2 (Node Black Leaf 3 Leaf); Node Black (Node Black Leaf 1 Leaf) 2 (Node Black Leaf 3 (Node Red Leaf 4 Leaf)); Node Black (Node Black Leaf 1 Leaf) 2 (Node Red (Node Black Leaf 3 Leaf) 4 (Node Black Leaf 5 Leaf))] : list t
= [Node Black Leaf 2 Leaf; Node Black (Node Red Leaf 1 Leaf) 2 Leaf; Node Black (Node Red Leaf 1 Leaf) 2 (Node Red Leaf 4 Leaf); Node Black (Node Black (Node Red Leaf 1 Leaf) 2 Leaf) 3 (Node Black Leaf 4 Leaf); Node Black (Node Black (Node Red Leaf 1 Leaf) 2 Leaf) 3 (Node Black Leaf 4 (Node Red Leaf 6 Leaf))] : list t

Even if you don't use Alectryon's literate programming features, these webpages have one additional advantage beyond convenient browsing: because they record both your code and Coq's responses, they can serve as a permanent record of your developments immune to bitrot and suitable for archival.

Editing literate Coq documents

Besides generating webpages from standalone Coq files, Alectryon can help you write documentation, blog posts, and all sorts of other documents mixing proofs and prose. Alectryon's literate module implements translations from Coq to reStructuredText and from reStructuredText to Coq, which allow you to toggle between two independent views of the same document: one best for editing code, and one best for editing reST prose. Concretely, Alectryon knows how to convert between this:

=============================
 Writing decision procedures
=============================

Here's an inductive type:

.. coq::

   Inductive Even : nat -> Prop :=
   | EvenO : Even O
   | EvenS : forall n, Even n -> Even (S (S n)).

.. note::

   It has two constructors:

   .. coq:: unfold out

      Check EvenO.
      Check EvenS.

… and this:

(*|
=============================
 Writing decision procedures
=============================

Here's an inductive type:
|*)

Inductive Even : nat -> Prop :=
| EvenO : Even O
| EvenS : forall n, Even n -> Even (S (S n)).

(*|
.. note::

   It has two constructors:
|*)

Check EvenO.
Check EvenS.

Because the transformations are (essentially) inverses of each other, you don't have to pick one of these two styles and stick to it (or worse, to maintain two copies of the same document, copy-pasting snippets back and forth). Instead, you can freely switch between using your favorite Coq IDE to write code and proofs while editing bits of prose within comments, and using your favorite reStructuredText editor to write prose.

The reason for picking reStructuredText as the markup language for comments is that it's designed with extensibility in mind, which allows me to plug Alectryon into the standard Docutils and Sphinx compilation pipelines for reStructuredText (Sphinx is what the documentations of Haskell, Agda, Coq, and Python are written in). This is how this blog is written, and in fact you can download the sources if you're curious to see what it looks like. This is also how I made my SLE2020 slides (press p to see the presenter notes) and how I wrote my SLE2020 paper.

A small Emacs package (alectryon.el), allows you to toggle quickly between Coq and reST. The screenshot below demonstrates this feature: on the left is the Coq view of an edited excerpt of Software Foundations, in coq-mode; on the right is the reST view of the same excerpt, in a rst-mode buffer. The conversion is transparent, so editing either view updates the same .v file on disk. Notice the highlight indicating a reStructuredText warning on both sides:

Side-by-side comparisons of Coq and reStructuredText views of the same document

Alectryon's syntax-highlighting is done with Pygments, but it uses an update Coq grammar with a database of keywords and commands extracted directly from the reference manual (ultimately, this part should be merged upstream, and the database-generation tool should be merged into the Coq reference manual; I'll write a separate blog post about it at some point).

Recording Coq's output and caching it

Alectryon's design is pretty modular, so if you want to use it for other purposes it's easy to use just some parts of it. In particular, its core is a simple API that takes a list of code snippets, feeds them to Coq through SerAPI, and records goals and messages. This functionality is exposed on the command line (taking json as input and producing json as output) and also as a Python module:

>>> from alectryon.core import annotate
>>> annotate(["Example xyz (H: False): True. (* ... *) exact I. Qed.", "Print xyz."])
[[CoqSentence(
     sentence='Example xyz (H: False): True.',
     responses=[],
     goals=[CoqGoal(name='2',
                    conclusion='True',
                    hypotheses=[CoqHypothesis(name='H', body=None, type='False')])]),
  CoqText(string=' (* ... *) '),
  CoqSentence(sentence='exact I.', responses=[], goals=[]),
  CoqText(string=' '),
  CoqSentence(sentence='Qed.', responses=[], goals=[])],

 [CoqSentence(sentence='Print xyz.',
              responses=['xyz = fun _ : False => I\n     : False -> True'],
          goals=[])]]

Alectryon uses JSON caches to speed up consecutive runs, but even when performance isn't a problem caches provide a very useful form of regression testing for embedded Coq snippets. Without such tests, it's easy for seemingly innocuous changes in a library to break its documentation in subtle ways. For example, you might have the following snippet:

The function plus is defined recursively:

plus = fix plus (n m : nat) {struct n} : nat := match n with | 0 => m | S p => S (plus p m) end : nat → nat → nat Argument scopes are [nat_scope nat_scope]

If you rename plus to Nat.add and add a compatibility notation, this is what your documentation will silently become, with no error or warning to let you realize that something went wrong:

Notation plus := Init.Nat.add

This was such a common problem in the reference manual that we implemented workarounds to catch the most egregious cases (where changes caused snippets to print errors instead of executing successfully). But if you check in Alectryon's caches into source control, then the following will show up pretty clearly:

  "contents": "Print plus.",
  "messages": [
    {
      "_type": "message",
-     "contents": "plus = \nfix plus (n m : nat) {struct n} : nat := …"
+     "contents": "Notation plus := Nat.add"
    }

All these features are exposed through a command line interface documented in Alectryon's README. This project has been in development for over a year, but there's still lots of rough bits, so expect bugs and please report them!

Using Alectryon

Standalone usage

The easiest way to get started Alectryon is to use it very much like coqdoc, but using reStructuredText syntax in special comments delimited with (*| and |*), like in this hypothetical even.v document:

(*|
=======
 Title
=======

Prose. *Emphasis*; **strong emphasis**; ``code``; `coq code`; `link <url>`__.
|*)

Inductive Even : nat -> Prop :=
| EvenO : Even O
| EvenS : forall n, Even n -> Even (S (S n)).

… which can then be compiled into a static webpage using ../alectryon.py --frontend coq+rst --backend webpage even.v -o even.html.

This is what I did for FRAP and CPDT. For Software foundations and Flocq, I used a compatibility layer combining Alectryon to render the code and coqdoc to render the prose:

find . -name *.v -exec alectryon.py --frontend coqdoc --backend webpage {} \;

Authoring tips

There's a great reStructuredText primer on Sphinx's website, if you're new to this markup language (there's also an official quick-reference guide, which is as ugly as it is comprehensive). reStructuredText is no panacea, but it's a decent language with a good story about extensibility, and it's popular for writing documentation (Haskell, Agda, and Coq use it for their reference manuals).

If you use Emacs, you can install alectryon.el, a small Emacs library that makes it easy to toggle between reStructuredText and Coq:

(add-to-list 'load-path "path/to/alectryon/clone/")
(require 'alectryon)

With this, you'll get improved rendering of (*| … |*) comment markers, and you'll be able to toggle between reStructuredText and Coq with a simple press of C-c C-S-a. You probably also want to M-x package-install flycheck and pip3 install --user docutils, though neither of these are hard dependencies.

(Hi, reader! Are you thinking “why isn't this on MELPA?” Great question! It's because I haven't had the time to do it yet. But you can — yes, you! In exchange, I promise I'll sing your praises every time your name comes up in conversation — I might even refer to you as ‘writer-of-MELPA-recipes extraordinaire’.

Alternatively, if you're a member of this most distinguished category of people who write more grant proposals than Emacs Lisp programs, you should drop me a line: I'm on the academic job market this year, so we should chat!)

Integrated into a blog or manual

Alectryon is very easy to integrate with platforms and tools that support Sphinx or Docutils, like Pelican, readthedocs, Nikola, etc. (In the long run, I hope to migrate Coq's reference manual to Alectryon. It currently uses coqrst, a previous iteration of Alectryon that I wrote a few years ago based on coqtop instead of SerAPI).

For this blog, for example, I just added the following snippet to our pelicanconf.py:

import alectryon
import alectryon.docutils
from alectryon.html import ASSETS

# Register the ‘.. coq::’ directive
alectryon.docutils.register()

# Copy Alectryon's stylesheet
alectryon_assets = path.relpath(ASSETS.PATH, PATH)
STATIC_PATHS.append(alectryon_assets)
EXTRA_PATH_METADATA[alectryon_assets] = {'path': 'static/alectryon/'}

# Copy a custom Pygments theme with good contrast to theme/pygments
for pth in ("tango_subtle.css", "tango_subtle.min.css"):
    EXTRA_PATH_METADATA[path.join(alectryon_assets, pth)] = \
          {'path': path.join('theme/pygments/', pth)}

Similar steps would be needed for Sphinx, though using alectryon.sphinx.register() instead. I hear that there's work in progress to integrate with other blog platforms.

As a library

The choice of reStructuredText is a bit arbitrary, so it's not a hard dependency of Alectryon. It should be relatively straightforward to combine it with other input languages (like LaTeX, Markdown, etc.) — I just haven't found the time to do it. There's even an output mode that takes Coq fragments as input and produces individual HTML snippets for each, to make integration easier. See Alectryon's README for more info.

As an example, I made a compatibility shim for Coqdoc that uses Alectryon to render Coq code, responses, and goals, but calls to coqdoc to render the contents of (** … **) comments; look for coqdoc in file cli.py of the distribution to see how it works.

Writing Coq proofs in Coq+reST

In reStructuredText documents, code in .. coq:: blocks is executed at compilation time; goals and responses are recorded and displayed along with the code. Here's an example:

Inductive Even : nat -> Prop :=
| EvenO : Even O
| EvenS : forall n, Even n -> Even (S (S n)).

Fixpoint even (n : nat) : bool :=
  match n with
  | 0 => true
  | 1 => false | S (S n) => even n
  end.


n : nat, even n = true → Even n
IHn: n : nat, even n = true → Even n

n : nat, even n = true → Even n
IHn: n : nat, even n = true → Even n

even 0 = true → Even 0
IHn: n : nat, even n = true → Even n
even 1 = true → Even 1
IHn: n0 : nat, even n0 = true → Even n0
n: nat
even (S (S n)) = true → Even (S (S n))
IHn: n : nat, even n = true → Even n

true = true → Even 0
IHn: n : nat, even n = true → Even n
false = true → Even 1
IHn: n0 : nat, even n0 = true → Even n0
n: nat
even n = true → Even (S (S n))
IHn: n : nat, even n = true → Even n
H: true = true

Even 0
IHn: n : nat, even n = true → Even n
H: false = true
Even 1
IHn: n0 : nat, even n0 = true → Even n0
n: nat
H: even n = true
Even (S (S n))
IHn: n : nat, even n = true → Even n
H: true = true

Even 0
constructor.
IHn: n : nat, even n = true → Even n
H: false = true

Even 1
discriminate.
IHn: n0 : nat, even n0 = true → Even n0
n: nat
H: even n = true

Even (S (S n))
IHn: n0 : nat, even n0 = true → Even n0
n: nat
H: even n = true

Even n
auto. Qed.

Here is another example of highlighting:


(A : Type) (a : A), Some a = None → False
A: Type
a: A
H: Some a = None

False
A: Type
a: A
H: Some a = None

match Some a with | Some _ => False | None => True end
A: Type
a: A
s:= Some a: option A
H: s = None

match s with | Some _ => False | None => True end
A: Type
a: A
s: option A
H: s = None

match s with | Some _ => False | None => True end
A: Type
a: A
s: option A
H: s = None

True
first [exact I].
(λ (A : Type) (a : A) (H : Some a = None), let s := Some a in eq_ind_r (λ s0 : option A, match s0 with | Some _ => False | None => True end) I H)
Defined.
= λ (a : ?A) (H : Some a = None), match match H in (_ = y) return (y = Some a) with | eq_refl => eq_refl end in (_ = y) return match y with | Some _ => False | None => True end with | eq_refl => I end : a : ?A, Some a = None → False

Customizing the output

Directive arguments and special comments can be used to customize the display of Coq blocks. The documentation of Alectryon has details, but here are a few examples:

  • Run a piece of code silently:

    .. coq:: none
    
       Require Import Coq.Arith.Arith.
    
  • Start with all intermediate states shown, hide selectively:

    .. coq:: unfold
    
       Goal True /\ True. (* .fold *)
         split.
         - (* .fold *)
           idtac "hello". (* .no-goals *)
           apply I.
         - auto.
       Qed.
    

    TrueTrue

    True

    True

    True
    hello
    apply I.

    True
    auto. Qed.
  • Show only a message, hiding the input:

    .. coq::
    
       Compute (1 + 1). (* .unfold .messages *)
    
    = 2 : nat

    Of course, if you're going to hide the input but show some output (as with .no-input, .messages, or .goals), you'll need to add .unfold, since the usual way to show the output (clicking on the input) won't be available.

The default alectryon.css stylesheet supports two display modes: the proviola style (two windows side by side, with code shown on one side and goals on the other), and this blog's style (with goals shown alongside each fragment when the window is wide enough and below the input line otherwise). Both modes support clicking on an input line to show the output right below it. You can pick a mode by placing the

Some interesting technical bits

Future work

There are a few things that would improve the quality of the documents produced by Alectryon, but I don't have immediate plans to tackle all of them, mostly for lack of time:

Let me know if you're interested in tackling one of these. I'd love to work together or offer tips / pointers.