The derive
command automatically synthesizes a bunch of useful lemmas
given an inductive type declaration.
From elpi.apps Require Import derive.std.
#[module] derive Inductive peano := Zero | Succ (p : peano).
Print peano.peano.
(* Inductive peano : Set := Zero : peano | Succ : peano -> peano. *)
Eval compute in peano.eqb Zero (Succ Zero).
(* = false : bool *)
About peano.eqb_OK.
(*
peano.eqb_OK : forall x1 x2 : peano, reflect (x1 = x2) (peano.eqb x1 x2)
peano.eqb_OK is not universe polymorphic
Arguments peano.eqb_OK x1 x2
peano.eqb_OK is opaque
Expands to: Constant elpi.apps.derive.examples.readme.peano.eqb_OK
*)
See also examples/usage.v and tests/test_readme.v.
From elpi.apps Require Import derive.std.
sets globally
Uniform Inductive Parameters
.
See the documentation of that option in the Coq reference manual.
Using derive Inductive ty := ...
produces the inductive ty
, together with
derivations, all in the current scope. The #[module=<string>]
attriute can
be used to specify that the inductive and the derivations should be wrapped
in a module of the given name (the name of the inductive is used if no name
is specified).
When a wrapper module is generated, an alias (i.e., a notation) is generated
for the inductive to be accessible with its name, outside of the module scope.
This behaviour can be disabled by using the #[no_alias]
boolean attribute.
The #[prefix=<string>]
attribute can be used to specify a prefix for all the
derived definitions/lemmas.
Elpi's derive
app is a little framework to register derivations.
Currently there are 3 groups:
derive.std
contains well tested derivations including:eqb
andeqbOK
generate sound boolean equality test in linear time/space, see Practical and sound equality tests, automaticallyeqbOK
generates its soundness proof in linear time/spaceinduction
generates deep induction principles, see Stronger Induction Principles for Containersparam1
andparam2
generate the unary and binary parametricity translationsmap
map over a containerparam1_functor
functoriality lemmas (map over the param1 translation)lens
andlens_laws
generate lenses focusing on record fields and some equations governing setter/setters (aka record update syntax)
derive.legacy
contains derivations superseded bystd
:eq
andeqOK
generate sound equality tests in quadratic time/space, see Deriving proved equality tests in Coq-elpi
derive.experimental
contains derivations not suitable for mainstream use:idx2inv
generates an inductive type where indexes are replaced by non uniform parameters and equations
The elpi/
directory contains the elpi files implementing various automatic
derivation of terms. The corresponding .v files, defining the Coq commands,
are in theories/derive/
.
Single steps of the derivation are available as separate commands.
Only the main entry point derive
comes with an handy syntax; the other
commands have to be invoked mentioning Elpi
and only accept an already
declared inductive as input.
std (click to expand)
Map a container over its parameters.
Elpi derive.map list.
Check list_map : forall A B, (A -> B) -> list A -> list B.
See also theories/derive/lens.v for the Lens
definition and the support constants view
, set
and over
.
Record pa_record A := { f3 : peano; f4 : A; }.
Elpi derive.lens pa_record.
Check _f3 : forall A, Lens (pa_record A) (pa_record A) peano peano.
See also theories/derive/lens_laws.v for the statements of the 4 laws (set_set, view_set, set_view, exchange).
Elpi derive.lens_laws pa_record.
Check _f3_view_set : forall A (r : pa_record A) x, view _f3 (set _f3 x r) = x.
Unary parametricity translation.
Elpi derive.param1 nat.
Print is_nat. (*
Inductive is_nat : nat -> Type :=
| is_O : is_nat 0
| is_S : forall n : nat, is_nat n -> is_nat (S n) *)
Elpi derive.param1.functor is_list.
Check is_list_functor : forall A PA QA,
(forall x, PA x -> QA x) -> forall l, is_list A PA l -> list A QA l.
Elpi derive.param1.trivial is_nat.
Check is_nat_trivial : forall x : nat, { p : is_nat x & forall q, p = q }.
Check is_nat_inhab : forall x : nat, is_nat x.
Induction principle for T
based on is_T
Elpi derive.induction list.
Check list_induction :
forall (A : Type) (PA : A -> Type) P,
P (nil A) ->
(forall x : A, PA x -> forall xs, P xs -> P (cons A x xs)) ->
forall l, is_list A PA l -> P l.
The "name" of the constructor
Elpi derive.tag peano.
Check peano_tag : peano -> positive.
The types of the fields and the fields of each constructor
Elpi derive.fields peano.
Check peano_fields_t : positive -> Type.
Check peano_fields : forall (n:peano), peano_fields_t (peano_tag n).
Check peano_construct : forall (p: positive), peano_fields_t p -> Datatypes.option peano.
Check peano_constructP : forall (n:peano), peano_construct (peano_tag n) (peano_fields n) = Datatypes.Some n.
Equality test
Elpi derive.eqb peano.
Check peano_eqb : peano -> peano -> bool.
Two directions of the soundness proof
Elpi derive.eqbcorrect peano.
Check peano_eqb_correct : forall n m, peano_eqb n m = true -> n = m.
Check peano_eqb_refl : forall n, peano_eqb n n = true.
The soundness proof
Elpi derive.eqbOK peano.
Check peano_eqb_OK : forall n m, reflect (n = m) (peano_eqb n m).
Used by param1_trivial
, not interesting.
Elpi derive.param1.congr is_nat.
Check is_Succ congr : forall x (px qx : is_nat x),
px = qx ->
is_Succ x px = is_Succ x qx.
legacy (click to expand)
See Deriving proved equality tests in Coq-elpi: Stronger Induction Principles for Containers for a description of most of these components.
Given an inductive type it generates for each constructor a function that tests if a term is a specific constructor.
Example:
Elpi derive.isK list.
Print list_is_nil. (*
list_is_nil =
fun (A : Type) (i : list A) =>
match i with
| nil => true
| _ => false
end
*)
Given an inductive type it generates for each constructor K
and argument
i
of this constructor a function extracting that argument (provided enough
default values).
Elpi derive.projK Vector.t.
Check projcons1. (*
projcons1
: forall (A : Type) (H : nat),
A -> forall n : nat, Vector.t A n ->
Vector.t A H -> A
The intended use is to perform injection, i.e. one aleady has a term of the
shape K args
and can just use these args to provide the default values.
If the projected argument's type depends on the value of other arguments, then it
is boxed using existT
.
Check projcons3. (*
projcons3
: forall (A : Type) (H : nat),
A -> forall n : nat, Vector.t A n ->
Vector.t A H -> {i1 : nat & Vector.t A i1}
*)
injection H EqAB PL
given an equation H
of type EqAB
returns a list
of equations PL
. EqAB
is expected to be of the form K .. = K ..
for
a constructor K
.
coverage: does not do the smart thing when the obtained equations are like { i : nat & Vector.t A i } = ...
in which case, given that nat
is eqType
one could obtain systematically the two equalities.
Note: this is not a real derivation, since it generates no constant, but it a piece of code used by derivations.
discriminate H EqAB G PG
given an equation H
of type EqAB
and
a goal G
it provides a proof PG
. It asserts that EqAB
is of
the form K1 .. = K2 ..
when K1
is a constructor different from K2
.
Note: this is not a real derivation, since it generates no constant, but it a piece of code used by derivations.
We call a boolean congruence lemma an instance of the reflect
predicate
on a proposition K x1..xn = K y1..yn
and a boolean expression b1 && .. bn
.
Elpi derive.bcongr list.
Check nil_congr : forall A, reflect (@nil A = @nil A) true.
Check cons_congr :
forall A,
forall (x y : A) b1, reflect (x = y) b1 ->
forall (xs ys : list A) b2, reflect (xs = ys) b2 ->
reflect (cons x xs = cons y ys) (b1 && b2).
Generates a boolean comparison function.
Elpi derive.eq list.
Check list_eq. (*
list_eq
: forall A : Type,
(A -> A -> bool) -> list A -> list A -> bool
*)
Generates, for each constructor, the correctness lemma for the comparison function.
Elpi derive.eqK list.
Check eq_axiom_nil : forall A fa, axiom (list A) (list_eq A fa) (@nil A).
Check eq_axiom_cons : forall A fa,
forall x, axiom A fa x ->
forall xs, axiom (list A) (list_eq A fa) xs ->
axiom (list A) (list_eq A fa) (cons x xs).
Correctness of equality test using reified type information.
Elpi derive.eqcorrect list.
Check list_eq_correct :
forall A f l, is_list A (eq_axiom A f) l -> eq_axiom (list A) (list_eq A f) l.
Correctness of equality test.
Elpi derive.eqOK list.
Check list_eq_OK :
forall A f, (forall a, axiom A f a) -> (forall l, eq_axiom (list A) (list_eq A f) l).
This is the list of inductive types we use for testing, and the table with the result of each derivation (:sunny: = OK, :bug: = does not work but might, :cloud: = looks like this can't possible work)
Inductive empty := .
Inductive unit := tt.
Inductive peano := Zero | Succ (n : peano).
Inductive option A := None | Some (_ : A).
Inductive pair A B := Comma (a : A) (b : B).
Inductive seq A := Nil | Cons (x : A) (xs : seq A).
Inductive rose (A : Type) := Leaf | Node (sib : seq (rose A)).
Inductive nest A := NilN | ConsN (x : A) (xs : nest (pair A A)).
Fail Inductive bush A := BNil | BCons (x : A) (xs : bush (bush A)).
Inductive w A := via (f : A -> w A).
Inductive vect A : peano -> Type := VNil : vect A Zero | VCons (x : A) n (xs : vect A n) : vect A (Succ n).
Inductive dyn := box (T : Type) (t : T).
Inductive zeta Sender (Receiver := Sender) := Envelope (a : Sender) (ReplyTo := a) (c : Receiver).
Inductive beta (A : (fun x : Type => x) Type) := Redex (a : (fun x : Type => x) A).
Inductive iota := Why n (a : match n in peano return Type with Zero => peano | Succ _ => unit end).
Inductive large := K1 (_ : unit) | K2 (_ : unit) (_ : unit) | ...
Inductive prim_int := PI (i : Int63.int).
Inductive prim_float := PF (f : PrimFloat.float).
Record fo_record := { f1 : peano; f2 : unit; }.
Record pa_record A := { f3 : peano; f4 : A; }.
Record pr_record A := { pf3 : peano; pf4 : A; }. (* with primitive projections *)
Record dep_record := { f5 : peano; f6 : vect unit f5; }.
Variant enum := E1 | E2 | E3.
test | eq | param1 | map | induction | isK | projK | bcongr | eqK | eqcorrect | eqOK | lens_laws |
---|---|---|---|---|---|---|---|---|---|---|---|
empty | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
unit | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
peano | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
option | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
pair | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
seq | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
rose | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
nest | ☁️ | ☀️ | ☁️ | ☀️ | ☀️ | ☀️ | ☀️ | 🐛 | 🐛 | 🐛 | ☁️ |
w | ☁️ | ☀️ | 🐛 | ☀️ | ☀️ | ☀️ | ☀️ | 🐛 | 🐛 | 🐛 | ☁️ |
vect | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | 🐛 | 🐛 | 🐛 | 🐛 | ☁️ |
dyn | ☁️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | 🐛 | 🐛 | 🐛 | 🐛 | ☁️ |
zeta | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
beta | ☀️ | ☀️ | 🐛 | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | 🐛 | ☀️ | ☁️ |
iota | ☁️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ | 🐛 | ☁️ | ☁️ | ☁️ |
large | ☀️ | ☀️ | 🐛 | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
prim_int | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
prim_float | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ | ☁️ | ☁️ |
fo_record | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ |
pa_record | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ |
pr_record | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ |
dep_record | 🐛 | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | 🐛 | 🐛 | 🐛 | 🐛 | ☁️ |
enum | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☀️ | ☁️ |
test | functor | inhab | congr | trivial |
---|---|---|---|---|
is_empty | ☀️ | ☀️ | ☀️ | ☀️ |
is_unit | ☀️ | ☀️ | ☀️ | ☀️ |
is_peano | ☀️ | ☀️ | ☀️ | ☀️ |
is_option | ☀️ | ☀️ | ☀️ | ☀️ |
is_pair | ☀️ | ☀️ | ☀️ | ☀️ |
is_seq | ☀️ | ☀️ | ☀️ | ☀️ |
is_rose | ☀️ | ☀️ | ☀️ | ☀️ |
is_nest | 🐛 | 🐛 | ☁️ | ☁️ |
is_w | 🐛 | ☀️ | ☀️ | 🐛 |
is_vect | ☀️ | 🐛 | ☁️ | 🐛 |
is_dyn | ☀️ | ☁️ | ☁️ | 🐛 |
is_zeta | ☀️ | ☀️ | ☀️ | ☀️ |
is_beta | ☀️ | ☀️ | ☀️ | ☀️ |
is_iota | ☀️ | 🐛 | ☁️ | 🐛 |
is_large | ☀️ | ☀️ | 🐛 | 🐛 |
is_prim_int | ☀️ | ☀️ | ☀️ | ☀️ |
is_is_prim_float | ☀️ | ☀️ | ☀️ | ☀️ |
is_fo_record | ☀️ | ☀️ | ☀️ | ☀️ |
is_pa_record | ☀️ | ☀️ | ☀️ | ☀️ |
is_pr_record | ☀️ | ☀️ | ☀️ | ☀️ |
is_dep_record | ☀️ | 🐛 | ☀️ | 🐛 |
is_enum | ☀️ | ☀️ | ☀️ | ☀️ |
experimental (click to expand)
Inductive is_list A PA : list A -> Type :=
| nilR : is_list (@nil A)
| consR : forall a : A, PA a ->
forall xs : list A, is_list xs -> is_list (cons a xs).
Elpi derive.invert is_list.
Print is_list_inv. (*
Inductive is_list_inv (A : Type) (PA : A -> Type) (idx0 : list A) : Type :=
| nilR_inv : idx0 = nil -> is_list_inv A PA idx0
| consR_inv : forall a : A, PA a ->
forall xs : list A, is_list_inv A PA xs ->
idx0 = (cons a xs) ->
is_list_inv A PA idx0.
*)
Elpi derive.idx2inv is_list.
Check is_list_to_is_list_inv :
forall A PA l, is_list A PA l -> is_list_inv A PA l.
A derivation is made of:
- a file implementing the derivation
- a data base to carry some state
- a stand alone command
- a hook in the main derive procedure
At the light of that, here a typical derivation file myder.v
.
The first section
loads the standard derive code and declares the dependency the external file
myder.elpi
. The file derive_hook.elpi
contains a few data types needed
in order to register the derivation in the main derive loop.
From elpi.apps.derive Extra Dependency "derive_hook.elpi" as derive_hook.
From mypkg Extra Dependency "myder.elpi" as myder.
From elpi Require Import elpi.
From elpi.apps Require Import derive.
The database is typically a predicate myder
linking a type name to some
concept previously derived. We also need to know if we did already derive a
type, hence we declare a second predicate myder-done
(we could reuse the
former, but sometimes this is not easy, so here we are pedantic).
We like to prefix these data bases name with derive.
.
Elpi Db derive.mydb.db lp:{{
% [myder T D] links a type T to a derived concept D
pred myder o:gref, o:gref.
% [myder-done T] mean T was already derived
pred myder-done o:gref.
}}.
Then we build a standalone derivation accessible via the name derive.myder
which accumulates the external files declared before, the data base and
an entry point
Elpi Command derive.myder.
Elpi Accumulate File derive_hook.
Elpi Accumulate File myder.
Elpi Accumulate Db derive.mydb.db.
Elpi Accumulate lp:{{
main [str I] :- !, coq.locate I GR,
coq.gref->id GR Tname,
Prefix is Tname ^ "_",
derive.myder.main GR Prefix _.
main _ :- usage.
pred usage.
usage :- coq.error "Usage: derive.myder <object name>".
}}.
This is enough to run the derivation via something like
Elpi derive.myder nat.
. In order to have derive
run it one has to
accumulate some code on top of derive
itself.
Elpi Accumulate derive Db derive.myder.db.
Elpi Accumulate derive File myder.
Elpi Accumulate derive lp:{{
dep1 "myder" "somedep".
dep1 "myder" "someotherdep".
derivation
(indt T) Prefix % inputs
(derive "myder" % name (for dep1)
(derive.myder.main (indt T) Prefix) % code to run
(myder-done (indt T)) % idempotency test
).
}}.
First, one declares via dep1
the derivations that should run before, here somedep
and someotherdep
. derive
will compute a topological order and ensure
dependencies are run first.
Then one declares a derivation for a gref and a prefix. One can restrict
which grefs can be derived, here for example we make myder
only available
on indt
(inductive types, and not definitions or constructors).
Prefix
is a string, typically passed to the main code.
The the (derive ...)
tuple carrier the name of the derivation, already used
in dep1
and two predicates, one to run the derivation and one to
test if the derivation was already run.
The types for dep1
, derivation
and derive
are declared in
derive_hook.elpi
.
Finally, one is expected to Import
the myder.v
file in a derivation
group, for example better_std.v
would look like so:
From elpi.apps Require Export derive.
From elpi.apps Require Export
derive.map
derive.lens
derive.lens_laws
...
myder (* new derivation *)
.
So when the user Import
s better_std
he gets a fully loaded derive
.
The code of the derivation must be put in a namespace. So myder.elpi
should
look like so
namespace derive.myder {
pred main i:gref, i:string, o:list prop.
main GR Prefix Clauses :- std.do! [
... % synthesize Body and Type
Name is Prefix ^ "myconcept",
coq.ensure-fresh-global-id Name FName,
coq.env.add-const FName Body Type _ C,
Clauses = [myder-done GR, myder GR (const C)],
std.forall Clauses (x\
coq.elpi.accumulate _ "derive.myder.db" (clause _ _ x)
),
].
}
It is important that all clauses added to the database are also returned
(see the last argument of main
). Derive runs all derivations at once
and databases are updated only when the program ends. So derive will
assume, with =>
, the clauses generated by one derivation before running the
nest one.