Built with Alectryon, running Coq+SerAPI. Bubbles () indicate interactive fragments: hover for details, tap to reveal contents. Use Ctrl+↑ Ctrl+↓ to navigate, Ctrl+🖱️ to focus. On Mac, use instead of Ctrl.

Using the marker-placement mini-language

To compile:

$ alectryon references.rst
    # ReST → HTML; produces ‘references.html’
$ DOCUTILSCONFIG=references.docutils.conf alectryon \
    references.rst -o references.xe.tex --latex-dialect xelatex
    # ReST → HTML; produces ‘references.xe.tex’

Inserting numbered references

Alectryon supports references to individual sentences and hypotheses within a code fragment. The easiest way to reference a sentence is to use :mref:`search-term`. Alectryon will search for that text and automatically add a label to the first matching sentence of the proof. For example:

plus_comm: forall n m : nat, n + m = m + n
n, m: nat

n + m = m + n
plus_comm: forall n m : nat, n + m = m + n
n, m: nat

n + m = m + n
plus_comm: forall n m : nat, n + m = m + n9
n, m: nat3
Heq: n = 04

2
0 + m = m + 05
plus_comm: forall n m : nat, n + m = m + n
n, m, n0: nat
Heq: n = S n07
S n0 + m = m + S n08
plus_comm: forall n m : nat, n + m = m + n
n, m: nat
Heq: n = 0

0 + m = m + 0
rewrite <- plus_n_O; reflexivity.

The Fixpoint command (1) indicates that we are beginning an inductive proof.

Optionally, the label can be picked manually, using :mref:`label <target>`:

The proof starts with a case analysis, indicated by “”.

Instead of whole sentences, is possible to refer to individual goals and hypotheses:

In the first case (2), we see the variable n in the context (3), and we see that it is 0 (4); notice how the conclusion of the first goal 5 does not mention n (it says 0 instead). In the second case 6, we see that n equals S n0 (7) and the conclusion (8) mentions S n0 instead of 0.

Note how the reference to a hypothesis of the second goal caused the whole hypothesis block to be unfolded (by default, hypotheses of secondary goals are folded).

As expected, referring multiple times to the same object creates a single marker, even using different references:

To allow forward- and back-references, counters are not reset from one block to the next:

  
plus_comm: forall n m : nat, n + m = m + n
n, m, n0: nat
Heq: n = S n0

S n0 + m = m + S n0
plus_comm: forall n m : nat, n + m = m + n
n, m, n0: nat
Heq: n = S n0

S (n0 + m) = m + S n0
plus_comm: forall n m : nat, n + m = m + n
n, m, n0: nat
Heq: n = S n0

S (m + n0) = m + S n0
plus_comm: forall n m : nat, n + m = m + n
n, m, n0: nat
Heq: n = S n0

m + S n0 = m + S n0
reflexivity. Qed.
  • Bullets (-, +, *) delimit subproofs (10, 11)
  • It all started at 1

Defining custom counter styles

Custom counter styles can be defined like using the .. role:: directive and the :counter-style: option:

Here is how it looks:

The following commands print information about an identifier α, print its definition β, and compute the type of a term γ or its reduction δ.

Nat.add : nat -> nat -> nat Nat.add is not universe polymorphic Arguments Nat.add (_ _)%nat_scope Nat.add is transparent Expands to: Constant Coq.Init.Nat.add
Nat.add = fix add (n m : nat) {struct n} : nat := match n with | 0 => m | S p => S (add p m) end : nat -> nat -> nat Arguments Nat.add (_ _)%nat_scope
2 + 3 : nat
= 5 : nat
= 5 : nat
= 5 : nat
= 5 : nat
= 5 : nat
= 5 : nat
= (fun n : nat => n + S n) 2 : nat

The second batch of commands perform reduction with a custom strategy: .

Each inline reference is a link to the corresponding code fragment.

Setting properties

Objects located using the marker-placement mini-language can be tagged with arbitrary properties by appending a [key]=val annotation to the placement expression. For example:

nat : Set

These properties can then be used within custom transforms. Out of the box, Alectryon only recognizes the [lang] annotation; if it is found, the corresponding code is highlighted using the Pygments lexer for that language.

Extraction Language Haskell.
add :: Nat -> Nat -> Nat add n m = case n of { O -> m; S p -> S (add p m)}

Inserting textual references

Instead of inserting a link to the relevant goal fragment, you can use the :mquote: role to insert a copy of a goal fragment inline. This only works for an input sentence, the conclusion or name of a goal, and the type, body, or name of a hypothesis:

The proof above had two cases: Heq: n = 0 (4) and Heq: n = S n0 (7). The second goal below is named gg. The last case of the proof below has two induction hypotheses: List.In a l -> List.In a l' and List.In a l' -> List.In a l''. The two permutation hypotheses are H and H0.

For conciseness, it is possible to define an alias of :mquote: that uses a fixed prefix. Notice how the second example overrides the .g part of the prefix, too.

The proof above had two cases: Heq: n = 0 and Heq: n = S n0. The second goal below is named gg. The last case of the proof below has two induction hypotheses: List.In a l -> List.In a l' and List.In a l' -> List.In a l''. The two permutation hypotheses are H and H0.

Newlines in quoted objects are removed, and line breaks are allowed:

Record P {A B: Type} :=
  { p_first: A;
    p_second: B }.

forall (A B : Type) (p : P), let a_first := p_first p in let b_second := p_second p in {| p_first := a_first; p_second := b_second |} = p

forall (A B : Type) (p : P), let a_first := p_first p in let b_second := p_second p in {| p_first := a_first; p_second := b_second |} = p
destruct p; reflexivity. Qed.

The first sentence is Record P {A B: Type} := { p_first: A; p_second: B }.. [1]

[1]The second sentence is Goal forall {A B} (p: @P A B), let a_first := p.(p_first) in let b_second := p.(p_second) in {| p_first := a_first; p_second := b_second |} = p.. Later on we'll see a message: Ignoring implicit binder declaration in unexpected position. [unexpected-implicit-declaration,syntax].

To preserve newlines, use the .. mquote:: directive instead:

Goal forall {A B} (p: @P A B),
  let a_first := p.(p_first) in
  let b_second := p.(p_second) in
  {| p_first := a_first;
     p_second := b_second |} = p.
Ignoring implicit binder declaration in unexpected position.
[unexpected-implicit-declaration,syntax]

There, too, you may want to define aliases:

H: Permutation l l'
H0: Permutation l' l''

Finally, you may chose a different Pygments lexer to highlight a quote. For example, here is a piece of Scheme code produced by Extraction:

(define add (lambdas (n m)
  (match n
     ((O) m)
     ((S p) `(S ,(@ add p m))))))

Customizing proof rendering (experimental)

References can also be used to customize the display of goals and hypotheses. In the following, hypotheses whose name start with l are omitted, and so are hypotheses named a and A. After the call to induction (12) the output is further limited to just goals 2 and 4, by excluding all goals and re-including only 2 and 4. In goal 4, hypotheses whose type is exactly list A are shown, regardless of previous status, so l, l', l'' are visible (13). Finally, the -.s(…).msg(…) annotation reduces output for the second line (Check …) to include only the warning that it produces (and not its regular output); and the -.s{Proof.} annotation completely hides the Proof. line.

Ignoring implicit binder declaration in unexpected position. [unexpected-implicit-declaration,syntax]

Permutation l l' -> List.In a l -> List.In a l'
H: Permutation l l'
IHPermutation: List.In a l -> List.In a l'
Hin: List.In a (x :: l)

gg
List.In a (x :: l')
l, l', l'': list A13
H: Permutation l l'
H0: Permutation l' l''
IHPermutation1: List.In a l -> List.In a l'
IHPermutation2: List.In a l' -> List.In a l''
Hin: List.In a l
List.In a l''
all: simpl in *; tauto. Qed.

Asserting properties of the output

A constant concern when displaying proof states to readers is that what is displayed to the user may go stale. Alectryon mitigates the issue by automatically collecting proof states, but simply recording the prover's output doesn't fully solve the issue. That is because the output of a command may change in an unexpected way, without raising an error. For example, we may have written, with an early version of Coq:

Notation plus := Nat.add

To show the recursive definition of addition. But as Coq's standard library got reorganized, the definition of plus changed to being an alias for Nat.add, making the output of Print plus uninteresting. In general, the recommended way to prevent this issue is by recording and versioning the prover's output using Alectryon caching facility (--cache-directory). For small checks, however, Alectryon provides the massert directive, which checks that all references in its body resolve to a part of Coq's output. For example, the following checks that plus is indeed an alias and Nat.add a Fixpoint.

Nat.add = fix add (n m : nat) {struct n} : nat := match n with | 0 => m | S p => S (add p m) end : nat -> nat -> nat Arguments Nat.add (_ _)%nat_scope