Awesome
Tricks in Coq
Some tips, tricks, and features in Coq that are hard to discover.
If you have a trick you've found useful feel free to submit an issue or pull request!
Meta
- Author(s):
- Tej Chajed (initial)
- Coq-community maintainer(s):
- Tej Chajed (@tchajed)
- License: The code in the repository is licensed under the terms of the MIT license. The documentation (including this README) is licensed under the CC0 license.
- Compatible Coq versions: Coq master
To build all the examples in src/
, run make
.
Ltac
- The
pattern
tactic generalizes an expression over a variable. For example,pattern y
transforms a goal ofP x y z
into(fun y => P x y z) y
. An especially useful way to use this is to pattern match oneval pattern y in constr:(P x y z)
to extract just the function. lazymatch
is likematch
but without backtracking on failures inside the match action. If you're not using failures for backtracking (this is often the case), thenlazymatch
gets better error messages because an error inside the action becomes the overall error message rather than the uninformative "no match" error message. (The semantics ofmatch
mean Coq can't do obviously do better - it can't distinguish between a bug in the action and intentionally using the failure to prevent pattern matching.)deex
(see Deex.v) is a useful tactic for extracting the witness from anexists
hypothesis while preserving the name of the witness and hypothesis.Ltac t ::= foo.
re-defines the tactict
tofoo
. This changes the binding, so tactics that refer tot
will use the new definition. You can use this for a form of extensibility: writeLtac hook := fail
and then use
in your tactic. Now the user can insert an extra case in your tactic's core loop by overridingrepeat match goal with | (* initial cases *) | _ => hook | (* other cases *) end
hook
.learn
approach - see Learn.v for a self-contained example or Clément's thesis for more detailsunshelve
tactical, especially useful with an eapply - good example use case is constructing an object by refinement where the obligations end up being your proofs with the values as evars, when you wanted to construct the values by proofunfold "+"
worksdestruct matches
tactic; see coq-tactical's SimplMatch.v- using
instantiate
to modify evar environment (thanks to Jonathan Leivent on coq-club) eexists ?[x]
lets one name an existential variable to be able to refer to it later- strong induction is in the standard library:
Require Import Arith.
and useinduction n as [n IHn] using lt_wf_ind.
- induction on the length of a list:
Require Import Coq.Arith.Wf_nat.
andinduction xs as [xs IHxs] using (induction_ltof1 _ (@length _)); unfold ltof in IHxs.
debug auto
,debug eauto
, anddebug trivial
give traces, including failed invocations.info_auto
,info_eauto
, andinfo_trivial
are less verbose ways to debug which only report what the resulting proof includesconstructor
andeconstructor
backtrack over the constructors over an inductive, which lets you do fun things exploring the constructors of an inductive type. See Constructors.v for some demonstrations.- There's a way to destruct and maintain an equality:
destruct_with_eqn x
. You can also dodestruct x eqn:H
to explicitly name the equality hypothesis. This is similar tocase_eq x; intros
; I'm not sure what the practical differences are. rew H in t
notation to useeq_rect
for a (safe) "type cast". Need to importEqNotations
- see RewNotation.v for a working example.intro
-patterns can be combined in a non-trivial way:intros [=->%lemma]
-- see IntroPatterns.v.change
tactic supports patterns (?var
): e.g.repeat change (fun x => ?f x) with f
would eta-reduce all the functions (of arbitrary arity) in the goal.- One way to implement a tactic that sleeps for n seconds is in Sleep.v.
- Some tactics take an "occurrence clause" to select where they apply. The common ones are
in *
andin H
to apply everywhere and in a specific hypotheses, but there are actually a bunch of forms, for example:in H1, H2
(justH1
andH2
)in H1, H2 |- *
(H1
,H2
, and the goal)in * |-
(just hypotheses)in |-
(nowhere)in |- *
(just the goal, same as leaving the whole thing off)in * |- *
(everywhere, same asin *
) These forms would be especially useful if occurrence clauses were first-class objects; that is, if tactics could take and pass occurrence clauses. Currently user-defined tactics support occurrence clauses via a set of tactic notations.
- Defining tactics (
Tactic Notation
s) that accept multiple optional parameters directly is cumbersome, but it can be done more flexibly using Ltac2. An example can be found in TacticNotationOptionalParams.v. - You can use notations to shorten repetitive Ltac patterns (much like Haskell's PatternSynonyms). Define a notation with holes (underscores) and use it in an Ltac match, eg
Notation anyplus := (_ + _).
and then
I would recommend usingmatch goal with | |- context[anyplus] => idtac end
Local Notation
so the notation isn't available outside the current file. - You can make all constructors of an inductive hints with
Hint Constructors
; you can also do this locally in a proof witheauto using t
wheret
is the name of the inductive. - The
intuition
tactic has some unexpected behaviors. It takes a tactic to run on each goal, which isauto with *
by default, using hints from all hint databases.intuition idtac
orintuition eauto
are both much safer. When using these, note thatintuition eauto; simpl
is parsed asintuition (eauto; simpl)
, which is unlikely to be what you want; you'll need to instead write(intuition eauto); simpl
. - The
Coq.Program.Tactics
library has a number of useful tactics and tactic helpers. Some gems that I like:add_hypothesis
is likepose proof
but fails if the fact is already in the context (a lightweight version of thelearn
approach);destruct_one_ex
implements the tricky code to eliminate anexists
while retaining names (it's a better version of ourdeex
);on_application
matches any application off
by simply handling a large number of arities. - You can structure your proofs using bullets. You should use them in the order
-
,+
,*
so that Proof General indents them correctly. If you need more bullets you can keep going with--
,++
,**
(although you should rarely need more then three levels of bullets in one proof). - You can use the
set
tactic to create shorthand names for expressions. These are speciallet
-bound variables and show up in the hypotheses asv := def
. To "unfold" these definitions you can dosubst v
(note the explicit name is required,subst
will not do this by default). This is a good way to make large goals readable, perhaps while figuring out what lemma to extract. It can also be useful if you need to refer these expressions. - When you write a function in proof mode (useful when dependent types are involved), you probably want to end the proof with
Defined
instead ofQed
. The difference is thatQed
makes the proof term opaque and prevents reduction, whileDefined
will simplify correctly. If you mix computational parts and proof parts (eg, functions which produce sigma types) then you may want to separate the proof into a lemma so that it doesn't get unfolded into a large proof term. - To make an evar an explicit goal, you can use this trick:
unshelve (instantiate (1:=_))
. The way this work is to instantiate the evar with a fresh evar (created due to the_
) and then unshelve that evar, making it an explicit goal. See UnshelveInstantiate.v for a working example. - The
enough
tactic behaves likeassert
but puts the goal for the stated fact after the current goal rather than before. - You can use
context E [x]
to bind a context variable, and thenlet e := eval context E [y] in ...
to substitute back into the context. See Context.v for an example. - If you have a second-order match (using
@?z x
, which bindz
to a function) and you want to apply the function, there's a trick involving a seemingly useless match. See LtacGallinaApplication.v for an example. auto with foo nocore
with the pseudo-databasenocore
disables the defaultcore
hint databases and only uses hints fromfoo
(and the context).- If you need to apply a theorem to a hypothesis and then immediately destruct the result, there's a concise way to do it without repetition:
apply thm in H as [x H]
, for example, might be used thenthm
produces an existential for a variable namedx
. - If you have a hypothesis
H: a = b
and needf a = f b
, you can useapply (f_equal f) in H
. (Strictly speaking this is just using thef_equal
theorem in the standard library, but it's also very much like the inverse direction for thef_equal
tactic.) - If you want to both run Ltac and return a
constr
, you can do so by wrapping the side effect inlet _ := match goal with _ => side_effect_tactic end in ...
. See https://stackoverflow.com/questions/45949064/check-for-evars-in-a-tactic-that-returns-a-value/46178884#46178884 for Jason Gross's much more thorough explanation. - If you want
lia
to help with non-linear arithmetic involving division or modulo (or the similarquot
andrem
), you can do that for simple cases withLtac Zify.zify_post_hook ::= Z.div_mod_to_equations.
See DivMod.v for an example and the micromega documentation the full details. - Coq's
admit
will force you to useAdmitted
. If you want to use Qed, you can instead useAxiom falso : False. Ltac admit := destruct falso.
This can be useful for debugging Qed errors (say, due to universes) or slow Qeds.
Gallina
-
tactics in terms, eg
ltac:(eauto)
can provide a proof argument -
maximally inserted implicit arguments are implicit even when for identifier alone (eg,
nil
is defined to include the implicit list element type) -
maximally inserted arguments can be defined differently for different numbers of arguments - undocumented but
eq_refl
provides an example -
r.(Field)
syntax: same asField r
, but convenient whenField
is a projection function for the (record) type ofr
. If you use these, you might also wantSet Printing Projections
so Coq re-prints calls to projections with the same syntax. -
Function
vernacular provides a more advanced way to define recursive functions, which removes the restriction of having a structurally decreasing argument; you just need to specify a well-founded relation or a decreasing measure maps to a nat, then prove all necessary obligations to show this function can terminate. See manual and examples in Function.v for more details.Three alternatives may be considered as drop-in replacements for
Function
.Program Fixpoint
may be useful when defining a nested recursive function. See manual and this StackOverflow post.- CPDT's way of defining general recursive functions with
Fix
combinator. - Equations is a plugin which extends Coq with new commands for defining recursive functions, and compiles everything down to eliminators for inductive types, equality and accessibility.
-
One can pattern-match on tuples under lambdas:
Definition fst {A B} : (A * B) -> A := fun '(x,_) => x.
-
Records fields can be defined with
:>
, which make that field accessor a coercion. There are three ways to use this (since there are three types of coercion classes). See Coercions.v for some concrete examples.- If the field is an ordinary type, the record can be used as that type (the field will implicitly be accessed). One good use case for this is whenever a record includes another record; this coercion will make the field accessors of the sub-record work for the outer record as well. (This is vaguely similar to Go embedded structs)
- If the field has a function type, the record can be called.
- If the field is a sort (eg,
Type
), then the record can be used as a type.
-
When a Class field (as opposed to a record) is defined with
:>
, it becomes a hint for typeclass resolution. This is useful when a class includes a "super-class" requirement as a field. For example,Equivalence
has fields for reflexivity, symmetry, and transitivity. The reflexivity field can be used to generically take anEquivalence
instance and get a reflexivity instance for free. -
The type classes in RelationClasses are useful but can be repetitive to prove. RelationInstances.v goes through a few ways of making these more convenient, and why you would want to do so (basically you can make
reflexivity
,transitivity
, andsymmetry
more powerful). -
The types of inductives can be definitions, as long as they expand to an "arity" (a function type ending in
Prop
,Set
, orType
). See ArityDefinition.v. -
"Views" are a programming pattern that can be used to perform a case analysis only on "relevant" parts of a datatype given the context of the case analysis. See Views.v.
-
Record fields that are functions can be written in definition-style syntax with the parameters bound after the record name, eg
{| func x y := x + y; |}
(see RecordFunction.v for a complete example). -
If you have a coercion
get_function : MyRecord >-> Funclass
you can useAdd Printing Coercion get_function
and then add a notation forget_function
so your coercion can be parsed as function application but printed using some other syntax (and maybe you want that syntax to beprinting only
). -
You can pass implicit arguments explicitly in a keyword-argument-like style, eg
nil (A:=nat)
. UseAbout
to figure out argument names. -
If you do nasty dependent pattern matches or use
inversion
on a goal and it produces equalities ofexistT
's, you may benefit from small inversions, described in this blog post. While the small inversion tactic is still not available anywhere I can find, some support is built in to Coq's match return type inference; see SmallInversions.v for examples of how to use that. -
You can use tactics-in-terms with notations to write function-like definitions that are written in Ltac. For example, you can use this facility to write macros that inspect and transform Gallina terms, producing theorem statements and optionally their proofs automatically. A simple example is given in DefEquality.v of writing a function that produces an equality for unfolding a definition.
-
Notations can be dangerous since they by default have global scope and are imported by
Import
, with no way to selectively import. A pattern I now use by default to make notations controllable is to define every notation in a module with a scope; see NotationModule.v.This pattern has several advantages:
- notations are only loaded as needed, preventing conflicts when not using the notations
- the notations can be brought into scope everywhere as needed with
Import
andLocal Open Scope
, restoring the convenience of a global notation - if notations conflict, some of them can always be scoped appropriately
-
Coq has a module system, modeled after ML (eg, the one used in OCaml). You can see some simple examples of using it in Modules.v. In user code, I've found module types and module functors to be more trouble than they're worth 90% of the time - the biggest issue is that once something is in a module type, the only way to extend it is with a new module that wraps an existing module, and the only way to use the extension is to instantiate it. At the same time, you can mostly simulate module types with records.
-
Coq type class resolution is extremely flexible. There's a hint database called
typeclass_instances
and typeclass resolution is essentiallyeauto with typeclass_instances
. Normally you add to this database with commands likeInstance
, but you can add whatever you want to it, includingHint Extern
s. See coq-record-update for a practical example. -
Classes are a bit special compared to any other type. First of all, in
(_ : T x1 x2)
Coq will only trigger type class resolution to fill the hole whenT
is a class. Second, classes get special implicit generalization behavior; specifically, you can write{T}
and Coq will automatically generalize the arguments to T, which you don't even have to write down. See the manual on implicit generalization for more details. Note that you don't have to useClass
at declaration time to make something a class; you can do it after the fact withExisting Class T
. -
Set Printing Projections
is nice in theory if you use records, but it interacts poorly with classes. See Projections.v.
Other Coq commands
-
Search
vernacular variants; see Search.v for examples. -
Search s -Learnt
for a search of local hypotheses excluding Learnt -
Locate
can search for notation, including partial searches. -
Optimize Heap
(undocumented) runs GC (specificallyGc.compact
) -
Optimize Proof
(undocumented) runs several simplifications on the current proof term (seeProofview.compact
) -
(in Coq 8.12 and earlier)
Generalizable Variable A
enables implicit generalization;Definition id `(x:A) := x
will implicitly add a parameterA
beforex
.Generalizable All Variables
enables implicit generalization for any identifier. Note that this surprisingly allows generalization without a backtick in Instances. Issue #6030 generously requests this behavior be documented, but it should probably require enabling some option. This has been fixed in Coq 8.13; the old behavior requiresSet Instance Generalized Output
. In Coq 8.14 the option has been removed. -
Check
supports partial terms, printing a type along with a context of evars. A cool example isCheck (id _ _)
, where the first underscore must be a function (along with other constraints on the types involved). -
The above also works with named existentials. For example,
Check ?[x] + ?[y]
works. -
Unset Intuition Negation Unfolding
will causeintuition
to stop unfoldingnot
. -
Definitions can be normalized (simplified/computed) easily with
Definition bar := Eval compute in foo.
-
Set Uniform Inductive Parameters
(in Coq v8.9+beta onwards) allows you to omit the uniform parameters to an inductive in the constructors. -
Lemma
andTheorem
are synonymous, except thatcoqdoc
will not show lemmas. Also synonymous:Corollary
,Remark
, andFact
.Definition
is nearly synonymous, except thatTheorem x := def
is not supported (you need to useDefinition
). -
Sections are a powerful way to write a collection of definitions and lemmas that all take the same generic arguments. Here are some tricks for working with sections, which are illustrated in Sections.v:
- Use
Context
, which is strictly more powerful thanVariable
- you can declare multiple dependent parameters and get type inference, and can write{A}
to make sure a parameter is implicit and maximally inserted. - Tactics and hints are cleared at the end of a section. This is often annoying but you can take advantage of it by writing one-off tactics like
t
that are specific to the automation of a file, and callers don't see it. Similarly with adding hints tocore
with abandon. - Use notations and implicit types. Say you have a section that defines lists, and you want another file with a bunch of list theorems. You can start with
Context (A:Type). Notation list := (List.list A). Implicit Types (l:list).
and then in the whole section you basically never need to write type annotations. The notation and implicit type disappears at the end of the section so no worries about leaking it. Furthermore, don't writeTheorem foo : forall l,
but instead writeTheorem foo l :
; you can often also avoid usingintros
with this trick (though be careful about doing induction and ending up with a weak induction hypothesis). - If you write a general-purpose tactic
t
that solves most goals in a section, it gets annoying to writeProof. t. Qed.
every time. Instead, defineNotation magic := ltac:(t) (only parsing).
and writeDefinition foo l : l = l ++ [] := magic.
. You do unfortunately have to writeDefinition
;Lemma
andTheorem
do not support:=
definitions. You don't have to call itmagic
but of course it's more fun that way. Note that this isn't the best plan because you end up with transparent proofs, which isn't great; ideally Coq would just supportTheorem foo :=
syntax for opaque proofs.
- Use
-
Haskell has an operator
f $ x
, which is the same asf x
except that its parsed differently:f $ 1 + 1
meansf (1 + 1)
, avoiding parentheses. You can simulate this in Coq with a notation:Notation "f $ x" := (f x) (at level 60, right associativity, only parsing).
(from jwiegley/coq-haskell). -
A useful convention for notations is to have them start with a word and an exclamation mark. This is borrowed from @andres-erbsen, who borrowed it from the Rust macro syntax. An example of using this convention is in Macros.v. There are three big advantages to this approach: first, using it consistently alerts readers that a macro is being used, and second, using names makes it much easier to create many macros compared to inventing ASCII syntax, and third, starting every macro with a keyword makes them much easier to get parsing correctly.
-
To declare an axiomatic instance of a typeclass, use
Declare Instance foo : TypeClass
. This better than the pattern ofAxiom
+Existing Instance
. -
To make Ltac scripts more readable, you can use
Set Default Goal Selector "!".
, which will enforce that every Ltac command (sentence) be applied to exactly one focused goal. You achieve that by using a combination of bullets and braces. As a result, when reading a script you can always see the flow of where multiple goals are generated and solved. -
Arguments foo _ & _
(in Coq 8.11) adds a bidirectionality hint saying that an application offoo
should infer a type from its arguments after typing the first argument. See Bidirectional.v for an example and the latest Coq documentation. -
Coq 8.11 introduced compiled interfaces, aka
vos
files (as far as I can tell they are a more principled replacement forvio
files). Suppose you make a change deep down toLib.v
and want to start working onProof.v
which importsLib.v
through many dependencies. Withvos
files, you can recompile all the signatures thatProof.v
depends on, skippinng proofs, and keep working. The basic way to use them is to compileProof.required_vos
, a special dependencycoqdep
generates that will build everything needed to work onProof.v
. Coq natively looks forvos
files in interactive mode, and uses emptyvos
files to indicate that the file is fully compiled in avo
file.Note that Coq also has
vok
files; it's possible to check the missing proofs in avos
file, but this does not produce avo
and so all Coq can do is record that the proofs have been checked. They can also be compiled in parallel within a single file, although I don't know how to do that. Compilingvok
s lets you fairly confidently check proofs, but to really check everything (particularly universe constraints) you need to buildvo
files from scratch.Signature files have one big caveat: if Coq cannot determine the type of a theorem or the proof ends with
Defined
(and thus might be relevant to later type-checking), it has to run the proof. It does so silently, potentially eliminating any performance benefit. The main reason this happens is due to proofs in a section that don't annotate which section variables are used withProof using
. Generally this can be fixed withSet Default Proof Using "Type"
, though only on Coq 8.12+. -
Coq 8.12 has a new feature
Set Printing Parentheses
that prints parentheses as if no notations had an associativity. For example, this will print(1,2,3)
as((1,2),3)
. This is much more readable than entirely disabling notations. -
You can use
Export Set
or#[export] Set
to set options in a way that affects any file directly importing the file (but not transitively importing, the wayGlobal Set
works). This allows a project to locally set up defaults with anoptions.v
file with all of its options, which every file imports. You can use this for basic sanity settings, likeSet Default Proof Using "Type".
andSet Default Goal Selector "!"
without forcing them on all projects that import your project. -
You can use
all: fail "goals remaining".
to assert that a proof is complete. This is useful when you'll useAdmitted.
but want to document (and check in CI) that the proof is complete other than theadmit.
tactics used. -
You can also use
Fail idtac.
to assert that a proof is complete, which is shorter than the above but more arcane. -
You can use
Fail Fail Qed.
to really assert that a proof is complete, including doing universe checks, but then still be able toRestart
it. I think this is only useful for illustrating small examples but it's amusing that it works. (The newSucceed
vernacular in Coq 8.15 is a better replacement, because it preserves error messages on failure.) -
Hints can now be set to global, export, or local. Global (the current default) means the hint applies to any module that transitivity imports this one; export makes the hint visible only if the caller imports this module directly. The behavior will eventually change for hints at the top-level so that they become export instead of global (see https://github.com/coq/coq/pull/13384), so this might be worth understanding now. HintLocality.v walks through what the three levels do.
-
The uniform inheritance condition for coercions is gone as of March 2022. This condition required that all the implicit arguments of the target of a coercion could be inferred from the source, so if the source had extra implicit arguments the definition could not be a coercion. This is no longer the case - elaboration will infer the missing arguments. There's still a warning but you can disable it with
#[warning="-uniform-inheritance"]
on the coercion.
Warnings and options
- You can use
Set Warnings "-deprecated-syntactic-definition"
to achieve the same effect as-w -deprecated-syntactic-definition
from the command line, but from within a Coq file. As of Coq 8.18 you can also use#[warning="-deprecated-syntactic-definition"]
to disable the warning for one command.
Using Coq
- You can pass
-noinit
tocoqc
orcoqtop
to avoid loading the standard library. - Ltac is provided as a plugin loaded by the standard library; if you have
-noinit
to load it you needDeclare ML Module "ltac_plugin".
(see NoInit.v). - Numeral notations are only provided by the prelude, even if you issue
Require Import Coq.Init.Datatypes
. - If you use Coq master, the latest Coq reference manual is built and deployed to https://coq.github.io/doc/master/refman/index.html automatically.
- At Qed, Coq re-checks the proof term, which can take a while. At the same
time, Qed rarely fails - some reasons why it might include that there's a
universe inconsistency, the coinductive guarded check fails, there's a bug in
an OCaml tactic, or there's an incorrect use of
exact_no_check
. It's not so easy to disable Qed - in Perennial we do so using this disable-qed.sh script, which replacesQed
withUnshelve. Fail idtac. Admitted.
. This still ensures there are no goals, but skips the Qed checking. We only skip Qed during CI, but run it regularly locally to have a fully consistent build; another good tradeoff would be to run with Qed checking on a weekly basis but not on every commit.