1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
|
(*|
Tutorial on the HOAS for Coq terms
**********************************
:author: Enrico Tassi
.. include:: ../etc/tutorial_style.rst
..
Elpi is an extension language that comes as a library
to be embedded into host applications such as Coq.
Elpi is a variant of λProlog enriched with constraints.
λProlog is a programming language designed to make it easy
to manipulate abstract syntax trees containing binders.
Elpi extends λProlog with programming constructs that are
designed to make it easy to manipulate abstract syntax trees
containing metavariables (also called unification variables, or
evars in the Coq jargon).
This software, "coq-elpi", is a Coq plugin embedding Elpi and
exposing to the extension language Coq specific data types (e.g. terms)
and API (e.g. to declare a new inductive type).
In order to get proper syntax highlighting using VSCode please install the
"gares.coq-elpi-lang" extension. In CoqIDE please chose "coq-elpi" in
Edit -> Preferences -> Colors.
This tutorial focuses on the integration of Elpi within Coq, in particular
it describes how Coq terms are exposed to Elpi programs and how Coq APIs can
be called.
This tutorial assumes the reader is familiar with Elpi and HOAS; if it is not
the case, please take a look at the
`Elpi tutorial <https://lpcic.github.io/coq-elpi/tutorial_elpi_lang.html>`_
first.
.. contents::
================
HOAS for Gallina
================
|*)
From elpi Require Import elpi. (* .none *)
Elpi Command tutorial_HOAS. (* .none *)
(*|
The full syntax of Coq terms can be found in
`coq-builtin.elpi <https://github.com/LPCIC/coq-elpi/blob/master/builtin-doc/coq-builtin.elpi>`_
together with a detailed documentation of the encoding of contexts and the
APIs one can use to interact with Coq. This tutorial, and the two more
that focus on commands and tactics, are a gentle introduction to all that.
We defer to later quotations and antiquotations: syntactic features that
let one write terms in Coq's native syntax. Here we focus on the abstract
syntax tree of Coq terms.
-----------------------
Constructor :e:`global`
-----------------------
Let's start with the :type:`gref` data type (for global reference).
.. code:: elpi
type const constant -> gref.
type indt inductive -> gref.
type indc constructor -> gref.
:type:`constant`, :type:`inductive` and :type:`constructor` are Coq specific
data types that are opaque to Elpi. Still the :type:`gref` data type lets you
see what these names point to (a constant, and inductive type or a
constructor).
The :builtin:`coq.locate` API resolves a string to a :type:`gref`.
|*)
Elpi Query lp:{{
coq.locate "nat" GRnat,
coq.locate "S" GRs,
coq.locate "plus" GRplus
}}.
(*|
The :e:`coq.env.*` family of APIs lets one access the
environment of well typed Coq terms that have a global name.
|*)
Definition x := 2.
Elpi Query lp:{{
coq.locate "x" GR,
% all global references have a type
coq.env.typeof GR Ty,
% destruct GR to obtain its constant part C
GR = const C,
% constants may have a body, do have a type
coq.env.const C (some Bo) TyC
}}.
(*|
An expression like :e:`indt «nat»` is not a Coq term (or better a type) yet.
The :constructor:`global` term constructor turns a :type:`gref` into an
actual :type:`term`.
.. code:: elpi
type global gref -> term.
----------------------------------
Constructors :e:`app` and :e:`fun`
----------------------------------
The :constructor:`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 :e:`app [global (indc «S»), global (indc «O»)]` is
the representation of `1`.
.. code:: elpi
type app list term -> term.
Let's move to binders!
|*)
Definition f := fun x : nat => x.
Elpi Query lp:{{
coq.locate "f" (const C),
coq.env.const C (some Bo) _
}}.
(*|
The :constructor:`fun` constructor carries a pretty printing hint ```x```,
the type of the bound variable `nat` and a function describing the body:
.. code:: elpi
type fun name -> term -> (term -> term) -> term.
.. note:: :type:`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 :type:`name` are just identifiers
written between ````` (backticks).
.. coq::
Elpi Query lp:{{
fun `foo` T B = fun `bar` T B % names don't matter
}}.
API such as :builtin:`coq.name-suffix` lets one craft a family of
names starting from one, eg ``coq.name-suffix `H` 1 N`` sets :e:`N`
to ```H1```.
------------------------------------
Constructors :e:`fix` and :e:`match`
------------------------------------
The other binders :constructor:`prod` (Coq's `forall`, AKA `Π`) and :constructor:`let` are similar,
so let's rather focus on :constructor:`fix` here.
|*)
Elpi Query lp:{{
coq.locate "plus" (const C),
coq.env.const C (some Bo) _
}}.
(*|
The :constructor:`fix` constructor carries a pretty printing hint,
the number of the recursive argument (starting at :e:`0`), the type
of the recursive function and finally the body where the recursive
call is represented via a bound variable
.. code:: elpi
type fix name -> int -> term -> (term -> term) -> term.
A :constructor:`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
order of the constructors in the inductive type declaration.
.. code:: elpi
type match term -> term -> list term -> term.
The return clause is represented as a Coq function expecting in input
the indexes of the inductive type, the inspected term and generating the
type of the branches.
|*)
Definition m (h : 0 = 1 ) P : P 0 -> P 1 :=
match h as e in eq _ x return P 0 -> P x
with eq_refl => fun (p : P 0) => p end.
Elpi Query lp:{{
coq.locate "m" (const C),
coq.env.const C (some (fun _ _ h\ fun _ _ p\ match _ (RT h p) _)) _,
coq.say "The return type of m is:" RT
}}.
(*|
---------------------
Constructor :e:`sort`
---------------------
The last term constructor worth discussing is :constructor:`sort`.
.. code:: elpi
type sort universe -> term.
type prop universe.
type typ univ -> universe.
The opaque type :type:`univ` is a universe level variable. Elpi holds a store of
constraints among these variables and provides APIs named :e:`coq.univ.*` to
impose constraints.
|*)
Elpi Query lp:{{
coq.sort.sup U U1,
coq.say U "<" U1,
% This constraint can't be allowed in the store!
not(coq.sort.leq U1 U)
}}.
(*|
.. note:: the user is not expected to declare universe constraints by hand
The type checking primitives update the store of constraints
automatically and put Coq universe variables in place of Elpi's unification
variables (:e:`U` and :e:`V` below).
Let's play a bit more with universe constraints using the
:builtin:`coq.typecheck` API:
|*)
Elpi Query lp:{{
ID = (fun `x` (sort (typ U)) x\ x),
A = (sort (typ U)), % the same U as before
B = (sort (typ V)),
coq.say "(id b) is:" (app [ID, B]),
% error, since U : U is not valid
coq.typecheck (app [ID, A]) T (error ErrMsg),
coq.say "(id a) is illtyped:" ErrMsg,
% ok, since V : U is possible
coq.typecheck (app [ID, B]) T ok,
% remark: U and V are now Coq's univ with constraints
coq.say "after typing (id b) is:" (app [ID, B]) ":" T,
coq.univ.print
}}.
(*|
The :stdtype:`diagnostic` data type is used by :builtin:`coq.typecheck` to
tell if the term is well typed. The constructor :e:`ok` signals success, while
:e:`error` carries an error message. In case of success universe constraints
are added to the store.
=============================
Quotations and Antiquotations
=============================
Writing Gallina terms as we did so far is surely possible but very verbose
and unhandy. Elpi provides a system of quotations and antiquotations to
let one take advantage of the Coq parser to write terms.
The antiquotation, from Coq to Elpi, is written `lp:{{ ... }}` and we have
been using it since the beginning of the tutorial. The quotation from
Elpi to Coq is written :e:`{{:coq ... }}` or also just :e:`{{ ... }}` since
the `:coq` is the default quotation (Coq has no default quotation, hence you always need
to write `lp:` there).
|*)
Elpi Query lp:{{
% the ":coq" flag is optional
coq.say {{:coq 1 + 2 }} "=" {{ 1 + 2 }}
}}.
(*|
Of course quotations can nest.
|*)
Elpi Query lp:{{
coq.locate "S" S,
coq.say {{ 1 + lp:{{ app[global S, {{ 0 }} ] }} }}
% elpi.... coq.. elpi........... coq elpi coq
}}.
(*|
One rule governs bound variables:
.. important::
if a variable is bound in a language, Coq or Elpi,
then the variable is only visible in that language (not in the other one).
The following example is horrible but proves this point. In real code
you are encouraged to pick appropriate names for your variables, avoiding
gratuitous (visual) clashes.
|*)
Elpi Query lp:{{
coq.say (fun `x` {{nat}} x\ {{ fun x : nat => x + lp:{{ x }} }})
% e c c e
}}.
(*|
A commodity quotation without parentheses let's one quote identifiers
omitting the curly braces.
That is `lp:{{ ident }}` can be written just `lp:ident`.
|*)
Elpi Query lp:{{
coq.say (fun `x` {{nat}} x\ {{ fun x : nat => x + lp:x }})
% e c c e
}}.
(*|
It is quite frequent to put Coq variables in the scope of an Elpi
unification variable, and this can be done by simply writing
`lp:(X a b)` which is a shorthand for `lp:{{ X {{ a }} {{ b }} }}`.
.. warning:: writing `lp:X a b` (without parentheses) would result in a
Coq application, not an Elpi one
Let's play a bit with these shorthands:
|*)
Elpi Query lp:{{
X = (x\y\ {{ lp:y + lp:x }}), % x and y live in Elpi
coq.say {{ fun a b : nat => lp:(X a b) }} % a and b live in Coq
}}.
(*|
Another commodity quotation lets one access the coqlib
feature introduced in Coq 8.10.
Coqlib gives you an indirection between your code and the actual name
of constants.
|*)
Register Coq.Init.Datatypes.nat as my.N.
Register Coq.Init.Logic.eq as my.eq.
Elpi Query lp:{{
coq.say {{ fun a b : lib:my.N => lib:@my.eq lib:my.N a b }}
}}.
(*|
.. note:: The (optional) `@` in `lib:@some.name` disables implicit arguments.
The `{{:gref .. }}` quotation lets one build the gref data type, instead of the
term one. It supports `lib:` as well.
|*)
Elpi Query lp:{{
coq.say {{:gref nat }},
coq.say {{:gref lib:my.N }}.
}}.
(*|
The last thing to keep in mind when using quotations is that implicit
arguments are inserted (according to the `Arguments` setting in Coq)
but not synthesized automatically.
It is the job of the type checker or elaborator to synthesize them.
We shall see more on this in the section on `holes`_.
|*)
Elpi Query lp:{{
T = (fun `ax` {{nat}} a\ {{ fun b : nat => lp:a = b }}),
coq.say "before:" T,
coq.typecheck T _ ok,
coq.say "after:" T
}}.
(*|
===========
The context
===========
The context of Elpi (the hypothetical program made of rules loaded
via :e:`=>`) is taken into account by the Coq APIs. In particular every time
a bound variable is crossed, the programmer *must* load in the context a
rule attaching to that variable a type. There are a few facilities to
do that, but let's first see what happens if one forgets it.
|*)
Fail Elpi Query lp:{{
T = {{ fun x : nat => x + 1 }},
coq.typecheck T _ ok,
T = fun _ _ Bo,
pi x\
coq.typecheck (Bo x) _ _
}}. (* .fails *)
(*|
This fatal error says that :e:`x` in :e:`(Bo x)` is unknown to Coq.
It is
a variable postulated in Elpi, but it's type, `nat`, was lost. There
is nothing wrong per se in using :e:`pi x\ ` as we did if we don't call Coq
APIs under it. But if we do, we have to record the type of :e:`x` somewhere.
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 :builtin:`decl` and :builtin:`def` are used
for that purpose:
.. code:: elpi
pred decl i:term, o:name, o:term. % Var Name Ty
pred def i:term, o:name, o:term, o:term. % Var Name Ty Bo
where :e:`def` is used to cross a :e:`let`.
|*)
Elpi Query lp:{{
T = {{ fun x : nat => x + 1 }},
coq.typecheck T _ ok,
T = fun N Ty Bo,
pi x\
decl x N Ty ==>
coq.typecheck (Bo x) _ ok
}}.
(*|
In order to ease this task, Rocq-Elpi provides a few commodity macros such as
`@pi-decl`:
.. code:: elpi
macro @pi-decl N T F :- pi x\ decl x N T ==> F x.
.. note:: the precedence of lambda abstraction :e:`x\ ` lets you write the
following code without parentheses for :e:`F`.
|*)
Elpi Query lp:{{
T = {{ fun x : nat => x + 1 }},
coq.typecheck T _ ok,
T = fun N Ty Bo,
@pi-decl N Ty x\
coq.typecheck (Bo x) _ ok
}}.
(*|
.. tip:: :e:`@pi-decl N Ty x\ ` takes arguments in the same order of :constructor:`fun` and
:constructor:`prod`, while
:e:`@pi-def N Ty Bo x\ ` takes arguments in the same order of :constructor:`let`.
.. _holes:
==========================
Holes (implicit arguments)
==========================
An "Evar" (Coq slang for existentially quantified meta variable) is
represented as a Elpi unification variable and a typing constraint.
|*)
Elpi Query lp:{{
T = {{ _ }},
coq.say "raw T =" T,
coq.sigma.print,
coq.say "--------------------------------",
coq.typecheck T {{ nat }} ok,
coq.sigma.print
}}.
(*|
Before the call to :builtin:`coq.typecheck`, :builtin:`coq.sigma.print`
prints nothing interesting, while after the call it also prints the following
syntactic constraint:
.. mquote:: .s(Elpi).msg(suspended on X1)
:language: elpi
which indicates that the hole :e:`X1` is linked to a Coq evar
and is expected to have type `nat`.
Now the bijective mapping from Coq evars to Elpi's unification variables
is not empty anymore:
.. mquote:: .s(Elpi).msg{Rocq-Elpi mapping:*[?]X11 <-> X1*}
:language: text
Note that Coq's evar identifiers are of the form `?X<n>`, while the Elpi ones
have no leading `?`. The Coq Evar map says that `?X11` has type `nat`:
.. mquote:: .s(Elpi).msg{EVARS:*[?]X11==[[] |- nat[]]*}
:language: text
The intuition is that Coq's Evar map (AKA sigma or evd), which assigns
typing judgement to evars, is represented with Elpi constraints which carry
the same piece of info.
Naked Elpi unification variables, when passed to Coq's API, are
automatically linked to a Coq evar. We postpone the explanation of the
difference "raw" and "elab" unification variables to the chapter about
tactics, here the second copy of :e:`X1` in the evar constraint plays
no role.
Now, what about the typing context?
|*)
Elpi Query lp:{{
T = {{ fun x : nat => x + _ }},
coq.say "raw T =" T,
T = fun N Ty Bo,
@pi-decl N Ty x\
coq.typecheck (Bo x) {{ nat }} ok,
coq.sigma.print.
}}.
(*|
In the value of raw :e:`T` we can see that the hole in `x + _`, which occurs under the
binder :e:`c0\ `, is represented by an Elpi unification variable :e:`X1 c0`, that
means that :e:`X1` sees :e:`c0` (:e:`c0` is in the scope of :e:`X1`).
The constraint is this time a bit more complex. Let's dissect it:
.. mquote:: .s(Elpi).msg(suspended on X1)
:language: elpi
Here `{...}` is the set of names (not necessarily minimized) used in the
constraint, while `?-` separates the assumptions (the context) from the
conclusion (the suspended goal).
The mapping between Coq and Elpi is:
.. mquote:: .s(Elpi).msg{Rocq-Elpi mapping:*[?]X13 <-> X1*}
:language: text
where `?X13` can be found in Coq's sigma:
.. mquote:: .s(Elpi).msg{EVARS:*[?]X13==[[]x |- nat[]]*}
:language: text
As expected both Elpi's constraint and Coq's evar map record a context
with a variable :e:`x` (of type `nat`) which is in the scope of the hole.
Unless one is writing a tactic, Elpi's constraints are just used to
represent the evar map. When a term is assigned to a variable
the corresponding constraint is dropped. When one is writing a tactic,
things are wired up so that assigning a term to an Elpi variable
representing an evar resumes a type checking goal to ensure the term has
the expected type.
We will explain this in detail in the tutorial about tactics.
----------------------------
Outside the pattern fragment
----------------------------
This encoding of evars is such that the programmer does not need to care
much about them: no need to carry around an assignment/typing map like the
Evar map, no need to declare new variables there, etc. The programmer
can freely call Coq API passing an Elpi term containing holes.
There is one limitation, though. The rest of this tutorial describes it
and introduces a few APIs and options to deal with it.
The limitation is that the automatic declaration and mapping
does not work in all situations. In particular it only works for Elpi
unification variables which are in the pattern fragment, which mean
that they are applied only to distinct names (bound variables).
This is the case for all the `{{ _ }}` one writes inside quotations, for
example, but it is not hard to craft a term outside this fragment.
In particular we can use Elpi's substitution (function application) to
put an arbitrary term in place of a bound variable.
|*)
Fail Elpi Query lp:{{
T = {{ fun x : nat => x + _ }},
% remark the hole sees x
T = fun N Ty Bo,
% 1 is the offending term we put in place of x
Bo1 = Bo {{ 1 }},
% Bo1 is outside the pattern fragment
coq.say "Bo1 (not in pattern fragment) =" Bo1,
% boom
coq.typecheck Bo1 {{ nat }} ok.
}}. (* .fails *)
(*|
This snippet fails hard, with the following message:
.. mquote:: .s(Elpi).msg(Flexible term outside)
:language: elpi
Indeed :e:`Bo1` contains a term outside the pattern fragment,
the second argument of `plus`, which is obtained by replacing
:e:`c0` with `{{ 1 }}` in :e:`X0 c0`.
While programming Coq extensions in Elpi, it may happen that we want to
use a Coq term as a syntax tree (with holes) and we need to apply
substitutions to it but we don't really care about the scope of holes.
We would like these holes to stay `{{ _ }}` (a fresh hole which sees the
entire context of bound variables). In some sense, we would like `{{ _ }}`
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 :macro:`@holes!` option.
|*)
Elpi Query lp:{{
T = {{ fun x : nat => x + _ }},
T = fun N Ty Bo,
Bo1 = Bo {{ 1 }},
coq.say "Bo1 before =" Bo1,
% by loading this rule in the context, we set
% the option for the APIs called under it
(@holes! ==> coq.typecheck Bo1 {{ nat }} ok),
coq.say "Bo1 after =" Bo1.
}}.
(*|
Note that after the call to :builtin:`coq.typecheck`, :e:`X0` is assigned the
term :e:`_\ X1`, that means that the offending argument has been pruned
(discarded).
.. note:: All APIs taking a term support the :macro:`@holes!` option.
In addition to the :macro:`@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 :builtin:`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
precise, in the sense that the relation between the input and the output
terms is not straightforward (it's not unification).
|*)
Coercion nat2bool n := match n with O => false | _ => true end.
Open Scope bool_scope.
Elpi Query lp:{{
T = {{ fun x : nat => x && _ }},
T = fun N Ty Bo,
Bo1 = Bo {{ 1 }},
coq.elaborate-skeleton Bo1 {{ bool }} Bo2 ok
}}.
(*|
Here :e:`Bo2` is obtained by taking :e:`Bo1`, considering all
unification variables as holes and all `{{ Type }}` levels as fresh
(the are none in this example), and running Coq's elaborator on it.
The result is a term with a similar structure (skeleton), but a coercion
is inserted to make :e:`x` fit as a boolean value, and a fresh hole :e:`X1` is
put in place of the term :e:`X0 (app [global (indc «S»), global (indc «O»)])`
which is left untouched.
Skeletons and their APIs are described in more details in the tutorial
on commands.
That is all for this tutorial. You can continue by reading the tutorial
about
`commands <https://lpcic.github.io/coq-elpi/tutorial_coq_elpi_command.html>`_
or the one about
`tactics <https://lpcic.github.io/coq-elpi/tutorial_coq_elpi_tactic.html>`_.
|*)
|