Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discharge on the fly #72

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions text/discharge-on-the-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
- Title: Discharge on the fly

- Drivers: Hugo Herbelin

----

# Summary

After the refactoring of discharge made in coq/coq#14727, it becomes possible to discharge declarations at the time of their declaration. We discuss a proposal in this direction.

# Motivation

It is common in a section to want to temporarily generalize the notions defined in the section but to continue to work in the context of the section for further developments, as in:

```coq
Section S.
Context {A B:Type}.
Inductive option := None | Some (a:A).
Definition map (f : A -> B) (o : option) :=
match o with
| None => Top.None
| Some a => Top.Some (f a)
end.
(* ... *)
End S.
```

This is what the proposal tends to achieve.

# Details about the design

## Basic idea

The basic proposal is to emulate a section
```coq
Section S.
Variable a : A.
Definition f := ...
```
as
```coq
Definition f a := ...
Module S.
Parameter a : A.
Definition f := ...
```

that is, that each persistent declaration of the section is declared both in the section and, discharged, at toplevel (as well as appropriately discharged in all intermediary section levels).

The resulting full names are typically `Top.f` for the toplevel copy, and `Top.S.f` for the copy in the section (as well as `Top.S.T.f`, etc., if there are nested sections).

At the end of the section, the module being built is discarded.

The distributivity of declarations over section levels is obtained by maintaining one environment/state for each section level:
- opening a section forks a new copy of the current environment/state for the section; this copy becomes the active one
- extending a section content enriches simultaneously and hereditarily all section levels (i.e. `Definition f := t` adds `Top.f` to all section levels, `Top.S.f` to all section levels from level `Top.S`, `Top.S.T.f` to all section levels from level `Top.S.T`, etc.); there are however three kinds of declarations (see below)
- closing a section drops the active copy of the environment/state and makes active the environment/state of the outer section (which itself growed since the fork)

## Definitionally connecting the different copies
Copy link

@CohenCyril CohenCyril Sep 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitional equality is not enough in practice, the various copies must also be identified in the following contexts:

  • in the syntactic phase of rewrite pattern matching
  • in patterns of Hint
  • as canonical keys
  • in Search results

(and I'm not sure how exhaustive is this list)

There are at least the two following ways to address this problem:

  1. Use a notation instead of a definition for aliases in each sections
  2. Call Declare Equivalent Keys all the time and make sure it is taken into account by every relevant part of the system (and document it properly cf [Declare Equivalent Keys] should be documented coq#4551).

Ideally, intermediate definitions should be discarded when exiting the section.

(Optionally, also, in the future, it would be nice if re-entering a named section could provide the same context and the same aliases....)

Copy link
Member Author

@herbelin herbelin Sep 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as canonical keys

In my experiences to provide simultaneously the non-discharged and discharged versions of a canonical key, I had several incompatibilities. Studying the reason of these incompatibilities might help to understand if there is a way to make it working, but that does not seem to be straightforward to support (see for instance the failure on mathcomp I got when the discharged versions of a canonical projection are also allowed to be used).

the various copies must also be identified

By identified, I guess you mean eq_constr-equal (alpha-convertible) vs conv-equal. Is it that you think that it would help compatibility? In any case, I struggled in not breaking too much compatibility in coq/coq#17888. I'm a bit pessimistic about the room for manœuver that we have.

(Optionally, also, in the future, it would be nice if re-entering a named section could provide the same context and the same aliases....)

I agree. And it is not impossible to do if we accept to keep the inside of sections in vo files. And, at worst, even if keeping the inner of sections in vo files happens to be too costly in size, we may also recompute dynamically the instances.

[In any case, thanks for your support!]


Different strategies are possible to definitionally relate the different copies.

- The body of the constant is in the fully discharged copy and each copy in a section takes the form:
```
Definition f := Top.f a.
```

How to evaluate the risk that the encoding surfaces too easily (e.g. when reducing), losing the advantages of reasoning in a section?

- Each copy has a full copy of the body discharged accordingly to the level of section where it lives without the less generalized copy evaluating to the more generalized one. This provides convertibility of `Top.f a` and `Top.S.f` for transparent constants but not for opaque constants or inductive types. For opaque constants and inductive types, a possibility would be that the conversion algorithm takes into account a table of correspondences between `Top.f a` and `Top.S.f`.

## Which authoritative definition?

The pre-existing model gets the typability of the fully-discharged declarations by trusting the type-checking of the non-discharged declarations and trusting the discharge algorithm.

From the moment all copies are generated at the same time, it would reduce the trusted type-checking basis of documents whose sections are closed to instead type the fully-discharged declaration (see @gares' [suggestion](https://github.com/coq/coq/pull/17888#issuecomment-1653536034)). The trust in the discharge algorithm would then serve only to trust the (volative) inner of the sections (via the claim that discharging correctly reverts specialization).

## Three kinds of objects in sections

In practice, relatively to sections, Coq objects are of three kinds:
- volatile: meaningful only in the section it is declared: this is the case of `Variable`, `Hypothesis`, polymorphic universes, etc.
- generalizable: meaningful in all section levels: this is the case of `Definition`, `Inductive`, `Axiom`, etc.
- global: meaningful in the section-free level: this is the case of `Require`, monomorphic universes, etc.

## Non-logical objects

Implicit arguments, arguments scopes, arguments renamings, ... are attached to a constant and should a priori be present at each level.

For tables, such as coercions, canonical projections, hints, instances, it is unclear what status to give to them.

For instance, having only the generalized form of the coercions would lead to too general coercions whose arguments cannot be inferred. Keeping the less general copy is a priori the safest path for compatibility. Do we need more?

# Compatibility issues

The model adds new names. So, it can in theory introduces new name ambiguities and force to qualify more.

The question of efficiency is also to study. In the minimalistic approach, it is only about moving discharging tasks currently done at the end of a section towards the place where the object is discharged. But keeping several copies of coercions, hints, etc. at all levels may possibly result in slowdowns for e.g. `auto` or type classes resolution.

# The experiment of PR coq/coq#17888

At the time of writing, PR coq/coq#17888 does the following:
- introduces a distinction between the three kinds of objects
- build dynamically the contents of all section levels and inherit their contents from outer sections to inner sections, for both the logical environment and the non-logical state
- "certify" the innermost declaration, but certifying instead the outermost would be easy
- the different discharged copies of a declaration are distinct copies of the original copy (no explicit sharing, no convertibility between opaque constants and inductive types from different section levels)

# Further questions

The model would a priori supports declaring module variables in a section and discharging modules in sections into functors.