constant, inductive and constructor are Coq specific
+data types that are opaque to Elpi. Still the gref data type lets you
see what these names point to (a constant, and inductive type or a
constructor).
The fix constructor carries a pretty printing hint,
+ app [global (indc «S»), app [c0, c3, c2]]]
C = «Nat.add»
The fix constructor carries a pretty printing hint,
the number of the recursive argument (starting at 0), the type
of the recursive function and finally the body where the recursive
call is represented via a bound variable
typefixname->int->term->(term->term)->term.
-
A match constructor carries the term being inspected,
+
A match constructor carries the term being inspected,
the return clause
and a list of branches. Each branch is a Coq function expecting in input
the arguments of the corresponding constructor. The order follows the
@@ -327,13 +327,13 @@
The opaque type univ is a universe level variable. Elpi holds a store of
+
The opaque type univ is a universe level variable. Elpi holds a store of
constraints among these variables and provides APIs named coq.univ.* to
impose constraints.
Let's play a bit more with universe constraints using the
-coq.typecheck API:
The diagnostic data type is used by coq.typecheck to
tell if the term is well typed. The constructor ok signals success, while
error carries an error message. In case of success universe constraints
are added to the store.
In some sense Elpi's way of traversing a binder is similar to a Zipper.
The context of Elpi must record the part of the Zipper context that is
relevant for binders.
Note that after the call to coq.typecheck, X0 is assigned the
+
Note that after the call to coq.typecheck, X0 is assigned the
term _\X1, that means that the offending argument has been pruned
(discarded).
Note
-
All APIs taking a term support the @holes! option.
+
All APIs taking a term support the @holes! option.
-
In addition to the @holes! option, there is a class of APIs which can
+
In addition to the @holes! option, there is a class of APIs which can
deal with terms outside the pattern fragment. These APIs take in input a term
-skeleton. A skeleton is not modified in place, as coq.typecheck
+skeleton. A skeleton is not modified in place, as coq.typecheck
does with its first argument, but is rather elaborated to a term related to it.
In some sense APIs taking a skeleton are more powerful, because they can
modify the structure of the term, eg. insert a coercions, but are less
diff --git a/tutorial_coq_elpi_command.html b/tutorial_coq_elpi_command.html
index edbe9c5eb..1bac97d44 100644
--- a/tutorial_coq_elpi_command.html
+++ b/tutorial_coq_elpi_command.html
@@ -187,9 +187,9 @@
"true" : "bool"
The 3rd term has type "bool" which should be a subtype of "nat".
The command check_arg receives a term T and type checks it, then it
prints the term and its type.
-
The coq.typecheck API has 3 arguments: a term, its type and a
-diagnostic which can either be ok or (error Message).
-The assert-ok! combinator checks if the diagnostic is ok,
+
The coq.typecheck API has 3 arguments: a term, its type and a
+diagnostic which can either be ok or (error Message).
+The assert-ok! combinator checks if the diagnostic is ok,
and if not it prints the error message and bails out.
The first invocation succeeds while the second one fails and prints
the type checking error (given by Coq) following the string passed to
@@ -315,10 +315,10 @@
The command still fails even if we told Coq how to inject booleans values
into the natural numbers. Indeed the Check commands works.
-
The call to coq.typecheck modifies the term in place, it can assign
+
The call to coq.typecheck modifies the term in place, it can assign
implicit arguments (like the type parameter of eq) but it cannot modify the
structure of the term. To do so, one has to use the
-coq.elaborate-skeleton API.
Synthesizing a term typically involves reading an existing declaration
and writing a new one. The relevant APIs are in the coq.env.* namespace
-and are named after the global reference they manipulate, eg coq.env.const
-for reading and coq.env.add-const for writing.
+and are named after the global reference they manipulate, eg coq.env.const
+for reading and coq.env.add-const for writing.
Here we implement a little command that given an inductive type name
generates a term of type nat whose value is the number of constructors
of the given inductive type.
The command starts by locating the first argument and asserting it points to
-an inductive type. This line is idiomatic: coq.locate aborts if
+an inductive type. This line is idiomatic: coq.locate aborts if
the string cannot be located, and if it relates it to a gref which is not
-indt (for example const plus) assert! aborts with the given
+indt (for example const plus) assert! aborts with the given
error message.
-
coq.env.indt lets one access all the details of an inductive type,
+
coq.env.indt lets one access all the details of an inductive type,
here we just use the list of constructors.
-The twin API coq.env.indt-decl lets
+The twin API coq.env.indt-decl lets
one access the declaration of the inductive in HOAS form, which might be
easier to manipulate in other situations, like the next example.
Then the program crafts a natural number and declares a constant for it.
For the sake of introducing copy, the swiss army knife of λProlog, we
+
For the sake of introducing copy, the swiss army knife of λProlog, we
write a command which takes an inductive type declaration and builds a new
one abstracting the former one on a given term. The new inductive has a
parameter in place of the occurrences of that term.
copy (app [global (indt «option»), global (indt «nat»)]) c0
and that rule masks the one for app when the sub-term being copied is
-exactly optionnat. The API copy-indt-decl copies an inductive
+exactly optionnat. The API copy-indt-decl copies an inductive
declaration and calls copy on all the terms it contains (e.g. the
type of the constructors).
-
The copy predicate is very flexible, but sometimes one needs to collect
-some data along the way. The sibling API fold-map lets one do that.
+
The copy predicate is very flexible, but sometimes one needs to collect
+some data along the way. The sibling API fold-map lets one do that.
For example one can use fold-map to collect into a list all the occurrences
+
For example one can use fold-map to collect into a list all the occurrences
of inductive type constructors in a given term, then use the list to postulate
-the right number of binders for them, and finally use copy to capture them.
+the right number of binders for them, and finally use copy to capture them.
The initial contents of a Db, predsome-pred. in the example
above, is usually just the type declaration for the predicates part of the Db,
@@ -547,7 +547,7 @@
The std.findall predicate gathers in a list all solutions to
-a query, while std.forall iterates a predicate over a list.
-It is important to notice that coq.error is a fatal error which
+
bob is24 years old
alice is21 years old
The std.findall predicate gathers in a list all solutions to
+a query, while std.forall iterates a predicate over a list.
+It is important to notice that coq.error is a fatal error which
aborts an Elpi program. Here we shadow the catch all clause with a regular
-failure so that std.findall can complete to list all the results.
+failure so that std.findall can complete to list all the results.
"this" holding the empty string and an attribute for "more.stuff" holding
the string "33".
Attributes are usually validated (parsed) and turned into regular options
-using coq.parse-attributes and a description of their types using
-the attribute-type data type:
The elpi tactic/command bad failed without giving a
specific error message. Please report this
-inconvenience to the authors of the program.
You should use the coq.error API or the assert! one
-to abort a program. There is a dedicated coq.ltac.fail API to abort
+inconvenience to the authors of the program.
You should use the coq.error API or the assert! one
+to abort a program. There is a dedicated coq.ltac.fail API to abort
tactics.
-
Warnings can be reported using the coq.warning which lets you
+
Warnings can be reported using the coq.warning which lets you
pick a name and category. In turn these can be used to disable or make fatal
your warnings (as any other Coq warning).
type checking and linting. Some mistakes are minor and Elpi only warns about
them. You can pass -w+elpi.typecheck to coqc to turn these warnings into
errors.
-
The entry point for tactics is called solve which maps a goal
-into a list of sealed-goal (representing subgoals).
+
The entry point for tactics is called solve which maps a goal
+into a list of sealed-goal (representing subgoals).
Tactics written in Elpi can be invoked by prefixing its name with elpi.
The _Trigger component, which we did not print, is a variable that, when
assigned, triggers the elaboration of its value against the type of the goal
and obtains a value for Proof this way.
-
Keeping in mind that the solve predicate relates one goal to a list of
+
Keeping in mind that the solve predicate relates one goal to a list of
subgoals, we implement our first tactic which blindly tries to solve the goal.
For a simple tactic like blind the list of subgoals is easy to write, since
it is empty, but in general one should collect all the holes in
the value of Proof (the checked proof term) and build goals out of them.
-
There is a family of APIs named after refine, the mother of all
+
There is a family of APIs named after refine, the mother of all
tactics, in
elpi-ltac.elpi
which does this job for you.
Usually a tactic builds a (possibly partial) term and calls
-refine on it.
terms are received in raw format, eg before elaboration
Indeed the type argument to eq is a variable.
-One can use APIs like coq.elaborate-skeleton to infer holes like
+One can use APIs like coq.elaborate-skeleton to infer holes like
X0.
The coq.error aborts the execution of both
Elpi and any enclosing Ltac context. This failure cannot be caught
by Ltac.
-
On the contrary the coq.ltac.fail builtin can be used to
+
On the contrary the coq.ltac.fail builtin can be used to
abort the execution of Elpi code in such a way that Ltac can catch it.
This API takes an integer akin to Ltac's fail depth together with
the error message to be displayed to the user.
-
Library functions of the assert! family call, by default, coq.error.
+
Library functions of the assert! family call, by default, coq.error.
The flag @ltacfail!N can be set to alter this behavior and turn errors into
calls to coq.ltac.failN.
assumption is a very simple tactic: we look up in the proof
context for an hypothesis which unifies with the goal.
-Recall that Ctx is made of decl and def
+Recall that Ctx is made of decl and def
(here, for simplicity, we ignore the latter case).
refine does unify the type of goal with the type of the term,
+
Qed.
refine does unify the type of goal with the type of the term,
hence we can simplify the code further. We obtain a
tactic very similar to our initial blind tactic, which picks
candidates from the context rather than from the program itself.
and that rule masks the one for app when the
sub-term being copied matches (P/\_). The first time this rule
is used X0 is assigned, making the rule represent the term (P/\P).
Now let's refine the tactic to build a let-in, and complain if the
@@ -720,9 +720,9 @@
Crafting by hand the list of subgoal is not easy.
In particular here we did not set up the new trigger for Pa and Pb,
nor seal the goals appropriately (we did not bind proof variables).
refine T (goal _RawEv_Ev_)GS:-RawEv=T, coq.ltac.collect-goals EvGS_.
-
Now that we know the low level plumbing, we can use refine ;-)
+
Now that we know the low level plumbing, we can use refine ;-)
The only detail we still have to explain is what exactly a
-sealed-goal is. A sealed goal wraps into a single object all
+sealed-goal is. A sealed goal wraps into a single object all
the proof variables and the assumptions about them, making this object easy
(or better, sound) to pass around.
if the tactic is a multi-goal one, it will receive all goals
In Elpi you can implement a multi-goal tactic by providing a rule for
-the msolve predicate. Since such a tactic will need to manipulate
+the msolve predicate. Since such a tactic will need to manipulate
multiple goals, potentially living in different proof context, it receives
-a list of sealed-goal, a data type which seals a goal and
+a list of sealed-goal, a data type which seals a goal and
its proof context.
nabla binds all proof variables, then seal
+ (X3 c0 c1) [])]
nabla binds all proof variables, then seal
holds a regular goal, which in turn carries the proof context.
-
In order to operate inside a goal one can use the coq.ltac.open utility,
+
In order to operate inside a goal one can use the coq.ltac.open utility,
which postulates all proof variables using pix\ and loads the proof
context using =>.
Operating on multiple goals at the same time is doable, but not easy.
@@ -902,7 +902,7 @@
coq.say is a built-in predicate provided by Coq-Elpi which
+}}.
The age of alice is20
Query assignments:
A = 20
coq.say is a built-in predicate provided by Coq-Elpi which
prints its arguments.
You can look at the output buffer of Coq to see the value for A or hover
or toggle the little bubble after }}. if you are reading the tutorial with a
web browser.
Toplevel input, characters 441-442
R3 is linear: name it _R3 (discard) or R3_ (fresh variable)
[elpi.linear-variable,elpi.typecheck,elpi,default]
Query assignments:
R1 = X0
R2 = [2, 3, 4]
R3 = [2, 3, 4]
The problem with bad is that TMP is fresh each time the rule
-is used, but not every time the anonymous rule passed to map
+is used, but not every time the anonymous rule passed to map
is used. Technically TMP is quantified (allocated) where L
and Result are.
There are two ways to quantify TMP correctly, that is inside the
@@ -1549,7 +1549,7 @@
In the example above once condition holds we start a sequence of
steps which we will not reconsider. Locally, backtracking is still
available, e.g. between generate and test.
-See also the std.spy-do! predicate which prints each and every step,
-and the std.spy one which can be used to spy on a single one.
+See also the std.spy-do! predicate which prints each and every step,
+and the std.spy one which can be used to spy on a single one.