Code Monkey home page Code Monkey logo

pumpkin-pi's Introduction

Coming here from the latest PLDI 2021 draft? The documentation below is meant for users and is a bit out of date. The PLDI 2021 draft code guide can be found here.

This is PUMPKIN Pi (formerly DEVOID), a plugin for automatic discovery of and lifting across equivalences between types in Coq. PUMPKIN Pi is a part of the PUMPKIN PATCH proof repair plugin suite, and is included as a dependency of PUMPKIN PATCH starting with release 0.1.

PUMPKIN Pi began as DEVOID, the artifact for the ITP paper Ornaments for Proof Reuse in Coq. It has since been extended. A version of PUMPKIN Pi that corresponds to the ITP camera-ready can be found in this release. A pre-print of our PLDI 2021 paper here.

Given two types A and B that are related in certain ways, PUMPKIN Pi can search for and prove the relation between those types, then lift functions and proofs between them. The following relations are currently supported automatically:

  1. Algebraic Ornaments: the type B (like vector) is the type A (like list) indexed by a fold over A (like length)
  2. Records and Tuples: the type A is a nested tuple of the form x * (y * ...), and the type B is a record with the same number of fields that have the same types
  3. Swapping and Renaming Constructors: the types A and B are two inductive types that are equal up to swapping constructor order and renaming constructors
  4. Escaping Sigma Types: the type B (like vector T n) is the type A (like { s : sigT (vector T) & projT1 s = n}) escaping the sigma type

It then ports functions and proofs across those equivalences. For changes that don't fall into the above four buckets, you need to supply the equivalence yourself in a deconstructed form called a configuration. If you need help with this, please check out two examples: switching between unary and binary natural numbers here, and an easier refactoring of constructors here. Also check out the PLDI 2021 draft here.

Getting Started

This section of the README is a getting started guide for users. Please report any issues you encounter to GitHub issues, and please feel free to reach out with questions.

Building PUMPKIN Pi

The only dependency you need to install yourself is Coq 8.9.1. PUMPKIN Pi also depends on a Coq plugin library which is included as a submodule automatically in the build script, and on the fix-to-elim plugin, which is also included. To build PUMPKIN Pi, run these commands:

cd plugin
./build.sh

Using PUMPKIN Pi

For an overview of how to use the tool, see Example.v and minimal_records.v.

Overview

At a high level, there are two main commands: Find ornament to search for equivalences (misleadingly named as an artifact of history and time constraints), and Lift (or Repair for tactic support) to lift along those equivalences. Find ornament supports two additional options to increase user confidence and to make the functions that it generates more useful. If you skip running the Find ornament command and just run Lift or Repair, then PUMPKIN Pi will run Find ornament for you automatically first.

In addition, there are a few commands that help make PUMPKIN Pi more useful: Preprocess for pattern matching and fixpoint support, and Unpack to help recover more user-friendly types. The Preprocess command comes from our plugin fix-to-elim.

Search

See Search.v for an example of search.

Command
Find ornament A B as A_to_B.

This command searches for the equivalence that describes the change from A to B, when the change is supported by automatic configuration.

Outputs

For algebraic ornaments, Find ornament returns three functions if successful:

  1. A_to_B,
  2. A_to_B_inv, and
  3. A_to_B_index.

A_to_B and A_to_B_inv form a specific equivalence, with A_to_B_index describing the fold over A.

For other kinds of equivalences, Find ornament returns only the first two of these.

Options for Correctness

Find ornament supports two options. By default, these options are disabled. Together, setting these two options tells Find ornament to prove that its outputs are correct. (Also as an artifact of time constraints, the options use the old name, DEVOID.)

Set DEVOID search prove coherence.

For algebraic ornaments, this option tells Find ornament to additionally generate a proof A_to_B_coh that shows that A_to_B_index computes the left projection of applying A_to_B.

Set DEVOID search prove equivalence.

This option tells Find ornament to generate proofs A_to_B_section and A_to_B_retraction that prove that A_to_B and A_to_B_inv form an equivalence.

Using Custom Equivalences with Automatic Configuration

If Find ornament fails for an automatic configuration or you would like to use an existing equivalence, you can run this command before lifting (still misleadingly named as an artifact of history and time constraints):

Save ornament A B { promote = f; forget = g}. 

where f and g form an equivalence that describes one of the supported relations between A and B. Note that support for this is extremely experimental, and will not yet work if you try this with changes not supported by automatic configuration (you will need manual configuration for that; more below). You can find an example in TestLift.v.

You can also use this to substitute in a different equivalence for an existing equivalence, but again there are some restrictions here on what is possible. See ListToVectCustom.v for more information.

You can also skip one of promote or forget, provide just one, and let PUMPKIN Pi find the other for certain automatic configurations, for example:

Save ornament A B { promote = f }.

This is especially useful when there are many possible equivalences and you'd like to use a particular one, but let PUMPKIN Pi figure out the rest. See Swap.v for examples of this.

Using Custom Equivalences with Manual Configuration

To use a custom equivalence not at all supported by one of the four search procedures, like switching between unary and binary natural numbers, check out two examples here and here. These examples set manual configuration and essentially skip the search procedure. We will document them soon! The arXiv paper says a lot about these but the examples are the best place to look for now.

Ambiguity

Sometimes, automatic configuration of PUMPKIN Pi finds multiple possible equivalences. With swapping constructors in particular, there can be an exponential number of possible equivalences.

In the case of ambiguity, PUMPKIN Pi presents up to the first 50 possible options for the user (in a smart order), presents this as an error, and gives instructions for the user to provide more information to Find ornament in the next iteration. If the equivalence you want is not in the first 50 candidates, please use Save ornament. See Swap.v for examples of both of these.

Tactic Support

PUMPKIN Pi produces tactic suggestions for all proofs that Find ornament finds. These are experimental, especially with dependent types, but may help you work with tactic proofs about equivalences. You should think of these as suggestions to tweak. Improving the decompiler to both be sound and produce intuitive tactics that match common proof engineering styles is an ongoing project!

Lift and Repair

See Example.v, minimal_records.v, and Swap.v for examples of lifting.

Command
Lift A B in f as g. (* no tactic suggestions *)
Repair A B in f as g. (* tactic suggestions *)

This command lifts a function or proof f along the configured equivalence. If you have already called Find ornament on A and B, it will use the discovered equivalence. If you have used manual configuration, it will use that configuration. Otherwise, it will search for an equivalence first.

Outputs

Lift or Repair produces a term g which is the analogue of f, but refers to B in place of A.

Alternate Syntax

You can run this with an alternate syntax as well:

Lift A B in f as ..suffix.
Repair A B in f as ..suffix.

This will name the result f_suffix.

Whole Module Lifting

You can lift an endure module across an ornament all at the same time by running this command:

Lift Module A B in Foo as Bar. (* no tactic suggestions *)
Repair Module A B in Foo as Bar. (* tactic suggestions *)

This will create a new module Bar with all of the liftings from Foo.

Tactic Support

Using Repair instead of Lift in thte above commands gives you tactic suggestions. These tactic suggestions are experimental, but may help you with workflow integration.

To suggest decision procedures to try to improve tactic output, you can pass the hint option, like:

Repair Module A B in Foo as Bar { hint : "ring" "auto" }

This option comes after opaque, like:

Repair Module A B in Foo as Bar { opaque : A_rect B_rect; hint : "ring" "auto" }

At some point, we hope to make it possible to reuse the tactics from the original proof script easily, even when they are not decision procedures.

Prettier Types

By default, PUMPKIN Pi lets Coq infer the types of lifted terms. You can instead tell PUMPKIN Pi to lift the types (these are typically prettier) if you set the following function:

Set DEVOID lift type.
Opaque Terms

If you'd like, you can tell the Lift or Repair command to treat certain terms as opaque when you are positive that lifting them will have no effect:

Lift A B in f as g { opaque constant1 constant2 ... }.
Repair A B in f as g  { opaque constant1 constant2 ... }.

This can make lifting much faster. It is strongly advisable to do this for certain terms that you know PUMPKIN Pi should never reduce. However, this can also cause unpredictable errors if your assumption is incorrect, so be careful about your assumption.

You can also set a term to be globally opaque every time you lift between A and B by using the following command:

Configure Lift A B { opaque constant1 constant2 ... }.

You can find an example of this in more_records.v.

Caching

PUMPKIN Pi by default caches all lifted terms it encounters as it goes in order to save time. You can disable this if you'd like by running this command:

Unset DEVOID smart cache. 

Additional Functionality

This functionality is demonstrated in Example.v.

Preprocess

The Lift command does not support pattern matching and fixpoints directly. To lift a function f' that uses pattern matching and (certain) fixpoints, run:

Preprocess f' as f.

Then, run:

Lift A B in f as g.

You can also run Preprocess on an entire module; see ListToVect.v for an example of this. See the fix-to-elim plugin for more functionality for Preprocess.

Bonus Features for Algebraic Ornaments

When lifting across algebraic ornaments, to recover a slightly more user-friendly type for a lifted term g with very little extra work, run:

Unpack g as g_u.

The type after Unpack still may not be very user-friendly. If you would like to put in a little more work to get back types that are very user friendly, check out the methodology in Example.v. The key is to set the following option:

Set DEVOID search smart eliminators.

This generates a useful induction principle. Using that induction princple and composing this by lifting across another equivalence, you can get from your unlifted type A to B at a particular index, with much nicer type signatures.

Assumptions for Automatic Configuration with Algebraic Ornaments

PUMPKIN Pi makes some assumptions about your terms and types for now (described in Section 3 of the ITP 2019 paper). Assumptions.v describes these assumptions in more detail and gives examples of unsupported terms and types. Note that the assumptions for records and tuples are not yet documented, since support for those types is brand new.

These assumptions are mostly to simplify search. We hope to loosen them eventually. If any are an immediate inconvenience to you, then please cut a GitHub issue with an example you would like to see supported so that we can prioritize accordingly.

Known Issues

Please see our GitHub issues before reporting a bug (though please do report any bugs not listed there).

One outstanding issue (an unimplemented optimization) has consequences for how we lift and unpack large constants compositionally. For now, for large constants, you should prefer lifting several times and then unpacking the result several times over iteratively lifting and unpacking. See this issue.

There are also still some open questions about eta expansion and definitional equality. Practically, from a user's perspective, that means that lifting may sometimes fail with mysterious type errors for types that look almost but not quite correct. Rest assured, we are working on fixing this, or at the least understanding why it happens well enough to significantly improve error messaging and rule out invalid inputs.

Examples

This part of the README explains some examples and how they correspond to the ITP 2019 paper. You may find these useful as a user, as a reader, or as a developer.

ITP 2019 Paper Examples

The example from the paper are in the examples directory. Here is an overview of the examples, in order of relevance to the paper:

  • Intro.v: Examples from Section 1 of the paper
  • Example.v: Motivating example from Section 2 of the paper
  • Assumptions.v: Assumptions from Section 3 of the paper
  • LiftSpec.v: Intuition for lifting from Section 3 of the paper
  • Search.v: Search examples from Section 4 of the paper
  • Lift.v: Lifting examples from Section 4 of the paper
  • ListToVect.v: Example of preprocessing a module from Section 5 of the paper
  • Projections.v: Evidence for non-primitive projection claims from Section 5 of the paper

Tuple and Record Examples

The most useful examples of lifting between tuples and records are in minimal_records.v and more_records.v.

Swapping and Renaming Constructors Examples

The most useful examples of swapping and renaming constructors are in Swap.v.

Other Examples

The coq directory has more examples, including regression tests and examples from the arXiv paper.

The eval directory has examples from the case study from Section 6 of the ITP 2019 paper. They may be a bit hard to read since there is some scripting going on. They may still be interesting to you whether or not you want to actually run the case study.

ITP 2019 Case Study

This section of the README describes how to reproduce the case study from Section 4 of the ITP 2019 paper. The case study is in the eval directory. We support the case study scripts on only Linux right now.

Building the Case Study Code

To run the case study code, you need the following additional dependencies:

Run the following to make EFF:

cd <path-to-EFF>
make && make install

Datamash should install in a straightforward way from a package manager using the link above.

Reproducing the Paper Case Study

The particular commit for EFF used for the results in the paper is this commit. We have rerun the experiments using this commit, the newest commit at the time of release, and there results have not changed significantly. Results are not guaranteed to be the same for different commits of EFF, especially later ones which may include optimizations not yet implemented at the time of writing. Similarly, on different architectures, the numbers may be slightly different; the orders of magnitude should be comparable.

Enter the eval directory:

cd eval

Increase your stack size:

ulimit -s 100000

Run the following script:

./together.sh

The script takes a while, as it runs each function ten times each on large data both for PUMPKIN Pi and for EFF. When it is done, check the results folder for the median runtimes as well as the size of reduced functions. This also does a sanity check to make sure both versions of the code produce the same output. It does not yet try to reduce the proof of pre_permutes using EFF, otherwise the case study would take a very long time to run. To run this just once, enter the equiv4free directory:

cd equiv4free

In that directory, uncomment the following line in main.v:

(*Redirect "../out/normalized/pre_permutes-sizedEFFequiv" Eval compute in pre_permutes'.*)

Then run the following script, which runs the EFF code just once:

./prepermutes.sh

This should take a while (how long depends on your architecture) and produce a very large term.

Understanding the Case Study Code

The code for the case study is in the eval folder. The case study uses the same exact input datatypes for both PUMPKIN Pi and EFF, copying and pasting in the inputs, lifted functions, and equivalences PUMPKIN Pi produces to run on the dependencies of EFF. The reasons for copying the inputs are that EFF has different Coq dependencies, so the base functions perform differently. In addition, lifting constants with EFF additionally slows down performance, and we would like to control for only the performance of lifted functions, which is easiest to understand.

There is one other thing worth understanding about the case study code. We used to use this command to measure the performance of foo:

Eval vm_compute in foo.

An ITP reviewer noted that this includes the amount of time to print foo. This is a lot of overhead that clouds the usefulness of the data. The reviewer suggested writing:

Eval vm_compute in (let _ := foo in tt).

Unfortunately, Coq seems to be a bit too smart for that; it doesn't actually bother computing foo if you do that. Instead, we now write:

Eval vm_compute in (List.tl [foo]).

This forces Coq to compute foo, but ultimately prints [], thereby not adding printing time. It does add the time to run List.tl, but this overhead is very minimal on a list of a single element (much more minimal than the overhead of printing).

Implementation and Development

Transparency is very important to me and my coauthors. My goal is not just to produce a useful a tool, but also to demonstrate ideas that other people can use in their tools.

Some information for transparency is in the paper: Section 4 discusses the core algorithms that PUMPKIN Pi implements, and section 5 discusses a sample of implementation challenges.

This part of the README is here to complement that. It describes the structure of the code a bit. It should also help if you are interested in contributing to PUMPKIN Pi, or if you are interested in using some of the libraries from PUMPKIN Pi in your code.

Understanding the Code

This is an outline of the structure of the code. Please cut an issue if you are confused about anything here. Please also feel free to ask if you are confused about anything that the code does.

Regression Testing

The test script in the plugin directory runs all of the regression tests:

./test.sh

After making a change to PUMPKIN Pi, you should always run this. You should also run the case study scripts to check performance.

Licensing and Attribution

PUMPKIN Pi is MIT licensed, since I have a very strong preference for the MIT license and since I believe I do not need to use Coq's LGPL when writing language plugins. This interpretation might be wrong, though, so I suppose you should tread lightly. If you are an expert in licensing, definitely let me know if this is wrong.

Regardless, I would like PUMPKIN Pi to be used freely by anyone for any purpose. All I ask for is attribution, especially in any papers that you publish that use PUMPKIN Pi or any of its code. Please cite the ITP paper or the arXiv paper when referring to PUMPKIN Pi in those papers.

pumpkin-pi's People

Contributors

ejgallego avatar nateyazdani avatar ptival avatar tlringer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pumpkin-pi's Issues

Support lifting for refinements

Lifting currently assumes that the new index is not A itself, and does not refer to A. That means that we can't handle refinement types like is_positive and is_even. There are some conceptual difficulties to handling this, but they are an interesting set of types worth handling. Essentially, the issue is that lifting needs to prevent itself from infinitely recursing once it lifts.

I believe this assumption was also accidentally omitted in the paper submission, so it should either be fixed before publication, or the paper should add the assumption to address it.

Improve automatically generated type for retraction

The automatically generated type is, for example:

: forall (A : Type) (v : {H : nat & vector A H}),
      (fun H : {H : nat & vector A H} => orn_list_vector A (orn_list_vector_inv A H) = H) v

We want, for example:

: forall (A : Type) (v : {H : nat & vector A H}),
    orn_list_vector A (orn_list_vector_inv A v) = v

We can pass this type to Coq. While we're at it, we can generate the section type (which Coq figures out fine) and pass it to Coq too, just so that we have an extra validation step.

Ornamental lifting generates an ill-typed term (possibly related to ornamentally lifted inductive predicate)

I discovered this bug through a candidate test case, shown below, for ornamentally lifted inductive predicates. I suspect that the cause is some mismatch of assumptions between the algorithm for ornamental lifting and the "sigma eliminators" built by the machinery for ornamentally lifted inductive predicates.

Add LoadPath "coq".
Require Import Ornamental.Ornaments.
Require Import List Test.
Require Import Indtype.

  Lemma app_is_app (A : Type) (xs ys : list A) : is_app xs ys (app xs ys).
  Proof.
    induction xs; simpl; constructor; assumption.
  Defined.
  Lift list vector in app_is_app as appV_is_appV.

The failure occurs during type checking in lift_definition_by_ornament, after ornamental lifting completes. With a debug print of the ornamentally lifted term inserted, the test case yields the following error trace:

Debug:
(fun (A : Type) (xs ys : {H : nat & vector A H}) =>
 vector_rect A
   (fun (n : nat) (v : vector A n) =>
    is_appV A (n; v) ys .&
      ((fun A0 : Type =>
        fix app (l m : {H : nat & vector A0 H}) {struct l} :
          {H : nat & vector A0 H} :=
          m .& (fun H : nat => vector A0 H) l .1 l .2) A 
         (n; v) ys .&)) (is_app_nilV A ys .&)
   (fun (n : nat) (a : A) (v : vector A n)
      (H : is_appV A (n; v) ys .&
             ((fun A0 : Type =>
               fix app (l m : {H : nat & vector A0 H}) {struct l} :
                 {H : nat & vector A0 H} :=
                 m .& (fun H : nat => vector A0 H) l .1 l .2) A 
                (n; v) ys .&)) =>
    is_app_consV A a (n; v) ys .&
      ((fun A0 : Type =>
        fix app (l m : {H0 : nat & vector A0 H0}) {struct l} :
          {H0 : nat & vector A0 H0} :=
          m .& (fun H0 : nat => vector A0 H0) l .1 l .2) A 
         (n; v) ys .&) H) xs .1 xs .2)
Debug: Vernac Interpreter Executing command
File "./coq/Indtype_bug.v", line 11, characters 2-49:
Error: Illegal application (Non-functional construction): 
The expression "existT" of type
 "forall (A : Type) (P : A -> Type) (x : A), P x -> {x : A & P x}"
cannot be applied to the terms
 "nat" : "Set"
 "fun H : nat => vector A0 H" : "nat -> Type"
 "m .1" : "nat"
 "m .2" : "(fun H : nat => vector A0 H) m .1"
 "fun H : nat => vector A0 H" : "nat -> Type"
 "l .1" : "nat"
 "l .2" : "(fun H : nat => vector A0 H) l .1"
frame @ file "toplevel/coqtop.ml", line 494, characters 10-28
frame @ file "list.ml", line 77, characters 12-15
frame @ file "toplevel/coqtop.ml", line 293, characters 2-33
frame @ file "toplevel/coqtop.ml", line 235, characters 18-109
frame @ file "toplevel/vernac.ml", line 224, characters 30-94
raise @ file "clib/exninfo.ml", line 65, characters 2-15
frame @ file "lib/flags.ml", line 99, characters 19-40
raise @ file "clib/exninfo.ml", line 65, characters 2-15
frame @ file "lib/flags.ml", line 17, characters 14-17
raise @ file "clib/exninfo.ml", line 65, characters 2-15
frame @ file "toplevel/vernac.ml", line 103, characters 31-46
frame @ file "stm/stm.ml", line 2646, characters 11-49
raise @ file "clib/exninfo.ml", line 65, characters 2-15
frame @ file "stm/stm.ml", line 2635, characters 4-55
frame @ file "stm/stm.ml", line 2525, characters 4-100
raise @ file "clib/exninfo.ml", line 65, characters 2-15
frame @ file "stm/stm.ml", line 934, characters 6-10
frame @ file "stm/stm.ml", line 2398, characters 16-43
raise @ file "clib/exninfo.ml", line 65, characters 2-15
frame @ file "stm/stm.ml", line 1087, characters 12-91
raise @ file "clib/exninfo.ml", line 65, characters 2-15
frame @ file "vernac/vernacentries.ml", line 2344, characters 4-36
raise @ file "clib/exninfo.ml", line 65, characters 2-15
frame @ file "vernac/vernacentries.ml", line 2311, characters 8-666
frame @ file "lib/flags.ml", line 99, characters 19-40
raise @ file "clib/exninfo.ml", line 65, characters 2-15
frame @ file "lib/flags.ml", line 17, characters 14-17
frame @ file "vernac/vernacentries.ml", line 2158, characters 30-66
raise @ file "clib/exninfo.ml", line 65, characters 2-15
frame @ file "vernac/vernacinterp.ml", line 80, characters 4-18
raise @ unknown
frame @ file "src/frontend.ml", line 53, characters 9-40
raise @ unknown
frame @ file "pretyping/typing.ml", line 410, characters 10-30
frame @ file "pretyping/typing.ml", line 346, characters 17-39
frame @ file "pretyping/typing.ml", line 346, characters 17-39
frame @ file "pretyping/typing.ml", line 346, characters 17-39
frame @ file "pretyping/typing.ml", line 332, characters 17-46
frame @ file "array.ml", line 93, characters 21-40
frame @ file "pretyping/typing.ml", line 346, characters 17-39
frame @ file "pretyping/typing.ml", line 346, characters 17-39
frame @ file "pretyping/typing.ml", line 332, characters 17-46
frame @ file "array.ml", line 93, characters 21-40
frame @ file "pretyping/typing.ml", line 339, characters 24-44
frame @ file "pretyping/typing.ml", line 346, characters 17-39
frame @ file "pretyping/typing.ml", line 308, characters 35-67
frame @ file "pretyping/typing.ml", line 376, characters 14-44
frame @ file "array.ml", line 91, characters 21-40
frame @ file "pretyping/typing.ml", line 346, characters 17-39
frame @ file "pretyping/typing.ml", line 346, characters 17-39
frame @ file "pretyping/typing.ml", line 95, characters 12-66
raise @ file "lib/loc.ml", line 89, characters 16-23

Missing eta-expansions/contractions cause lifted terms to be ill-typed

I've encountered a bug in which a lifted subterm may have a type containing an eta-expanded dependent pair, conflicting with the expectation of its surrounding term context. This may be caused by a disagreement between how types and terms are lifted.

I've only seen this problem crop up when the base proof/function transforms an equality (commonly by means of f_equal) with a recursive constructor on one or both sides and then relies on a definitional equality with the recursive field. The lifting of tl_ok is the most minimal example of this that I've found. The failed lifting of uncons_eq and the successful liftings of its separated components (hd_cons_eq and tl_cons_eq) aim to demonstrate the problematic interaction of a lifted subterm with its lifted term context.

I'd recommend checking whether the liftings in the existing testsuite and/or case-study actually inhabit their lifted types; this may reveal further edge cases that I haven't thought of.

Here is an example Coq file to demonstrate the bug. Based on my understanding of when the bug occurs, these tests are all that I can think of for the list ~> vector ornament.

Add LoadPath "coq".
Require Import Ornamental.Ornaments.
Require Import Test Lift.

Open Scope list_scope.

Arguments tl {A}.
Arguments hd {A}.

Lemma tl_ok {A : Type} (x : A) (xs xs' : list A) :
  xs = cons x xs' -> tl xs = xs'.
Proof. intro E. rewrite E. reflexivity. Defined.
Fail Lift orn_list_vector orn_list_vector_inv in @tl_ok as tlV_ok.

Lemma uncons_eq {A : Type} (x y : A) (xs ys : list A) :
  cons x xs = cons y ys -> x = y /\ xs = ys.
Proof. intro E. split. exact (f_equal (hd x) E). exact (f_equal tl E). Defined.
Fail Lift orn_list_vector orn_list_vector_inv in @uncons_eq as unconsV_eq.

(* The two components of the previous proof can be lifted successfully when
   separated out, because there is no surrounding proof context to disagree
   with their eta-expanded types. They may be interesting for comparison/debugging. *)

Lemma tl_cons_eq {A : Type} (x y : A) (xs ys : list A) :
  cons x xs = cons y ys -> xs = ys.
Proof. apply (f_equal tl). Defined.
Lift orn_list_vector orn_list_vector_inv in @tl_cons_eq as tl_consV_eq.
Check tl_consV_eq.

Lemma hd_cons_eq {A : Type} (x y : A) (xs ys : list A) :
  cons x xs = cons y ys -> x = y.
Proof. apply (f_equal (hd x)). Defined.
Lift orn_list_vector orn_list_vector_inv in @hd_cons_eq as hd_consV_eq.
Check hd_consV_eq.

Lifting probably has a bug with dependent IB

The current implementation logic for getting the indices to pass to IB doesn't make much sense, so I assume there's a bug which will show up when we test lifting with dependent IB.

Eliminator of lifted inductive predicate is not lifting of eliminator of inductive predicate

This is because of interplay between lifting inductive types and the implementation of the lift-packed rule that gets around Coq's lack of limited projections by lifting every t to (t'.1; t'.2).

To work around this, we should wrap the eliminator of the lifted inductive predicate to an eliminator with the expected type (one which takes as a premise (t'1.; t'.2) instead of t in P), then cache that result so that the core lifting algorithm uses that eliminator instead.

Partially automate the user-friendly indexing methodology

The long-term ideal workflow is something like this, but we need to have more tactic automation at other steps to support this (some of which I'm working on) and it is not super straight-forward to implement:

(* From:
 * https://github.com/antalsz/hs-to-coq/blob/master/base/GHC/List.v
 *)
Definition zip {a} {b} : list a -> list b -> list (a * b)%type :=
  fix zip arg_0__ arg_1__
        := match arg_0__, arg_1__ with
           | nil, _bs => nil
           | _as, nil => nil
           | cons a as_, cons b bs => cons (pair a b) (zip as_ bs)
  end.

Definition zipV  {a} {b} {n} (v1 : vector a n) (v2 : vector b n) : vector (a * b) n.
Proof.
  alegbraic_ornament list vector. (* I'll eventually implement something like this *)
  - apply zip. (* calls lift *)
  - induction l1, l2; intros; auto; inversion H. (* zip_index' becomes the goal here *)
    simpl. f_equal. auto.
Defined.

The Minimum Viable Product (MVP) goal is something like this:

(* From:
 * https://github.com/antalsz/hs-to-coq/blob/master/base/GHC/List.v
 *)
Definition zip {a} {b} : list a -> list b -> list (a * b)%type :=
  fix zip arg_0__ arg_1__
        := match arg_0__, arg_1__ with
           | nil, _bs => nil
           | _as, nil => nil
           | cons a as_, cons b bs => cons (pair a b) (zip as_ bs)
  end

Lift list vector in hs_to_coq.zip as zipV_p.

Lemma zip_index' :  
  forall {a} {b} (l1 : list a) (l2 : list b),
    ltv_index _ l1 = ltv_index _ l2 ->
    ltv_index _ (hs_to_coq.zip a b l1 l2) = ltv_index _ l1..
Proof.
   induction l1, l2; intros; auto; inversion H.
  simpl. f_equal. auto.
Defined.

Unpack dependent zipV_p as zipV using zip_index'.

Definition zipV_uf : forall {a} {b} {n} (v1 : vector a n) (v2 : vector b n), vector (a * b) n := zipV.

Feel free to propose anything in between these. Just comment here first with what you plan to do so we can discuss it

Define adjunction with nicer type (cf., #68)

As mentioned in the PR for auto-instantiated adjunction proofs (#68), the constant for each adjunction proof should be assigned a nicer type for the user. Currently, the type inferred by Coq refers to the fg_id' lemma, rather than the nice wrapper ${ornament}_retraction for an ornament ${ornament}.

This task is not technically challenging but involves a lot of annoying plumbing in OCaml to assemble the desired type.

Bug in forgetting with letin

This fails:

Definition append_vect (A : Type) (pv1 : sigT (vector A)) (pv2 : sigT (vector A)) :=
  let pv := append_vect_inner A pv1 pv2 in
  existT _ (projT1 pv) (projT2 pv).

Lift list vector in append as append_vect_lifted.

While this succeeds:

Definition append_vect (A : Type) (pv1 : sigT (vector A)) (pv2 : sigT (vector A)) :=
  existT _ (projT1 (append_vect_inner A pv1 pv2 )) (projT2 (append_vect_inner A pv1 pv2 )).

Lift list vector in append as append_vect_lifted.

Since we get an intermediate (vector A) in an unexpected place even though it eventually goes away. We should support this somehow. Forgetting in general is not very useful when going along the equivalence we have, honestly; would be better to use the other equivalence for this.

Support the kind of change Reviewer 2 was interested in

R2: I personally would be interested in dealing with the correspondence between trees and terms easily (in CoLoR for Coq terms are implemented using dependent types (number of arguments equal to the arity of the symbol); in Isafor for Isabelle they are implemented as rose trees (formally: using varyadic symbols), but i failed to make that work: (Error: Anomaly "Uncaught exception Failure("this kind of change is not yet supported")." Please report at http://coq.inria.fr/bugs/.) Will that be covered by future work?

It's kind of hard to know what R2 wants without seeing the type examples explicitly, but I should look into this at some point.

Also, I fixed this to not be an anomaly already, and to tell people to post issues here so I can ask them for their types.

Automate unwrapping of existential (sigma-packed) indices

When a user lifts a function or proof across an ornament, the ornamented type's new index is existentially quantified at each occurrence of the type. We'd like a command to automate the process of "unwrapping" the lifted function/proof into a form where the new index is quantified normally.

Example:

app : forall (A : Type), list A -> list A -> list A
~~> (lift by orn_vector_list)
appV : forall (A : Type), {n:nat & vector A n} -> {n:nat & vector A n} -> {n:nat & vector A n}
~~> (unwrap indices)
appV' : forall (A : Type) (n m : nat), vector A n -> vector A m -> vector A (n + m)

Since the tool currently supports only algebraic ornaments, we should always be able to find an expression for the output index, though perhaps not in a "preferred" form. I think that the output index expression should be unique up to definitional equality, unless an explicit rewrite is inserted in the term. User configuration could inform the tool of preferred operators with which to express the index expression (e.g., Nat.add and Nat.sub).

Tactic version of search for equivalences

Once we generate the proofs that promote and forget form an equivalence, add a tactic which proves the equivalence by calling the search functionality. This way, DEVOID can be used more easily to find equivalences to interface with other tools like EFF, and also to prove those equivalences. Blocked by #33.

Whole-module ornamental lifting (low priority)

Ideally, we'd like the plugin to provide a single Vernacular command to translate and then lift everything in a module, along the lines of Lift Module List from list to vector as Vector (syntax TBD).

Support lifting { a : A | indexer a = i_b } to B i_b directly

We lift along the equivalence between A and packed B. But we don't yet handle dealing with unpacked B, which I is equivalent to A and a proof about its index. This could ease lifting along the promotion direction to user-friendly types, and would also make the tool more useful in the forgetful direction.

Methodology for user-friendly types without UIP on the index

In Example.v, the current methodology for deriving the user-friendly type of the proof uses the fact that nat is an hSet, and so the auxiliary rewrites we apply are irrelevant. It would be nice to have a methodology that does not rely on this fact. While many indices have types that are hSet, and while UIP can safely be assumed in Coq in general, it's still not great.

Without UIP, the way the rewrites for the other user-friendly types are derived should be relevant. Thus the methodology should instruct the user to write the auxiliary lemma proofs in such a way that proofs of equalities between rewrites using the auxiliary lemmas will go through easily (and eventually, automation should help with this).

I think more generally that even when the index type itself isn't an hSet, there's something about the structure of an algebraic ornament here that is useful: the new index must fully determined by a fold over the old type. So my guess is that this forces some structure on the indices themselves that makes this possible without relying on any properties of the type. The intuition is fuzzy, though, and I don't know how to reify it into the code yet. (Please comment if you do!)

Case study numbers and text are out of sync

Problem: The text says that the case study code uses the components DEVOID finds, but actually it uses different components. This means the numbers are text aren't in sync, since the performance of EFF depends on the particular equivalences used.

Solution: Run both versions, since both are interesting. One version will be the version from the paper, which uses natural equivalences a human would write, and doesn't do it step by step like DEVOID does. One version will use the components DEVOID finds (and the equivalence proofs once that is in master) and do it step by step the same way DEVOID does. Include both numbers in the paper, since both are interesting. Target is camera-ready if the paper gets in.

Convert fixed-point expressions to eliminator applications

This would be an extension of match desugaring.

Problem and suggested translation process

For simple cases where a match expression is nested immediately inside the function body (e.g., fix f (n : nat) : A := match n with S n' => e_S | O => e_O end), translating fixed-points will be more or less straightforward; we can simply substitute for each possible recursive call in each branch of the match expression (e.g., e_S[IHn'/(f n')]).

The translation will get a bit tricker if the fixed-point takes multiple parameters, as that will require pushing any lambda-bindings after the recursive parameter into the branches of the match expression (equivalently, into the cases of an eliminator application), but Coq's utility functions can handle most of the dirty work for that.

The biggest challenge is when the fixed-point's body does not contain an immediate match expression. When that happens, the fixed-point body could even contain multiple match expressions (e.g., fix f ... : A := if ... then match ... end else match ... end), and we will somehow need to extract a single branch for each constructor. The most robust approach would be to substitute each constructor form of the recursive parameter, partially evaluate (which will reduce every internal match expression on the recursive parameter), and then substitute recursive calls like before.

Example of the translation process

Input:

fix f (x:A) (n:nat) (y:B) {struct n} : C :=
  if Nat.eqb n 7 then
    match n with S n' => e_S | O => e_O end
  else
    match n with S n' => e_S' | O => e_O' end

Factor out/in non-recursive parameters

Pull out pre-recursion parameters:

fun (x:A) : nat -> B -> C =>
  fix f (n : nat) (y : B) {struct n} : C :=
    if Nat.eqb n 7 then
      match n with S n' => e_S[f/(f x)] | O => e_O[f/(f x)] end
    else
      match n with S n' => e_S'[f/(f x)] | O => e_O'[f/(f x)] end

Push in post-recursion parameters:

fun (x:A) : nat -> B -> C =>
  fix f (n:nat) : B -> C :=
    fun (y:B) : C =>
      if Nat.eqb n 7 then
        match n with S n' => e_S[f/(f x)] | O => e_O[f/(f x)] end
      else
        match n with S n' => e_S'[f/(f x)] | O => e_O'[f/(f x)] end

Save the fixed-point expression's type, nat -> B -> C, to use as the elimination motive P later on.

Extract elimination case for constructor S

Decompose bindings up to the recursive reference:

x:A, f:nat -> B -> C, n:nat |- fun (y:B) => if Nat.eqb n 7 then match n with ... end else match n with ... end

Abstract constructor parameters (with recurrence following each recursive field), substitute constructor form for n, and then partially evaluate:

x:A, f:nat -> B -> C, n':nat, IHn':B -> C |- fun (y:B) => if Nat.eqb (S n') 7 then match (S n') with ... end else match (S n') with ... end
-->*[iota]
x:A, f:nat -> B -> C, n':nat, IHn':B -> C |- fun (y:B) => if Nat.eqb (S n') 7 then e_S[f/(f x)] else e_S'[f/(f x)]

Substitute the relevant recurrence for each recursive call:

x:A, f:nat -> B -> C, n':nat, IHn':B -> C |- fun (y:B) => if Nat.eqb (S n') 7 then e_S[f/(f x)][IHn'/(f n')] else e_S'[f/(f x)][IHn'/(f n')]

At this point, I'd recommend using Vars.noccurn to assert that there truly are no references to f left in the term. Coq's fixed-point rules say that that should be the case, but an explicit check will help debug index/offset calculations.

Delete functional recursive reference (from fixed-point):

x:A, n':nat, IHn':B -> C |- fun (y:B) => if Nat.eqb (S n') 7 then e_S[f/(f x)][IHn'/(f n')] else e_S'[f/(f x)][IHn'/(f n')]

Recompose constructor+recurrence parameters:

x:A |- fun (n':nat) (IH:B -> C) (y:B) : C => if Nat.eqb (S n') 7 then e_S[f/(f x)][IH/(f n')] else e_S'[f/(f x)][IH/(f n')]

We now have the elimination case for the S constructor, case_S.

Extract elimination case for constructor O

Same process as for S.

Construct eliminator application

We have built two elimination cases:

x:A |- case_S : nat -> (B -> C) -> B -> C (* forall (n:nat) (P n), P (S n) *)
x:A |- case_O : B -> C (* P O *)

With those expressions and the earlier elimination motive, we build an application of the eliminator:

x:A |- nat_rect P case_S case_O : P
fun (x:A) : P => nat_rect P case_S case_O

And that's it. Coq's (and our) utility functions can handle the index shuffling, substitution, and de-/re-composition of binders, but it will still be tricky to get all the offsets and whatnot right.

Caching should be smarter

If you find an ornament between A and B, and then also find an ornament between C and D, where D depends on A, and try to forgetfully lift terms defined over packed D, you get a term that also tries to lift along the ornament from A at the same time. This is weird behavior.

An example is finding an ornament between nat and some other type, then forgetting from packed vectors. Forget will unfortunately try to promote nat at the same time.

Syntax of commands feels awkward at times

Due to how organically the set of commands evolved, I feel like they are now slightly confusing. In:

Lift Module <fromType> <toType> in <fromModule> as <toModule>.

My brain expects the item that follows the keyword Module to be a module, but the module comes later...

Not sure what would work best, either of:

Lift <fromType> <toType> in Module <fromModule>as <toModule>.

or:

Lift Module <fromModule> as <toModule> lifting <fromType> to <toType>.

I don't love either, but something to consider...

Support universe polymorphism

Simon is interested in integrating this into EFF at some point. For better EFF integration, our tool needs to support universe polymorphism.

First-class lifts? First-class composition of lifts?

I often find myself having some sort of sequence of lifts I need to do on different values.

Modules make it so that I can stash all of those things in a module, and lift the module. But it might be worth considering, depending on how feasible it is, to be able to define a given lift between two-types as something first class? (this might already be doable with the Find ornament command?)

If so, is it also feasible to define the composition of two lifts as a first-class object?

Adding constructors

There are some simple examples of this, but really test some of these out and improve on them.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.