Thank you for your interest in contributing to Seymour! We welcome all contributions from the community and appreciate your efforts to improve the project.
If you contribute to the project, do not worry about code style too much; none of the style rules is enforced by a linter; we will accept your PR regardless of style; Martin will eventually adjust it. The guideline below is written primarily to assist you in reading the code.
-
Implicit and typeclass arguments may be put after
variablebut explicit arguments may not be. Example and a non-example:variable {R : Type} [R : Ring] (a : R)While
{R : Type} [R : Ring]in encouraged, the explicit argument(a : R)is not allowed to be a global variable. As a result, when you call a function or a lemma, you can be sure that all required arguments are visible at the lines of given function or lemma, which is very important. -
We use global variables sparingly — usually for arguments that are used by the entire file or section.
- Lines should not be longer than 128 characters (see the vertical ruler in VS Code) unless the line contains a long URL.
- Starting a proof on a new line is recommended even in cases where the entire proof fits into the same line with the statement.
- When the header of a declaration does not fit into one line, it is recommended to start the part after
:on a new line. - Make 2 empty lines between imports and the content of the file (unless it has
/-! a top-level comment section -/in which case 1 empty line goes above and 1 empty line goes below). - Make 1 empty line between the start of a section and a declaration.
- Make 1 empty line between declarations of any kind.
- Make 1 empty line between a declaration and the end of a section.
- Make 2 empty lines between the end of a section and what follows.
- Each file ends with a linebreak.
- We use the same indentation as Mathlib.
- Subitem: 2 spaces
- Continuation of the same item after a linebreak: 4 spaces
- Deviations from the style are welcome if they hint some metaïnformation.
-
There is always a space before a bracket (of any kind) opens and after it closes. Example:
foo {R : Type} [R : Ring] (a b : R) : ⟨a, b⟩ ∈ S -
Space after a brace opens and before a brace closes is written only in the
Setbuilder and inSubtypedeclarations. Examples:{ x : X | ∃ y : Y, Bar x y }{ x₁ : α × β₁ // f x₁.fst = ◩x₁.snd } -
With a comma, a space is written only after it, not before.
-
With a colon, a space is written from both sides.
- Use
camelCaseandPascalCaseandsnake_casethe same way Mathlib uses them.- Constants that live in
Sort(i.e., newTypeandPropdeclarations) are written inPascalCase - All other constants that live in
Type(e.g. functions that return real numbers) are written incamelCase - Constants that live in
Prop(i.e., theorems and lemmas) are written insnake_casewhose constituents are other constants converted tocamelCaseand other standard tokens likeextorcancel
- Constants that live in
- We like to write
.in the name of a constant if it facilitates the use of dot notation when calling it. Usually it means that the part of the name that comes before the last.is identical to the type of the first explicit argument.- In such a case, the parts before the last
.are not converted tocamelCase
- In such a case, the parts before the last
-
Data variables (both in arguments and local variables) are always denoted by a single letter, possibly with other stuff after it like lower index or apostrophe.
-
Prop variables (both in arguments and local variables) are always denoted by multiple letters, possibly with other stuff after them like lower index or apostrophe.
- Prefix
hmeans "the statement is anything about the following variables" - Not starting with
hmeans that the actual statement (or its substantial part) is spelled out, not only the variables that appear in it - We sometimes use the digit
9to refer to the number-1in the names of Prop variables (only there; writeneg_onein names of constants)
- Prefix
-
Examples:
intro a b a_lt_b habacarries data (e.g. an integer)bcarries data (e.g. an integer)a_lt_bcarries a proof ofa < bhabcarries any information aboutaandb
intro x y x_eq_y h1 h1'xcarries data (e.g. an integer)ycarries data (e.g. an integer)x_eq_ycarries a proof ofx = yh1carries any information involving the number1h1'carries another information involving the number1
intro M hM hM'Mcarries data (e.g. a matroid)hMcarries any information aboutMhM'carries another information aboutM
intro M M' hM hM'Mcarries data (e.g. a matroid)M'carries data (e.g. a matroid)hMcarries any information aboutMhM'carries any information aboutM'
intro M₁ M₂ hM₁ hM₂ hMMM₁carries data (e.g. a matroid)M₂carries data (e.g. a matroid)hM₁carries any information aboutM₁hM₂carries any information aboutM₂hMMcarries any information about bothM₁andM₂(indices afterhMMare not needed)
-
Never name anything
thisor standaloneh(these names are acceptable neither for data nor for propositions), but leaving automatically named stuff withthisorhis encouraged if the term is not explicitly referred to later.- Writing names like
h₁orh'orthis'orthis_is strongly discouraged regardless of the purpose
- Writing names like
-
Real-life example (focus on the third line of each version – explicit arguments of the lemma):
- Unacceptable argument names:
lemma Multiset.sum_lt_sum {ι α : Type} [OrderedCancelAddCommMonoid α] {s : Multiset ι} {f g : ι → α} (h : ∀ i ∈ s, f i ≤ g i) (h' : ∃ i ∈ s, f i < g i) : (s.map f).sum < (s.map g).sum := by obtain ⟨l⟩ := s simp only [Multiset.quot_mk_to_coe'', Multiset.map_coe, Multiset.prod_coe] exact l.sum_lt_sum f g h h'
- Bad argument names:
lemma Multiset.sum_lt_sum {ι α : Type} [OrderedCancelAddCommMonoid α] {s : Multiset ι} {f g : ι → α} (hle : ∀ i ∈ s, f i ≤ g i) (hlt : ∃ i ∈ s, f i < g i) : (s.map f).sum < (s.map g).sum := by obtain ⟨l⟩ := s simp only [Multiset.quot_mk_to_coe'', Multiset.map_coe, Multiset.prod_coe] exact l.sum_lt_sum f g hle hlt
- Acceptable argument names:
lemma Multiset.sum_lt_sum {ι α : Type} [OrderedCancelAddCommMonoid α] {s : Multiset ι} {f g : ι → α} (hfg : ∀ i ∈ s, f i ≤ g i) (hfg' : ∃ i ∈ s, f i < g i) : (s.map f).sum < (s.map g).sum := by obtain ⟨l⟩ := s simp only [Multiset.quot_mk_to_coe'', Multiset.map_coe, Multiset.prod_coe] exact l.sum_lt_sum f g hfg hfg'
- Better argument names:
lemma Multiset.sum_lt_sum {ι α : Type} [OrderedCancelAddCommMonoid α] {s : Multiset ι} {f g : ι → α} (all_f_le_g : ∀ i ∈ s, f i ≤ g i) (exists_f_lt_g : ∃ i ∈ s, f i < g i) : (s.map f).sum < (s.map g).sum := by obtain ⟨l⟩ := s simp only [Multiset.quot_mk_to_coe'', Multiset.map_coe, Multiset.prod_coe] exact l.sum_lt_sum f g all_f_le_g exists_f_lt_g
- Best argument names:
lemma Multiset.sum_lt_sum {ι α : Type} [OrderedCancelAddCommMonoid α] {s : Multiset ι} {f g : ι → α} (all_le : ∀ i ∈ s, f i ≤ g i) (exists_lt : ∃ i ∈ s, f i < g i) : (s.map f).sum < (s.map g).sum := by obtain ⟨l⟩ := s simp only [Multiset.quot_mk_to_coe'', Multiset.map_coe, Multiset.prod_coe] exact l.sum_lt_sum f g all_le exists_lt
- Unacceptable argument names:
A... matrixB... standard representation matrixC... circuitD... dependent setE... ground setF... fieldG... baseI... independent setJ... independent setM... matroidO... moduleP... propositionQ... propositionR... (semi)ringS... standard representation of a matroidX... set of row indicesY... set of col indicesZ... subset of indices
-
Do not open namespaces (but
open scopedis fine). -
Do not utilize automatic opening of a namespace for the declaration's name prefix.
For example, do not write:
def List.uppcaseLetters (l : List Char) := map Char.toUpper l
Either write down the full name of the function:
def List.uppcaseLetters (l : List Char) := List.map Char.toUpper l
Or, even better, utilize the dot notation:
def List.uppcaseLetters (l : List Char) := l.map Char.toUpper
This way:
- Reading the code is immediately clear without reading ahead and performing unification inside the reader's mind.
- The effect of the function will not change when you rename it (and the same applies to proofs).
Since deviation from the recommended style can be hard to spot here, we use the
implicitNamespacelinter by Damiano Testa to check that the automatically opened namespace hasn't been exploited. -
Annotate types even in situations where they can be inferred automatically, for example:
def List.uppcaseLetters (l : List Char) : List Char := l.map Char.toUpper
The only exception is decidability assumptions such as
[∀ a, Decidable (a ∈ Z)]whereZ : Set _because it appears all the time (we do not annotate the type ofa),
- We currently prefer
Typeover universe-polymorphic types. Generalization toType*will probably be done at the end of the project. - We prefer
UnitoverFin 1and we prefer⟨⟩over()for its only value. - We prefer not to write parentheses after quantifiers.
- We do not write a space after
¬but we write redundant parentheses around the negated expression when it contains any infix operator. - Orphaning parentheses is allowed.
- We like to use our custom notation declared at the beginning of the Basic file.
- We do not write
.1and.2to access fields; write their names instead (with the exception forIff.mpandIff.mprwhere we prefer our notation.→and.←respectively). - We prefer Mathlib's
haveover Std'shaveinside tactic-block proofs. - We prefer Mathlib's
congr_argandcongr_funover Std'scongrArgandcongrFunrespectively. - We prefer
exfalsooverabsurdto discharge "impossible" proof branches. - When a variable name refers to exactly 2 out of 3 objects, we often refer to the missing object (instead of the two present objects) (analogical to naming parts of a triangle).
- We do not write a space after
←in therwsyntax. - We do not write
↦as this syntax does not work everywhere. Write=>instead. - When passing arguments to
simpand other tactics that don't care about in which order they come, pass variables first, constants later. - Ordering of cases when eliminating
SignTypeis: firstzero(0), thenpos(1), thenneg(-1).