From 2f1e02da9cd3c5e7f4bd87535ffac6decb9749a5 Mon Sep 17 00:00:00 2001
From: gares stlc.html
HOAS for Gallina
syntax tree of Coq terms.
global
Let's start with the gref data type (for global reference).
+Let's start with the gref data type (for global reference).
type const constant -> gref. type indt inductive -> gref. type indc constructor -> gref.-
constant, inductive and constructor are Coq specific -data types that are opaque to Elpi. Still the gref data type lets you +
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 coq.locate API resolves a string to a gref.
+The coq.locate API resolves a string to a gref.
An expression like indt «nat»
is not a Coq term (or better a type) yet.
The global term constructor turns a gref into an -actual term.
+The global term constructor turns a gref into an +actual term.
type global gref -> term.
app
and fun
The app term constructor takes a list of terms and builds +
The app term constructor takes a list of terms and builds the (n-ary) application. The first term is the head, while the others are the arguments.
For example app [global (indc «S»), global (indc «O»)]
is
@@ -235,30 +235,30 @@
"f" (const C),
coq.env.const C (some Bo) _
-}}.The fun constructor carries a pretty printing hint `x`,
+}}.
The fun constructor carries a pretty printing hint `x`,
the type of the bound variable nat
and a function describing the body:
type fun name -> term -> (term -> term) -> term.
Note
-name is just for pretty printing: in spite of carrying
+
name is just for pretty printing: in spite of carrying
a value in the Coq world, it has no content in Elpi (like the unit type)
-Elpi terms of type name are just identifiers
+
Elpi terms of type name are just identifiers
written between ` (backticks).
API such as coq.name-suffix lets one craft a family of
+}}.
API such as coq.name-suffix lets one craft a family of
names starting from one, eg coq.name-suffix `H` 1 N sets N
to `H1`.
fix
and match
The other binders prod (Coq's forall
, AKA Π
) and let are similar,
-so let's rather focus on fix here.
The other binders prod (Coq's forall
, AKA Π
) and let are similar,
+so let's rather focus on fix here.
c1 (fun `n` (global (indt «nat»)) c3 \ global (indt «nat»)) [c2, fun `p` (global (indt «nat»)) c3 \ - app [global (indc «S»), app [c0, c3, c2]]]
The fix constructor carries a pretty printing hint, + app [global (indc «S»), app [c0, c3, c2]]]
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
type fix name -> 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 @@ -311,13 +311,13 @@
global (indc «O»)]) c4 \ app [c1, c2]
Constructor sort
-The last term constructor worth discussing is sort.
+The last term constructor worth discussing is sort.
type sort universe -> term.
type prop universe.
type typ univ -> universe.
-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:
+coq.typecheck API:
The diagnostic data type is used by coq.typecheck to
+
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.
@@ -588,7 +588,7 @@ The context
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.
-The two predicates decl and def are used
+
The two predicates decl and def are used
for that purpose:
pred decl i:term, o:name, o:term. % Var Name Ty
@@ -636,9 +636,9 @@ The context
app [global (indc «S»), global (indc «O»)]]
Tip
-@pi-decl N Ty x\
takes arguments in the same order of fun and
-prod, while
-@pi-def N Ty Bo x\
takes arguments in the same order of let.
+@pi-decl N Ty x\
takes arguments in the same order of fun and
+prod, while
+@pi-def N Ty Bo x\
takes arguments in the same order of let.
@@ -685,7 +685,7 @@ The context
α7 := Type
WEAK CONSTRAINTS:
-Before the call to coq.typecheck, coq.sigma.print
+
Before the call to coq.typecheck, coq.sigma.print
prints nothing interesting, while after the call it also prints the following
syntactic constraint:
evar (X1) (global (indt «nat»)) (X1) /* suspended on X1 */
which indicates that the hole X1
is linked to a Coq evar
@@ -833,7 +833,7 @@
Outside the pattern fragment<
to be a special dummy constant, to be turned into an actual hole on the
fly when needed.
This use case is perfectly legitimate and is supported by all APIs taking
-terms in input thanks to the @holes! option.
+terms in input thanks to the @holes! option.
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 5336267c3..64d102900 100644
--- a/tutorial_coq_elpi_command.html
+++ b/tutorial_coq_elpi_command.html
@@ -187,9 +187,9 @@
Defining commands
The first one Elpi Command hello.
sets the current program to hello.
Since it is declared as a Command
some code is loaded automatically:
-- APIs (eg coq.say) and data types (eg Coq term s) are loaded from
+
- APIs (eg coq.say) and data types (eg Coq term s) are loaded from
coq-builtin.elpi
-- some utilities, like copy or whd1 are loaded from
+
- some utilities, like copy or whd1 are loaded from
elpi-command-template.elpi
The second one Elpi Accumulate ...
loads some extra code.
@@ -214,7 +214,7 @@
Defining commands
as (str "world!")
.
@@ -263,7 +263,7 @@ Command arguments
the type of the record (which was omitted) defaults to Type
.
Finally note that the type of the second field
sees c0
(the value of the first field).
-See the argument data type
+
See the argument data type
for a detailed description of all the arguments a command can receive.
Processing raw arguments
@@ -296,9 +296,9 @@ Processing raw arguments
"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
@@ -314,10 +314,10 @@
Processing raw arguments
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.
+coq.elaborate-skeleton API.
#[arguments(raw)]
Elpi Command elaborate_arg.
Elpi Accumulate lp:{{
@@ -355,8 +355,8 @@ Examples
Synthesizing a term
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.
@@ -388,20 +388,20 @@ Synthesizing a term
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.
Abstracting an inductive
-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.
@@ -458,7 +458,7 @@ Abstracting an inductive
Arguments tree' A%type_scope
Arguments leaf' A%type_scope
Arguments node' A%type_scope _ _ _As expected tree'
has a parameter A
.
-Now let's focus on copy. The standard
+
Now let's focus on copy. The standard
coq library (loaded by the command template) contains a definition of copy
for terms and declarations.
An excerpt:
@@ -480,19 +480,19 @@ Abstracting an inductive
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 option nat
. The API copy-indt-decl copies an inductive
+exactly option nat
. 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.
An excerpt:
fold-map (fun N T F) A (fun N T1 F1) A2 :-
fold-map T A T1 A1, pi x\ fold-map (F x) A1 (F1 x) A2.
-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.
@@ -505,7 +505,7 @@ Using DBs to store data across ca
Since is a Db is accumulated when a program runs the current
contents of the Db are used.
Moreover the Db can be extended by Elpi programs themselves
-thanks to the API coq.elpi.accumulate, enabling code to save a state
+thanks to the API coq.elpi.accumulate, enabling code to save a state
which is then visible at subsequent runs.
The initial contents of a Db, some code
in the example
above, is usually just the type declaration for the predicates part of the Db,
@@ -546,7 +546,7 @@
Using DBs to store data across ca
Extending data bases this way is fine, but requires the user of our command
to be familiar with Elpi's syntax, which is not very nice. Instead,
-we can write a new program that uses the coq.elpi.accumulate API
+we can write a new program that uses the coq.elpi.accumulate API
to extend the Db.
Elpi Command set_age.
Elpi Accumulate Db age.db.
@@ -564,9 +564,9 @@ Using DBs to store data across ca
instance: these object live inside a Coq module (or a Coq file) and become
active when that module is Imported.
Deciding to which Coq module these
-extra rules belong is important and coq.elpi.accumulate provides
+extra rules belong is important and coq.elpi.accumulate provides
a few options to tune that. Here we passed _
, that uses the default
-setting. See the scope and clause data types for more info.
+setting. See the scope and clause data types for more info.
Inspecting a Db
So far we did query a Db but sometimes one needs to inspect the whole
@@ -587,11 +587,11 @@
Using DBs to store data across ca
}}.
Elpi Typecheck.
-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
+
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.
@@ -620,8 +620,8 @@ Attributes
"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:
+using coq.parse-attributes and a description of their types using
+the attribute-type data type:
Elpi Command parse_attr.
Elpi Accumulate lp:{{
@@ -694,10 +694,10 @@ Reporting errors
error:
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).
@@ -822,13 +822,13 @@ Parsing and Execution
}}.
Elpi Typecheck.
-If the only data to be passed to the interp phase is the list of
+
If the only data to be passed to the interp phase is the list of
synterp actions, then a few APIs can come in handy.
-The synterp phase has access to the API coq.synterp-actions
+The synterp phase has access to the API coq.synterp-actions
that lists the actions performed so far. The interp phase can use
-coq.replay-synterp-action and coq.next-synterp-action to
+coq.replay-synterp-action and coq.next-synterp-action to
replay an action or peek the next one to be performed.
-An excerpt of the synterp-action.
+An excerpt of the synterp-action.
% Action executed during the parsing phase (aka synterp)
kind synterp-action type.
diff --git a/tutorial_coq_elpi_tactic.html b/tutorial_coq_elpi_tactic.html
index d7b9c8046..e7c5d666d 100644
--- a/tutorial_coq_elpi_tactic.html
+++ b/tutorial_coq_elpi_tactic.html
@@ -193,9 +193,9 @@ Defining tactics
The first one Elpi Tactic show.
sets the current program to show
.
Since it is declared as a Tactic
some code is loaded automatically:
-- APIs (eg coq.say) and data types (eg Coq term s) are loaded from
+
- APIs (eg coq.say) and data types (eg Coq term s) are loaded from
coq-builtin.elpi
-- some utilities, like copy or whd1 are loaded from
+
- some utilities, like copy or whd1 are loaded from
elpi-command-template.elpi
The second one Elpi Accumulate ...
loads some extra code.
@@ -214,8 +214,8 @@
Defining tactics
code does not contain the most frequent kind of mistakes. This command
considers some mistakes minor and 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
.
x, y: nat
x + 1 = y
See the argument data type
+
See the argument data type
for a detailed description of all the arguments a tactic can receive.
-Now let's write a tactic which behaves pretty much like the refine
-one from Coq, but prints what it does using the API coq.term->string.
+Now let's write a tactic which behaves pretty much like the refine
+one from Coq, but prints what it does using the API coq.term->string.
Elpi Tactic refine.
Elpi Accumulate lp:{{
solve (goal _ _ Ty _ [trm S] as G) GL :-
@@ -463,14 +463,14 @@ Ltac arguments to Elpi arguments<
-
Failure
-The coq.error aborts the execution of both
+
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.fail N
.
Elpi Tactic abort.
@@ -499,7 +499,7 @@ Examples
Let's code assumption
in Elpi
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).
Elpi Tactic assumption.
Elpi Accumulate lp:{{
@@ -521,9 +521,9 @@ Let's code Q
but the goal has type id Q
which is
convertible (unifiable, for Coq's unification) to Q
.
Let's improve our tactic by looking for an assumption which is unifiable with
-the goal, and not just alpha convertible. The coq.unify-leq
+the goal, and not just alpha convertible. The coq.unify-leq
calls Coq's unification for types (on which cumulativity applies, hence the
--leq
suffix). The std.mem utility, thanks to backtracking,
+-leq
suffix). The std.mem utility, thanks to backtracking,
eventually finds an hypothesis that satisfies the following predicate
(ie unifies with the goal).
Elpi Tactic assumption2.
@@ -541,7 +541,7 @@ Let's code Proof.
P, Q: Prop
p: P
q: Q
P
P, Q: Prop
p: P
q: Q
id Q
all: elpi assumption2.
-Qed.
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.
@@ -564,7 +564,7 @@ Let's code Let's code set
in Elpi
The set
tactic takes a term, possibly with holes, and
makes a let-in out of it.
-It gives us the occasion to explain the copy utility.
+It gives us the occasion to explain the copy utility.
Elpi Tactic find.
Elpi Accumulate lp:{{
@@ -584,7 +584,7 @@ Let's code P, Q: Prop
P /\ P \/ P /\ Q
Abort.
This first approximation only prints the term it found, or better the first
instance of the given term.
-Now lets focus on copy. An excerpt:
+Now lets focus on copy. An excerpt:
copy X X :- name X. % checks X is a bound variable
copy (global _ as C) C.
@@ -602,7 +602,7 @@ Let's code
copy (app [global (indt «andn»), sort prop, sort prop, c0, X0 c0 c1]) c2
-and that rule masks the one for app when the
+
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
@@ -719,9 +719,9 @@
Let's code X1
some procedure to
turn that value into X0
is triggered. That procedure is called
elaboration and it is currently implemented by calling the
-coq.elaborate-skeleton API.
+coq.elaborate-skeleton API.
Given this set up, it is impossible to use a term of the wrong type as a
-Proof. Let's rewrite the split
tactic without using refine.
+Proof. Let's rewrite the split
tactic without using refine.
Elpi Tactic split_ll.
Elpi Accumulate lp:{{
solve (goal Ctx Trigger {{ lp:A /\ lp:B }} Proof []) GL :- !,
@@ -743,7 +743,7 @@ Let's code Qed.
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).
-The coq.ltac.collect-goals API helps us doing this.
+The coq.ltac.collect-goals API helps us doing this.
Elpi Tactic split_ll_bis.
Elpi Accumulate lp:{{
solve (goal Ctx Trigger {{ lp:A /\ lp:B }} Proof []) GL :- !,
@@ -764,14 +764,14 @@ Let's code
True /\ True /\ ?t
True
True
?t
all: elpi blind.
-Qed.
At the light of that, refine is simply:
+Qed.
At the light of that, refine is simply:
refine T (goal _ RawEv _ Ev _) GS :-
RawEv = T, coq.ltac.collect-goals Ev GS _.
-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.
@@ -783,9 +783,9 @@ multi-goal tactics
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.
Elpi Tactic ngoals.
Elpi Accumulate lp:{{
@@ -821,9 +821,9 @@ multi-goal tactics
nabla c1 \
seal
(goal [decl c1 `Q` (sort prop), decl c0 `P` (sort prop)] (X2 c0 c1) c1
- (X3 c0 c1) [])]
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 pi x\
and loads the proof
context using =>
.
Operating on multiple goals at the same time is doable, but not easy.
@@ -896,7 +896,7 @@
LCF tacticals
A few tacticals can be found in the
elpi-ltac.elpi file.
-For example this is the code of try:
+For example this is the code of try:
pred try i:tactic, i:sealed-goal, o:list sealed-goal.
try T G GS :- T G GS.
@@ -949,9 +949,9 @@ Setting arguments for a tactic
thenl [ open (tac1 Datum), open (tac2 Datum) ]
-but the binder structure of sealed-goal would prevent Datum
+
but the binder structure of sealed-goal would prevent Datum
to mention proof variables, that would otherwise escape the sealing.
-The utility set-goal-arguments:
+The utility set-goal-arguments:
coq.ltac.set-goal-arguments Args G G1 G1wArgs
diff --git a/tutorial_elpi_lang.html b/tutorial_elpi_lang.html
index 8737a7679..659659365 100644
--- a/tutorial_elpi_lang.html
+++ b/tutorial_elpi_lang.html
@@ -218,8 +218,8 @@ Logic programming
is a mode declaration, which we will explain later (ignore it for now).
Note
-int is the built-in data type of integers
-Integers come with usual arithmetic operators, see the calc built-in.
+int is the built-in data type of integers
+Integers come with usual arithmetic operators, see the calc built-in.
In order to run our program we have to write a query,
i.e. a predicate expression containing variables such as:
@@ -247,14 +247,14 @@ Logic programming
age alice A, coq.say "The age of alice is" A
-}}.
coq.say is a built-in predicate provided by Coq-Elpi which
+}}.
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.
Note
-string is a built-in data type
+string is a built-in data type
Strings are delimited by double quotes and \ is the escape symbol.
The predicate age
represents a relation (in contrast to a function)
@@ -872,8 +872,8 @@
Constraint Handling Rules
Spilling (relation composition)
Chaining "relations" can be painful, especially when
-they look like functions. Here we use std.append
-and std.rev to build a palindrome:
+they look like functions. Here we use std.append
+and std.rev to build a palindrome:
Elpi Program function lp:{{
pred make-palindrome i:list A, o:list A.
@@ -926,20 +926,20 @@ Spilling (relation composition)<
APIs for built-in data
Functions about built-in data types are available via the
-calc predicate or its infix version is
. Example:
+calc predicate or its infix version is
. Example:
The calc predicate works nicely with spilling:
+}}.The calc predicate works nicely with spilling:
Allocation of variables
The language let's one use λ-abstraction also to write anonymous rules
but one has to be wary of where variables are bound (allocated really).
-In our example we use the higher order predicate std.map:
+In our example we use the higher order predicate std.map:
pred std.map i:list A, i:(A -> B -> prop), o:list B.
@@ -983,7 +983,7 @@ Allocation of variables
good2 [1,2,3] R3
}}.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
@@ -1156,7 +1156,7 @@
Trace browser
of (fun c0 \ fun c1 \ c0) X0
The trace can be limited to a range of steps. Look at the
numbers run HERE {{{.
@@ -1470,7 +1470,7 @@ Precedence of }}.
Backtracking
-Backtracking can lead to weird execution traces. The std.do! predicate
+
Backtracking can lead to weird execution traces. The std.do! predicate
should be used to write non-backtracking code.
pred not-a-backtracking-one.
@@ -1483,8 +1483,8 @@ Backtracking
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.