Try to keep your lines at most 80 characters long.
- CamelCase for Coq files, example:
StateMachineHandlerMonad.v - lowercase with dashes for scripts, example:
proof-linter.sh - UPPERCASE with underscores for documentation, example:
CODING_CONVENTIONS.md
- general pattern:
From VLSM.X Require Import Module_Name1 Module_Name2.Example:
From VLSM.Lib Require Import Preamble ListExtras StdppListSet.- in case of Stdlib imports, the pattern should not include the full logical paths
Example:
From Coq Require Import FunctionalExtensionality Lia.- exception to the rule regarding Stdlib imports: Imports from
Programpreserve their entire path.
Example:
From Coq Require Export Program.Tactics.To allow using important features of Equations, such as inspect and the eq: notation,
which have not been released yet, we require users of the Equations plugin to write
From VLSM.Lib Require Import EquationsExtras.instead of
From Equations Require Import Equations.- C-style name prefixed with "sec_" (the role of the prefix is to prevent name collisions for cross-references in coqdoc documentation; it should not appear in names of non-sections)
- parts of section names can still be in CamelCase if it refers to an identifier written in CamelCase
Example:
Section sec_step_relations.Example (ELMOComponent is a previously defined identifier):
Section sec_ELMOComponent_lemmas.- CamelCase for class (type) name
- CamelCase for constructor name using prefix
mk(when construction viaInstancemight not be sufficient) - field declaration with C-style naming on separate line, with 2 spaces of indentation
- the
;in the last field should not be omitted - it's recommended to include the sort annotation, especially when it's
Prop
Example:
Class TotalOrder {A} (R : relation A) : Prop :=
{
total_order_partial :> PartialOrder R;
total_order_trichotomy :> Trichotomy (strict R);
}.- C-style names
- field declaration with C-style naming on separate line
Example with fully tactic-based definition:
Instance base_params (p : param) : BaseParams.Example with Program environment:
Program Instance base_params (p : param) : BaseParams := {
param_fst := _;
param_snd := foo_bar x _;
}.- C-style type name
- CamelCase constructors
- to avoid name clashes, constructor names can be prefixed with an abbreviation of the type name followed by
_ - no indentation in constructor declarations
- the first
|in constructor declarations should not be omitted - it's recommended to include the sort annotation, especially when it's
Prop
Example:
Inductive lv_event_type : Type :=
| State
| Sent
| Received.- C-style name
- if a definition doesn't fit into a single line, indent its body with 2 spaces when starting on the next line.
- generally avoid unnecessary type declarations for quantified variables
Example:
Definition lv_message_observations (s : state) (target : index) : set lv_event :=
set_union (lv_sent_observations s target) (lv_received_observations s target).- When explicitly defining a constant that lives in
Prop, avoid usingDefinitionwith:=. Instead useLemma(orTheorem,Proposition, etc.) and provide the constant term body using theexacttactic, while indicating the opacity withQedorDefined.
Not recommended:
Definition finite_trace_partial_map_app
: forall l1 l2, finite_trace_partial_map (l1 ++ l2) =
finite_trace_partial_map l1 ++ finite_trace_partial_map l2
:= map_option_app _.Recommended:
Lemma finite_trace_partial_map_app :
forall l1 l2 : list (transition_item TX),
finite_trace_partial_map (l1 ++ l2) =
finite_trace_partial_map l1 ++ finite_trace_partial_map l2.
Proof. exact (map_option_app _). Qed.- Indent match expressions by 2 spaces.
- Do not indent the
|relative to the match keyword. - Avoid wasting space with indentation, i.e. when the match branch is long, put the result on the next line.
Recommended:
Fixpoint add_in_sorted_list_fn
{A} (compare : A -> A -> comparison) (x : A) (l : list A) : list A :=
match l with
| [] => [x]
| h :: t =>
match compare x h with
| Lt => x :: h :: t
| Eq => h :: t
| Gt => h :: @add_in_sorted_list_fn A compare x t
end
end.Not recommended:
Fixpoint add_in_sorted_list_fn
{A} (compare : A -> A -> comparison) (x : A) (l : list A) : list A :=
match l with
| [] => [x]
| h :: t => match compare x h with
| Lt => x :: h :: t
| Eq => h :: t
| Gt => h :: @add_in_sorted_list_fn A compare x t
end
end.- C-style name
- generally avoid unnecessary type declarations for quantified variables
- two spaces indentation before lemma type
Example:
Lemma sync_some (s : vstate X) (from to : index) :
sync s (get_matching_state s to from) to from <> None.- C-style for record (type) name
- CamelCase for constructor name using prefix
mk - field declaration with C-style naming on separate line, with 2 spaces of indentation
- the
;in the last field should not be omitted - it's recommended to include the sort annotation, especially when it's
Prop
Example:
Record simp_lv_event : Type := mkSimpObs
{
simp_lv_event_type : simp_lv_event_type;
simp_lv_event_subject : index;
simp_lv_event_state : @state index index_listing;
}.Locality annotations should be put on the same line as the command they annotate. This makes it easier to process them with line-based tools, like grep or sed.
Recommended:
#[export] Instance compare_eq_dec {A} `{CompareStrictOrder A} : EqDecision A.Not recommended:
#[export]
Instance compare_eq_dec {A} `{CompareStrictOrder A} : EqDecision A.In case you need to split a long line, keep the annotation on the same line as the command and the name. When splitting the above example:
Recommended:
#[export] Instance compare_eq_dec
{A} `{CompareStrictOrder A} : EqDecision A.Not recommended:
#[export] Instance
compare_eq_dec {A} `{CompareStrictOrder A} : EqDecision A.For multi-line coqdoc comments, place each of (** and *) on a separate line.
Use two spaces of indentation for the comment itself.
Not recommended:
(** Very very long
Coqdoc comment. *)Recommended:
(**
Very very long
Coqdoc comment.
*)Here are some rules that we found useful. Some of them are more strict (like using bullets), others are more like rules of thumb.
Bullets make the outline (subgoal structure) of the proof more explicit. The canonical order you should follow is (from top-most to bottom-most): -, +, *. If you run out of these, you can use --, ++, **, etc.
Bad:
tac.
sub1_tac.
sub2_tac.
sub2_1_tac.
sub2_2_tac.Good:
tac.
- sub1_tac.
- sub2_tac.
+ sub2_1_tac.
+ sub2_2_tac.You should use brackets for assert.
Bad:
assert (H : some_fact).
- tac1.
- tac2.Good:
assert (H : some_fact). {
tac1.
}
tac2.Using try may make refactoring proofs harder, because the refactoring can cause some goals to no longer be solved by the try. Instead, you should use [tac | ... | tac] or goal selectors.
Bad:
tac; try done.Better:
tac; [| done | ... | done |].Also better:
tac. 1-3,5,8-9,11: done.Sometimes existential tactics (whose name begin with an "e", for example eapply, econstructor) will generate a goal which is not immediately visible, but "shelved". It then needs to be unshelved using the Unshelve command and then proven.
Try to avoid this situation, as proofs with Unshelve can be problematic to maintain and refactor. The typical way to fix the problem is to provide more arguments to eapply or make sure that typeclass instance search works properly.
Some tactics which can generate side subgoals, like assert or rewrite, allow solving these subgoals on the fly with a by clause. Use this by clause if the subgoal is easy to solve.
Bad:
assert (H : some_fact X Y). {
done.
}Good:
assert (H : some_fact X Y) by done.Traditionally proofs of many goals end with the use of a low-level finisher tactic, like assumption, reflexivity, trivial, contradiction or discriminate. You should strongly avoid using these and instead use done from the stdpp library which subsumes them. done can also solve some goals that congruence can solve, but not all of them. In case done works, you should prefer it over congruence.
Bad:
tac.
- assumption.
- reflexivity.
- trivial.
- contradiction.
- discriminate.Less bad:
tac; done.You should avoid using tac; done and instead use by tac, which works exactly the same.
Good:
by tac.Note that the by tactic comes from stdpp. There is also a standalone by tactic in ssreflect, but you should strongly avoid using it (to avoid dependency on ssreflect).
Also note that the by tactic is not the same thing as the by clause that can be used with assert or rewrite. You should avoid using these two bys together to avoid confusion. In these cases you should use by (tac; done) instead of by by tac.
Bad:
assert (H : some_fact) by by tac.Good:
assert (H : some_fact) by (tac; done).Autogenerated names are often tricky and change in hard to predict ways when the code is changed. For example, changing the sort of a definition from Prop to Type changes the autogenerated names from H, H0, H1... to X, X0, X1.... Therefore, when using tactics like intros or destruct, you should name the hypotheses explicitly.
Bad:
intros.Good:
intros x y p H Heq Hlt.When declaring typeclass instances as (local) axioms, you should use implicit generalization instead of naming the instance.
Bad:
Context
(index : Type)
(EqDecision_index : EqDecision index).Good:
Context
(index : Type)
`(EqDecision index).Best:
Context
`(EqDecision index).When you want to finish off a goal using automation tactics, you should use the least powerful tactic that works. At first, try done, If it doesn't work, then try one of auto, congruence and lia. If these don't work, try itauto, itauto congruence or itauto lia. If these don't work, you can try combinations like itauto (auto || congruence) or similar. Finally, if none of these work, you can try smt and firstorder.
You should completely avoid using the tactic intuition and its variants, like tauto, intuition congruence, intuition lia, and especially you should not use intuition to preprocess a goal (destruct hypotheses, etc.).