2015-05-12 04:58:15 +00:00
|
|
|
|
#+Title: Library Style Guidelines
|
|
|
|
|
#+Author: [[http://www.andrew.cmu.edu/user/avigad][Jeremy Avigad]]
|
|
|
|
|
|
|
|
|
|
Files in the Lean library generally adhere to the following guidelines
|
|
|
|
|
and conventions. Having a uniform style makes it easier to browse the
|
|
|
|
|
library and read the contents, but these are meant to be guidelines
|
|
|
|
|
rather than rigid rules.
|
|
|
|
|
|
2015-05-14 04:51:54 +00:00
|
|
|
|
** Identifiers and theorem names
|
2015-05-12 04:58:15 +00:00
|
|
|
|
|
|
|
|
|
We generally use lower case with underscores for theorem names and
|
|
|
|
|
definitions. Sometimes upper case is used for bundled structures, such
|
|
|
|
|
as =Group=. In that case, use CamelCase for compound names, such as
|
|
|
|
|
=AbelianGroup=.
|
|
|
|
|
|
|
|
|
|
We adopt the following naming guidelines to make it easier for users
|
|
|
|
|
to guess the name of a theorem or find it using tab completion. Common
|
|
|
|
|
"axiomatic" properties of an operation like conjunction or
|
|
|
|
|
multiplication are put in a namespace that begins with the name of the
|
|
|
|
|
operation:
|
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
import standard algebra.ordered_ring
|
|
|
|
|
open nat algebra
|
|
|
|
|
|
|
|
|
|
check and.comm
|
|
|
|
|
check mul.comm
|
|
|
|
|
check and.assoc
|
|
|
|
|
check mul.assoc
|
2015-05-12 13:38:16 +00:00
|
|
|
|
check @algebra.mul.left_cancel -- multiplication is left cancelative
|
2015-05-12 04:58:15 +00:00
|
|
|
|
#+END_SRC
|
|
|
|
|
In particular, this includes =intro= and =elim= operations for logical
|
|
|
|
|
connectives, and properties of relations:
|
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
import standard algebra.ordered_ring
|
|
|
|
|
open nat algebra
|
|
|
|
|
|
|
|
|
|
check and.intro
|
|
|
|
|
check and.elim
|
|
|
|
|
check or.intro_left
|
|
|
|
|
check or.intro_right
|
|
|
|
|
check or.elim
|
|
|
|
|
|
|
|
|
|
check eq.refl
|
|
|
|
|
check eq.symm
|
|
|
|
|
check eq.trans
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
For the most part, however, we rely on descriptive names. Often the
|
|
|
|
|
name of theorem simply describes the conclusion:
|
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
import standard algebra.ordered_ring
|
|
|
|
|
open nat algebra
|
|
|
|
|
|
|
|
|
|
check succ_ne_zero
|
|
|
|
|
check mul_zero
|
|
|
|
|
check mul_one
|
2015-05-12 13:38:16 +00:00
|
|
|
|
check @sub_add_eq_add_sub
|
|
|
|
|
check @le_iff_lt_or_eq
|
2015-05-12 04:58:15 +00:00
|
|
|
|
#+END_SRC
|
|
|
|
|
If only a prefix of the description is enough to convey the meaning,
|
|
|
|
|
the name may be made even shorter:
|
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
import standard algebra.ordered_ring
|
|
|
|
|
open nat algebra
|
|
|
|
|
|
2015-05-12 13:38:16 +00:00
|
|
|
|
check @neg_neg
|
|
|
|
|
check nat.pred_succ
|
2015-05-12 04:58:15 +00:00
|
|
|
|
#+END_SRC
|
|
|
|
|
When an operation is written as infix, the theorem names follow
|
|
|
|
|
suit. For example, we write =neg_mul_neg= rather than =mul_neg_neg= to
|
2015-05-12 13:38:16 +00:00
|
|
|
|
describe the patter =-a * -b=.
|
2015-05-12 04:58:15 +00:00
|
|
|
|
|
|
|
|
|
Sometimes, to disambiguate the name of theorem or better convey the
|
|
|
|
|
intended reference, it is necessary to describe some of the
|
|
|
|
|
hypotheses. The word "of" is used to separate these hypotheses:
|
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
import standard algebra.ordered_ring
|
|
|
|
|
open nat algebra
|
|
|
|
|
|
|
|
|
|
check lt_of_succ_le
|
2015-05-25 23:56:00 +00:00
|
|
|
|
check lt_of_not_ge
|
2015-05-12 04:58:15 +00:00
|
|
|
|
check lt_of_le_of_ne
|
|
|
|
|
check add_lt_add_of_lt_of_le
|
|
|
|
|
#+END_SRC
|
|
|
|
|
Sometimes abbreviations or alternative descriptions are easier to work
|
|
|
|
|
with. For example, we use =pos=, =neg=, =nonpos=, =nonneg= rather than
|
|
|
|
|
=zero_lt=, =lt_zero=, =le_zero=, and =zero_le=.
|
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
import standard algebra.ordered_ring
|
|
|
|
|
open nat algebra
|
|
|
|
|
|
|
|
|
|
check mul_pos
|
|
|
|
|
check mul_nonpos_of_nonneg_of_nonpos
|
|
|
|
|
check add_lt_of_lt_of_nonpos
|
|
|
|
|
check add_lt_of_nonpos_of_lt
|
|
|
|
|
-- END
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
These conventions are not perfect. They cannot distinguish compound
|
|
|
|
|
expressions up to associativity, or repeated occurrences in a
|
|
|
|
|
pattern. For that, we make do as best we can. For example, =a + b - b
|
2015-05-12 13:38:16 +00:00
|
|
|
|
= a= could be named either =add_sub_self= or =add_sub_cancel=.
|
2015-05-12 04:58:15 +00:00
|
|
|
|
|
|
|
|
|
Sometimes the word "left" or "right" is helpful to describe variants
|
|
|
|
|
of a theorem.
|
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
import standard algebra.ordered_ring
|
|
|
|
|
open nat algebra
|
|
|
|
|
|
|
|
|
|
check add_le_add_left
|
|
|
|
|
check add_le_add_right
|
|
|
|
|
check le_of_mul_le_mul_left
|
|
|
|
|
check le_of_mul_le_mul_right
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
2015-05-14 04:51:54 +00:00
|
|
|
|
** Line length
|
2015-05-12 04:58:15 +00:00
|
|
|
|
|
|
|
|
|
Lines should not be longer than 100 characters. This makes files
|
|
|
|
|
easier to read, especially on a small screen or in a small window.
|
|
|
|
|
|
2015-05-14 04:51:54 +00:00
|
|
|
|
** Header and imports
|
2015-05-12 04:58:15 +00:00
|
|
|
|
|
|
|
|
|
The file header should contain copyright information, a list of all
|
|
|
|
|
the authors who have worked on the file, and a description of the
|
|
|
|
|
contents. Do all =import=s right after the header, without a line
|
|
|
|
|
break. You can also open namespaces in the same block.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
/-
|
|
|
|
|
Copyright (c) 2015 Joe Cool. All rights reserved.
|
|
|
|
|
Released under Apache 2.0 license as described in the file LICENSE.
|
|
|
|
|
Author: Joe Cool.
|
|
|
|
|
|
|
|
|
|
A theory of everything.
|
|
|
|
|
-/
|
|
|
|
|
import data.nat algebra.group
|
|
|
|
|
open nat eq.ops
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
2015-05-14 04:51:54 +00:00
|
|
|
|
** Structuring definitions and theorems
|
2015-05-12 04:58:15 +00:00
|
|
|
|
|
|
|
|
|
Use spaces around ":" and ":=". Put them before a line break rather
|
|
|
|
|
than at the beginning of the next line.
|
|
|
|
|
|
|
|
|
|
Use two spaces to indent. You can use an extra indent when a long line
|
|
|
|
|
forces a break to suggest the the break is artificial rather than
|
|
|
|
|
structural, as in the statement of theorem:
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC lean
|
2015-05-12 13:38:16 +00:00
|
|
|
|
open nat
|
|
|
|
|
theorem two_step_induction_on {P : nat → Prop} (a : nat) (H1 : P 0) (H2 : P (succ 0))
|
2015-05-12 04:58:15 +00:00
|
|
|
|
(H3 : ∀ (n : nat) (IH1 : P n) (IH2 : P (succ n)), P (succ (succ n))) : P a :=
|
|
|
|
|
sorry
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
If you want to indent to make parameters line up, that is o.k. too:
|
|
|
|
|
#+BEGIN_SRC lean
|
2015-05-12 13:38:16 +00:00
|
|
|
|
open nat
|
|
|
|
|
theorem two_step_induction_on {P : nat → Prop} (a : nat) (H1 : P 0) (H2 : P (succ 0))
|
2015-05-12 04:58:15 +00:00
|
|
|
|
(H3 : ∀ (n : nat) (IH1 : P n) (IH2 : P (succ n)), P (succ (succ n))) :
|
|
|
|
|
P a :=
|
|
|
|
|
sorry
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
After stating the theorem, we generally do not indent the first line
|
|
|
|
|
of a proof, so that the proof is "flush left" in the file.
|
|
|
|
|
#+BEGIN_SRC lean
|
2015-05-12 13:38:16 +00:00
|
|
|
|
open nat
|
|
|
|
|
theorem nat_case {P : nat → Prop} (n : nat) (H1: P 0) (H2 : ∀m, P (succ m)) : P n :=
|
|
|
|
|
nat.induction_on n H1 (take m IH, H2 m)
|
2015-05-12 04:58:15 +00:00
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
When a proof rule takes multiple arguments, it is sometimes clearer, and often
|
|
|
|
|
necessary, to put some of the arguments on subsequent lines. In that case,
|
|
|
|
|
indent each argument.
|
|
|
|
|
#+BEGIN_SRC lean
|
2015-05-12 13:38:16 +00:00
|
|
|
|
open nat
|
|
|
|
|
axiom zero_or_succ (n : nat) : n = zero ∨ n = succ (pred n)
|
|
|
|
|
theorem nat_discriminate {B : Prop} {n : nat} (H1: n = 0 → B)
|
2015-05-12 04:58:15 +00:00
|
|
|
|
(H2 : ∀m, n = succ m → B) : B :=
|
2015-05-12 13:38:16 +00:00
|
|
|
|
or.elim (zero_or_succ n)
|
2015-05-12 04:58:15 +00:00
|
|
|
|
(take H3 : n = zero, H1 H3)
|
|
|
|
|
(take H3 : n = succ (pred n), H2 (pred n) H3)
|
|
|
|
|
#+END_SRC
|
|
|
|
|
Don't orphan parentheses; keep them with their arguments.
|
|
|
|
|
|
|
|
|
|
Here is a longer example.
|
|
|
|
|
#+BEGIN_SRC lean
|
2015-05-12 13:38:16 +00:00
|
|
|
|
import data.list
|
|
|
|
|
open list eq.ops
|
|
|
|
|
variable {T : Type}
|
|
|
|
|
local attribute mem [reducible]
|
|
|
|
|
local attribute append [reducible]
|
2015-05-12 04:58:15 +00:00
|
|
|
|
theorem mem_split {x : T} {l : list T} : x ∈ l → ∃s t : list T, l = s ++ (x::t) :=
|
|
|
|
|
list.induction_on l
|
|
|
|
|
(take H : x ∈ [], false.elim (iff.elim_left !mem_nil_iff H))
|
|
|
|
|
(take y l,
|
|
|
|
|
assume IH : x ∈ l → ∃s t : list T, l = s ++ (x::t),
|
|
|
|
|
assume H : x ∈ y::l,
|
|
|
|
|
or.elim (eq_or_mem_of_mem_cons H)
|
|
|
|
|
(assume H1 : x = y,
|
|
|
|
|
exists.intro [] (!exists.intro (H1 ▸ rfl)))
|
|
|
|
|
(assume H1 : x ∈ l,
|
|
|
|
|
obtain s (H2 : ∃t : list T, l = s ++ (x::t)), from IH H1,
|
|
|
|
|
obtain t (H3 : l = s ++ (x::t)), from H2,
|
|
|
|
|
have H4 : y :: l = (y::s) ++ (x::t), from H3 ▸ rfl,
|
|
|
|
|
!exists.intro (!exists.intro H4)))
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
A short definition can be written on a single line:
|
|
|
|
|
#+BEGIN_SRC lean
|
2015-05-12 13:38:16 +00:00
|
|
|
|
open nat
|
2015-05-12 04:58:15 +00:00
|
|
|
|
definition square (x : nat) : nat := x * x
|
|
|
|
|
#+END_SRC
|
|
|
|
|
For longer definitions, use conventions like those for theorems.
|
|
|
|
|
|
|
|
|
|
A "have" / "from" pair can be put on the same line.
|
2015-05-12 13:38:16 +00:00
|
|
|
|
#+BEGIN_SRC
|
2015-05-12 04:58:15 +00:00
|
|
|
|
have H2 : n ≠ succ k, from subst (ne_symm (succ_ne_zero k)) (symm H),
|
|
|
|
|
[...]
|
|
|
|
|
#+END_SRC
|
|
|
|
|
You can also put it on the next line, if the justification is long.
|
2015-05-12 13:38:16 +00:00
|
|
|
|
#+BEGIN_SRC
|
2015-05-12 04:58:15 +00:00
|
|
|
|
have H2 : n ≠ succ k,
|
|
|
|
|
from subst (ne_symm (succ_ne_zero k)) (symm H),
|
|
|
|
|
[...]
|
|
|
|
|
#+END_SRC
|
|
|
|
|
If the justification takes more than a single line, keep the "from" on the same
|
|
|
|
|
line as the "have", and then begin the justification indented on the next line.
|
2015-05-12 13:38:16 +00:00
|
|
|
|
#+BEGIN_SRC
|
2015-05-12 04:58:15 +00:00
|
|
|
|
have n ≠ succ k, from
|
|
|
|
|
not_intro
|
|
|
|
|
(take H4 : n = succ k,
|
|
|
|
|
have H5 : succ l = succ k, from trans (symm H) H4,
|
|
|
|
|
have H6 : l = k, from succ_inj H5,
|
|
|
|
|
absurd H6 H2)))),
|
|
|
|
|
[...]
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
When the arguments themselves are long enough to require line breaks, use
|
|
|
|
|
an additional indent for every line after the first, as in the following
|
|
|
|
|
example:
|
|
|
|
|
#+BEGIN_SRC lean
|
2015-05-12 13:38:16 +00:00
|
|
|
|
import data.nat
|
|
|
|
|
open nat eq
|
2015-05-12 04:58:15 +00:00
|
|
|
|
theorem add_right_inj {n m k : nat} : n + m = n + k → m = k :=
|
2015-05-12 13:38:16 +00:00
|
|
|
|
nat.induction_on n
|
2015-05-12 04:58:15 +00:00
|
|
|
|
(take H : 0 + m = 0 + k,
|
|
|
|
|
calc
|
2015-05-12 13:38:16 +00:00
|
|
|
|
m = 0 + m : symm (zero_add m)
|
2015-05-12 04:58:15 +00:00
|
|
|
|
... = 0 + k : H
|
2015-05-12 13:38:16 +00:00
|
|
|
|
... = k : zero_add)
|
2015-05-12 04:58:15 +00:00
|
|
|
|
(take (n : nat) (IH : n + m = n + k → m = k) (H : succ n + m = succ n + k),
|
|
|
|
|
have H2 : succ (n + m) = succ (n + k), from
|
|
|
|
|
calc
|
2015-05-12 13:38:16 +00:00
|
|
|
|
succ (n + m) = succ n + m : symm (succ_add n m)
|
2015-05-12 04:58:15 +00:00
|
|
|
|
... = succ n + k : H
|
2015-05-12 13:38:16 +00:00
|
|
|
|
... = succ (n + k) : succ_add n k,
|
2015-06-16 18:29:17 +00:00
|
|
|
|
have H3 : n + m = n + k, from succ.inj H2,
|
2015-05-12 04:58:15 +00:00
|
|
|
|
IH H3)
|
|
|
|
|
#+END_SRC lean
|
|
|
|
|
|
2015-05-14 04:51:54 +00:00
|
|
|
|
** Binders
|
2015-05-12 04:58:15 +00:00
|
|
|
|
|
|
|
|
|
Use a space after binders:
|
|
|
|
|
or this:
|
2015-05-12 13:42:41 +00:00
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
example : ∀ X : Type, ∀ x : X, ∃ y, (λ u, u) x = y :=
|
|
|
|
|
take (X : Type) (x : X), exists.intro x rfl
|
2015-05-12 04:58:15 +00:00
|
|
|
|
#+END_SRC
|
|
|
|
|
|
2015-05-14 04:51:54 +00:00
|
|
|
|
** Calculations
|
2015-05-12 04:58:15 +00:00
|
|
|
|
|
|
|
|
|
There is some flexibility in how you write calculational proofs. In
|
|
|
|
|
general, it looks nice when the comparisons and justifications line up
|
|
|
|
|
neatly:
|
2015-05-12 13:42:41 +00:00
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
import data.list
|
|
|
|
|
open list
|
|
|
|
|
variable {T : Type}
|
|
|
|
|
|
2015-05-12 04:58:15 +00:00
|
|
|
|
theorem reverse_reverse : ∀ (l : list T), reverse (reverse l) = l
|
|
|
|
|
| [] := rfl
|
|
|
|
|
| (a :: l) := calc
|
|
|
|
|
reverse (reverse (a :: l)) = reverse (concat a (reverse l)) : rfl
|
|
|
|
|
... = reverse (reverse l ++ [a]) : concat_eq_append
|
|
|
|
|
... = reverse [a] ++ reverse (reverse l) : reverse_append
|
|
|
|
|
... = reverse [a] ++ l : reverse_reverse
|
|
|
|
|
... = a :: l : rfl
|
2015-05-12 13:38:16 +00:00
|
|
|
|
#+END_SRC
|
2015-05-12 04:58:15 +00:00
|
|
|
|
To be more compact, for example, you may do this only after the first line:
|
2015-05-12 13:42:41 +00:00
|
|
|
|
#+BEGIN_SRC lean
|
|
|
|
|
import data.list
|
|
|
|
|
open list
|
|
|
|
|
variable {T : Type}
|
|
|
|
|
|
2015-05-12 04:58:15 +00:00
|
|
|
|
theorem reverse_reverse : ∀ (l : list T), reverse (reverse l) = l
|
|
|
|
|
| [] := rfl
|
|
|
|
|
| (a :: l) := calc
|
2015-05-12 13:38:16 +00:00
|
|
|
|
reverse (reverse (a :: l))
|
2015-05-12 04:58:15 +00:00
|
|
|
|
= reverse (concat a (reverse l)) : rfl
|
|
|
|
|
... = reverse (reverse l ++ [a]) : concat_eq_append
|
|
|
|
|
... = reverse [a] ++ reverse (reverse l) : reverse_append
|
|
|
|
|
... = reverse [a] ++ l : reverse_reverse
|
|
|
|
|
... = a :: l : rfl
|
|
|
|
|
#+END_SRC lean
|
|
|
|
|
|
2015-05-14 04:51:54 +00:00
|
|
|
|
** Sections
|
2015-05-12 04:58:15 +00:00
|
|
|
|
|
|
|
|
|
Within a section, you can indent definitions and theorems to make the
|
|
|
|
|
scope salient:
|
2015-05-12 13:42:41 +00:00
|
|
|
|
#+BEGIN_SRC lean
|
2015-05-12 04:58:15 +00:00
|
|
|
|
section my_section
|
|
|
|
|
variable A : Type
|
|
|
|
|
variable P : Prop
|
|
|
|
|
|
|
|
|
|
definition foo (x : A) : A := x
|
|
|
|
|
|
|
|
|
|
theorem bar (H : P) : P := H
|
|
|
|
|
end my_section
|
|
|
|
|
#+END_SRC
|
|
|
|
|
If the section is long, however, you can omit the indents.
|
|
|
|
|
|
|
|
|
|
We generally use a blank line to separate theorems and definitions,
|
|
|
|
|
but this can be omitted, for example, to group together a number of
|
|
|
|
|
short definitions, or to group together a definition and notation.
|
|
|
|
|
|
2015-05-14 04:51:54 +00:00
|
|
|
|
** Comments
|
2015-05-12 04:58:15 +00:00
|
|
|
|
|
|
|
|
|
Use comment delimeters =/-= =-/= to provide section headers and
|
|
|
|
|
separators, and for long comments. Use =--= for short or in-line
|
|
|
|
|
comments.
|