-
Notifications
You must be signed in to change notification settings - Fork 32
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
Document on primitive projections #57
base: master
Are you sure you want to change the base?
Conversation
fcc82af
to
d96a404
Compare
Thanks for initiating a CEP on this topic! Here's some feedback. Issue 1: I'd introduce a specific syntax for So essentially, something very close to design choice 1. Issue 2: Indeed, dropping the unfold boolean seems good. Why not error on attempts to unfold projections, rather than a noop? Issue 3: Can you elaborate on "it is enough to extend the conversion with eta-expansions of match into projections"? I'm wondering if we shouldn't forbid matches over primitive records. Issue 4: I would simply forbid the projection syntax for general applications. As I wrote above, this syntax could be different from t.(f args) Issue 5: the motivation wasn't clear to m e when I first read. How about adding an example type? |
They can be useful in the input syntax for inference. For instance |
Why not write I had the feeling that having discrepancies between user syntax and terms, like here ghost subterms had proven often painful in the past. |
|
||
*Orthogonal design choice*: same as design choices 1 and 2 but with a different notation, e.g `t.{f args}`. | ||
|
||
## Issue 5: The representation of projections in "positive" types. Can they take benefit of a compact representation? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does positive/negative mean precisely?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the constructor vs destructor variants of coinductive types. I'm not really sure primitive projections make sense for positive coinductive types? They don't make sense for inductive types right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Projections make sense for positive records as macro-definitions, as it has been for a long time in Coq. So, the answer depends on which component of "primitive" projections you are talking about. If you are talking about the compact representation, it makes sense to represent the defined projections of positive record types using a dedicated compact Proj
node, even if thought as macros, i.e. not as a primitive ingredient of positive record types.
|
||
## Issue 5: The representation of projections in "positive" types. Can they take benefit of a compact representation? | ||
|
||
*Design choice 1*: We don't make a difference between positive and negative types. Both supports a `Case` and a `Proj` node which behave anyway the same wrt reduction even when we think at them as syntactic sugar. We add eta-rule for `Case` in the conversion, which are the current rules for expansion into `Proj`. The only difference is that `Proj` is mentally thought as in normal form for a negative type and as a short-hand for a `match` for a positive type. As for `Case`, it can either be mentally thought as a short-hand for its let-proj-expansion for a negative type or explicitly expanded in `cbn` and `lazy`, while `Case` for a positive type is in normal form. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep in mind that if the record has eta then
Record R := r { A : Type; B : Type }.
Check fun x (f : match x with r a b => a -> b end) a b => f a b.
must somehow reduce the match on a variable. ie we cannot have Case be a normal form when it is on a type which has eta.
Conversely we can have projections with omitted parameters for types without eta, this is already the case for some coinductives. AFAIK it would work fine to allow both primitive projections and primitive match for non-eta inductives, they just wouldn't be convertible with each other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Record R := r { A : Type; B : Type }. Check fun x (f : match x with r a b => a -> b end) a b => f a b.must somehow reduce the match on a variable.
Ah, interesting, this is a place where reduction rather than conversion is needed. So, something special to take care of match
commutation would have to be done here.
Conversely we can have projections with omitted parameters for types without eta, this is already the case for some coinductives. AFAIK it would work fine to allow both primitive projections and primitive match for non-eta inductives, they just wouldn't be convertible with each other.
Then, is the equational theory of record types without eta but with projections and match clear to you? Is it just about removing t
≣ (fst t, snd t)
but keeping the commutation match t as z return P with (x,y) => v end
≣ v[x:=fst t][y:=snd t]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then, is the equational theory of record types without eta but with projections and match clear to you? Is it just about removing t ≣ (fst t, snd t) but keeping the commutation match t as z return P with (x,y) => v end ≣ v[x:=fst t][y:=snd t]?
No, this commutation is not well-typed without eta.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right. Do we then need baclofen-style restrictions on the form of P
?
It's also a rather simple example compared to some of the terms people write. BTW |
Thanks for the feeback!
There are the reasons given by @SkySkimmer. There is also the question of compatibility. It is years that the notation Additionally, with implicit arguments, it is easy to simulate
If we want to be as compatible as possible with the developments using non primitive records so that they behave as much as possible like before when the primitive attribute is activated, it might be convenient not to error on existing unfolds (but see below).
I suspect that there would be no harm in providing a By beta, An advantage is that code working without the primitive attribute would work more similarly when the primitive attribute is set. Note that it is not a compatibility mode though. No flags are involved, it is just that a
We could do that. With a script, it would probably be easy to change the existing occurrences of
The point here is that I suspect that there is no reason at the end to bind the attribute primitive to the concept of negative record rather than to the concept of compact representation of enough-applied projections. We are already used to having projections in positive record types and there is no reason not to represent the enough-applied projections in positive records in a compact way too. The logical view at negative types is that they are based on pairing and projections but So, there is a unifying view where positive and negative records have both pairing (a |
Probably issue 5 should be a separate CEP |
|
||
*Design choice 1*: not a kernel problem: the user (or vernac level) can declare `Definition f' params x := x.(f params)` and use it later as an ordinary definition | ||
- Advantages: a simple model | ||
- Drawbacks: the name `f'` is different |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is a sub choice of whether the projection names exist in the same namespace as constants, or if they're in different namespaces in which case the name can be reused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This choice is linked to the dedicated projection syntax choice. If you have a syntax t.(p)
that allows non-primitive projections, whatever the details, then there is an ambiguity.
A possible system: suppose we allow Then it is possible to do |
d96a404
to
0512fab
Compare
I think you meant to write a pure in-term type ascription that does not leave AST residues, but Coq's |
For the record, I added a 2nd design choice in issue 3 about forbidding any form of |
@mattam82, would you like to say something? |
f83458d
to
c22bf79
Compare
I wonder how to proceed next.
Any suggestions on what to do concretely? |
text/primitive-projections.md
Outdated
- Advantages: compatibility with the current usages | ||
- Drawbacks: it does some transformation under the carpet | ||
|
||
*Design subchoice 2.2*: Any application of `Const f` to enough arguments is automatically turned by `mkApp` into a `Proj` (this assumes that any constant `f` contains an information on its projection status)? In particular, `Const` is used only for non-applied or not-enough-applied projection. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FTR, I am strongly opposed to adding more quotients as smart constructors in the kernel. We currently have two of those, namely:
- Automatic concatenation of application nodes.
- Collapse of cast nodes.
The first one is reasonable because it's an isomorphism. The second one less so, because it implicitly erases information from the term.
As a general principle, I believe that quotienting ASTs is a very bad practice. It makes reasoning about them harder, and interferes with higher-level abstractions. We already have to rebuild the abstraction in EConstr
in order to transparently respect the same invariants in presence of evars, but even that is broken by evar instantiation (i.e. casts might disappear). There are probably other similar issues in the code but we're not aware of them because casts already have a strong tendency to disappear silently.
Doing the same thing with projections would make the situation even worse. Just like casts, the quotient you propose is lossy, so by applying a term you can make information disappear. This can silently hide ill-typed data (e.g. you have broken parameters but since the projection is fully applied they magically disappear) and it would also interfere with evars.
ASTs ought to be what they claim to be, i.e. trees, not semantical objects that have some built-in non-trivial equations. Otherwise, we wouldn't we also include full strong normalization in the smart constructors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About how much the concrete syntax can differ from internal representation, I think that I agree that there should be a mode (i.e. at least with Set Printing All
extended with universes and existential instances printing) where we can display the internal representation explicitly.
About primitive projections, are you proposing a "design choice" 2.3 which at the time a record with projection name f
is declared, do the following:
- provides the ability to use compact
Proj(f,c)
nodes with concrete syntaxc.(f _)
(that isc.(f)
when parameters are declared implicit) - defines a constant
Const(f)
bound tofun params c => c.(f _)
with concrete notationf
and with non-compact representationApp(Const(f),[|params;c|])
whose concrete notation is itselff params c
About compatibility, does this mean that all usages of f params c
which currently go to Proj(f,c)
would then go instead to App(Const(f),[|params;c|])
hoping that it works almost observationally the same, telling users to use instead c.(f params)
if they really want the compact representation?
Regarding the compatibility for records not tagged as primitive, is the syntax c.(f params)
disallowed? Is it kept as an alias for f params c
? Or is the plan to have a new notation say c.[f]
for the compact representation of projections in records tagged as primitive so that it does not interfere with the current usages?
Would it mean also keeping all current code which equates App(Const(f),[|params;c|])
and Proj(f,c)
in unification, pattern-matching, notations, etc. or the plan would instead to invite users to make a clear and explicit difference between f params c
and c.(f _)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've already stated it several times around before, but assuming we want to preserve f.(p)
for arbitrary applications (which is reasonable given that otherwise it'd be a serious backwards incompatible change) we desperately need a faithful way to display primitive projections to the user. The issues people have experienced so far is not so much a change of semantics rather than an utter confusion at things displayed the same (notably, the folded flag and the proj vs app distinction). Therefore, we must introduce a new syntax that will unambiguously represent Proj
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Someone needs to unilaterally decide the new syntax or it will stay in bikeshedding mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we desperately need a faithful way to display primitive projections to the user.
This is what #14640 is doing: Proj
nodes are bound to c.(f)
and App(Const(f),...)
nodes to f ...
, so when f
is a primitive projection, the concrete display faithfully reflects the internal representation.
Or is it that you would like more, e.g. that the notation c.(f)
is used exclusively for f
the name of a projection in a primitive record, and not only to discriminate between a compact and non-compact use of projections in primitive records?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#14640 is incomparable to what I am saying, since part of it is a prerequisite and part of it is unrelated. My proposal is just that we print Proj
nodes with a unique syntax different from anything we've been using so far. Obviously it requires preserving those nodes as far as possible inside constr_expr
when detyping, but it has no other effects on the runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My proposal is just that we print
Proj
nodes with a unique syntax different from anything we've been using so far.
Do you already have in mind a new syntax that could be added to the document? Also what are your plans about existing uses of the x.(f)
notation in primitive records at parsing time?
I think it is a bad idea. Not only would it break existing code that use primitive projections (as you mentioned), but it would also make it much harder for users to test whether they can switch their code to primitive projections.
This seems like a better way forward. |
This is somehow what I started in coq/coq#14640 (in addition to print |
By the way, it seems there is a design decision you did not consider (or I missed it):
The syntax So, it is a variant of your design choice 1 for issue 1. It would not hinder forward compatibility much, as partially applied projections are rather uncommon, in my experience. |
070c967
to
2c0ea2a
Compare
I added 4 new subchoices of choice 1 to make the different possibilities more explicit. @ppedrot's one is 1.2 (as far as I understood). Yours is 1.4. As for the current state of coq/coq#14640, it is halfway between 1.4 and 2.1 (since the current implementation is more on the side of choice 2).
A typical use of partially applied projections is e.g. in |
That’s only true if users are writing those matches by hand (personally never seen that), but more generally, many existing clients have to use 100% of the existing semantics even if they don’t want to. For instance, to work around the existence of wrappers and their semantics, one must often unfold them. So I was expecting a fresh flag, |
As a point of data, there are 93 such match constructs in CoqInterval and 102 in Flocq. But maybe I am an exception and users do not match over records? |
Could you give an example of what would be useful for you in case of change of semantics of primitive projections, e.g. a typical situation where you'd like to control iota or have a "new" flag? |
My anecdata is no stronger.
For instance if I have a lemma about the projection. That makes sense for both CS and TCs — if https://gitlab.mpi-sws.org/iris/iris/-/blob/95df9887ecd331923ac57345e30d5a35b708c1bc/iris/bi/interface.v#L4 IIUC, the "folded" flag on projections exist to support (1) this scenario (2) "perfect" compatibility with |
text/primitive-projections.md
Outdated
- Advantages: compatibility with the existing usage of often writing `f params x` for a primitive projection | ||
- Drawbacks: asymmetry parsing/printing (but deprecating `f params x` could eventually clarify things) | ||
|
||
*Design choice 2*: Whenever a record is defined, a `Const f` node of same name `f` is made available at the same time and `Proj (f,x)` and `App (Const f, params, x)` are considered convertible by default without needing any form of explicit unfolding. Note that the infrastructure to make them convertible basically already exists in coercions, unification, ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if the terms were syntactically equal so that hash-consing and the like would identify these terms.
2443349
to
e0eab44
Compare
e0eab44
to
a4097b4
Compare
I added in the document a table comparing a behavior of the diverse reduction commands on the diverse representations of projections. If I understand correctly your need (essentially support for |
For 1:2.3 (controlling delta depending on the number of arguments), I wonder whether it would work to declare the projection as a fixpoint in its last argument. The delta rule would then be provided by the current system for free, and tactics such as |
text/primitive-projections.md
Outdated
| simpl never | nop | nop | nop | nop | nop | ||
| simpl nomatch non-red `Build` | nop | nop | nop | nop | nop | ||
| delta | to `match` | to expanded unfolded Proj | to unfolded Proj | nop | nop | ||
| iota on `Build` | contract | nop | nop | nop (bug?) | contract |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least to go from folded to unfolded, we need beta delta
, not delta
; I expect iota
would then work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are several errors indeed.
- I failed to see that
delta
was not working alone on "folded" - The assumption I made on preparing tests on "unfolded" was wrong (due to using
delta
instead ofbeta delta
) and thusiota
but alsosimpl never
on unfolded work - The
unfold at
tests were otherwise mixed up. - I should have distinguished "match" and "expanded match"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want better inspection options as a first step…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the last version, to inspect folded vs unfolded (if this is what you meant), I used a match goal
which seems correctly discriminating. If you have time, that'll be good to double-check the output of the tests.
Is that so? I think I found at least one bug in the table, but regardless, AFAICT, the docs below imply that https://coq.inria.fr/refman/language/core/records.html#reduction Also, |
The current situation is more bizarre than I thought. Anyway, I think the important point is to agree on the expected semantics i.e. the last column of the table.
We have already the names "match", "fix", "cofix". We could use "proj"?? |
This CEPS is to adopt a strategy with respect to the different paths followed by primitive projections.
Rendered here
Feel free to edit, refine, extend.