ceps icon indicating copy to clipboard operation
ceps copied to clipboard

RFC: namespaces

Open gares opened this issue 8 years ago • 108 comments

@JasonGross feel free to take over this PR

gares avatar Jun 13 '17 13:06 gares

I've got to say that I really like the idea of separating, at least in part, the notion of namespace from the notion of modules (some may remember me championing this sort of idea years ago).

On the other hand, this proposal still feels a bit half-baked. And I would guess this is because the motivations are a bit narrow. (also let me remark that namespace and mixins are fairly different concepts, I would advise against equating them in the proposal). Let me share some of my own thoughts as a way forward:

  • One use of namespace that I find appealing is to associate a namespace to every constant/inductive where plugins such as the inductive scheme generator can put the constant they define. Or Program can put its obligations there too. This, however, goes against two assumptions in the proposal:
    • Namespace must be able to be nested (with the semantics being that namespace compose)
    • Namespaces must be able to be declared in a functor (in which case they would be composed with the namespace of the module resulting from a complete application)
  • I like to think of modules as extensible records which incidentally define a namespace (making the namespace thing more primitive)
  • Another thing we may want to do with namespace is manipulate them. For instance we could do, in the style of Haskells import qualified, something like this: Import Foo.Bar as Baz introducing a new namespace Baz in which all of Foo.Bar is accessible. But you can also do that several time for the same Baz. Typical use is that you have several modules declaring functions for a data-structure, say lists (either from different libraries, or maybe from a single library that declares functions and specification separately), and you want to say « meh, this is all just List-s ».

I'm probably missing other good motivations, so I'll come back here when I have more.

aspiwack avatar Sep 14 '17 08:09 aspiwack

One use of namespace that I find appealing is to associate a namespace to every constant/inductive where plugins such as the inductive scheme generator can put the constant they define. Or Program can put its obligations there too.

Yes, I miss this.

Import Foo.Bar as Baz introducing a new namespace Baz in which all of Foo.Bar is accessible.

+1. I don't know well Haskell but I can see how Agda is much more flexible than Coq in structuring name spaces.

To be able replicate to the directory structure within a file is also something to achieve.

What syntax do we need for these features? For instance, would it be enough, as proposed in BZ#3171 to extend the Module syntax so that it can set an arbitrary prefix, as in, e.g., Root Module Coq.MyTypes, or Module ../MyTypes, or Module Init.MyTypes, or Extend Root Module Coq.Init.Datypes. As well as an Import ... as Root Foo, Import ... as ../Foo, Import as Foo.Bar?

herbelin avatar Sep 14 '17 10:09 herbelin

I think this CEP is too important to be stalled like that for 3 years. Who wants to have a working group about it?

CohenCyril avatar Aug 14 '20 10:08 CohenCyril

Who wants to have a working group about it?

Why not. This is indeed important.

herbelin avatar Aug 18 '20 09:08 herbelin

Who wants to have a working group about it?

Count me in.

ppedrot avatar Aug 18 '20 10:08 ppedrot

Who wants to have a working group about it?

I'd be interested in joining

JasonGross avatar Aug 18 '20 23:08 JasonGross

I'm happy to join. Who is leading? (I mean, pinpointing the problems/differences... it may help to jump start the discussion).

gares avatar Aug 19 '20 09:08 gares

I could lead somehow, I had a look at Lean namespaces and agda modules recently. We also led a small experiment with Enrico recently (https://github.com/LPCIC/coq-elpi/pull/177). Should we use a coq call slot or another slot? We could perhaps meet September Wednesday 2nd or Thursday 3rd?

CohenCyril avatar Aug 24 '20 11:08 CohenCyril

Should we use a coq call slot or another slot? We could perhaps meet September Wednesday 2nd or Thursday 3rd?

My own feeling is that a slot different than the call slot might be more comfortable (specific audience, different time constraints) but that's only my own view. Wednesday or Thursday are fine to me.

A related question in passing: in PR coq/coq#12890, there is a need to generate names morally private to a tactic. Would it make sense as part of the discussion to consider private name spaces, or private name subspaces where it is ensured that no collision with user names will happen, e.g., here for ring, a subspace like pwd.Private.ring_lemma? Or is it overkill?

herbelin avatar Aug 25 '20 12:08 herbelin

Is the idea of associating a name space to inductive types part of the proposal? It is in the air for long (and finally something of that sort is implemented in Lean), but it seems to me that it would be rather natural to have nat, nat.O, nat.S, nat.rect, nat.add, nat.add.assoc, etc. without having to explicitly define a module (which would not support writing nat, requiring instead the nat.t workaround conventionally used in OCaml), nor having to see the module non-extensible after End is given.

Somehow, this is also starting to give more structure to the identifiers, observing that _ plays too many rules and introducing a specific symbol to indicate the idea of specialization.

herbelin avatar Sep 02 '20 09:09 herbelin

Is the idea of associating a name space to inductive types part of the proposal?

I'm rather an outsider these days, but for the record I'd very much like that.

aspiwack avatar Sep 02 '20 09:09 aspiwack

I think the discussion should separate the mechanism and it's adoption in the common practice.

I do like the idea of inductive as namespaces, and the derive command in Elpi already hacks it with modules, see https://github.com/LPCIC/coq-elpi/blob/master/apps/derive/examples/usage.v for an example.

But what is more important to me are the details of the mechanism. For example the last line of https://github.com/LPCIC/coq-elpi/blob/master/apps/NES/examples/usage_NES.v Shows something you can't implement on top of modules, since they play the role of metadata containers, while I believe namespaces should only contain data.

Imo we should focus on these details first.

gares avatar Sep 02 '20 10:09 gares

Let's try to meet: https://framadate.org/MmlNR3wImPHtDLqX

CohenCyril avatar Sep 02 '20 11:09 CohenCyril

The best date is Thursday, September 10, 2020 - 16:00-17:00 CEST. Let's meet then. @JasonGross @gares @herbelin does https://visio.inria.fr work for you all?

CohenCyril avatar Sep 07 '20 12:09 CohenCyril

I think visio works---that's what Coq calls use, right?

JasonGross avatar Sep 07 '20 19:09 JasonGross

I just sent @JasonGross @herbelin and @gares an invitation by mail

CohenCyril avatar Sep 10 '20 13:09 CohenCyril

@CohenCyril can I get one as well?

ppedrot avatar Sep 10 '20 13:09 ppedrot

@CohenCyril can I get one as well?

sure, wait a sec

CohenCyril avatar Sep 10 '20 13:09 CohenCyril

Here are my notes:

  • Clarify the difference between kernel name and user name
    • kernel names should be fresh identifiers outside of the user namespace
    • how to deal with kernel names with no user name? We need to display kernel names in a robust way.
    • this decorrelation is also a cheap way to have abstraction (you'd have to go as far as pattern match on unbound kernel name to break abstraction barriers)
  • How do namespaces compare to modules?
    • What is a namespace? What should it contain? Inductive Definitions for sure, what about hints, arguments, notations canonical structures?
    • Should we choose on import whether to get side-effects?
    • Ltac and Definition namespace do not collide, should it be the same for modules (the consensus seems to be yes)
    • Achieve separation of concern by taking out of modules the job of handling names. In this view namespaces should solely be a way to access the context. Hence opening a namespace should just shorten the names, importing side-effects should be independent from the namespacing system. As such side-effects should have a name, and getting the side effect is not about opening/shortening the names, but adding the designated side effects to the current environement.
    • Should namespaces be global? (Cyril: I am not sure I understood this part of the conversation)
    • What is a file? Still a module, the namespace being populated on import?

CohenCyril avatar Sep 11 '20 10:09 CohenCyril

Should namespaces be global? (Cyril: I am not sure I understood this part of the conversation)

Should namespaces be global entities, always visible, or be contained in modules? (I can open a namespace N defined in a module A only after having imported A).

To my recollection, namespaces are global in all the PL I've used.

gares avatar Sep 11 '20 12:09 gares

Should namespaces be global? (Cyril: I am not sure I understood this part of the conversation)

Should namespaces be global entities, always visible, or be contained in modules? (I can open a namespace N defined in a module A only after having imported A).

To my recollection, namespaces are global in all the PL I've used.

Ah I see, however, it cannot be the case for functors, first you do not have a symbol before you apply it, and you may not want to automatically populate a name space by a mere application (e.g. Module A := F B, unless this automatically opens namespace A?), but rather wait for import to get them... Somehow, isn't namespacing population a side effect?

CohenCyril avatar Sep 11 '20 12:09 CohenCyril

you may not want to automatically populate a name space by a mere application (e.g. Module A := F B)

I think you do. As soon as a new named symbol is created, it has to live in a namespace. I don't see the point of delaying the existence of this name. (Note that populating a namespace might be a lazy operation, but that is just an implementation detail. It should not be observable by the user.)

To my recollection, namespaces are global in all the PL I've used.

For the record, C++ has the notion of anonymous namespaces. Symbols in such namespaces can only be accessed by the current translation unit. In Coq parlance, it means that they have no fully qualified name, or rather, their fully qualified name is random.

silene avatar Sep 11 '20 12:09 silene

you may not want to automatically populate a name space by a mere application (e.g. Module A := F B)

I think you do. As soon as a new named symbol is created, it has to live in a namespace. I don't see the point of delaying the existence of this name. (Note that populating a namespace might be a lazy operation, but that is just an implementation detail. It should not be observable by the user.)

If we decorrelate modules and namespaces, it means Module A := F B is not only

  1. attributing a new kernel name (fresh) and username A to the resulting module F B (with many fresh kernel names for the objects of A), but also
  2. adding all the user names of F B to the namespace A

And these seem to be two distinct primitives, which make sense to combine together for users of the Module := command, but not in the API, and there might be more elaborate commands to only do 1. or 2. WDYT?

CohenCyril avatar Sep 11 '20 13:09 CohenCyril

For me, these two cases are the same primitive ("create a given name in a given namespace"), but it can indeed be called numerous times: once for creating the module name A in the enclosing namespace Foo, then several times to create the symbols bar, baz, etc, in the namespace Foo.A.

There is, however, another primitive for creating the namespace Foo.A, which is independent from the creation of module symbol Foo.A. Either of those can happen first. But obviously, namespace Foo.A needs to exist before symbols are added to it.

silene avatar Sep 11 '20 13:09 silene

But obviously, namespace Foo.A needs to exist before symbols are added to it.

Not obvious to me, I think the opposite actually, namespace x exist iff there is a definition which prefix is x.

CohenCyril avatar Sep 11 '20 14:09 CohenCyril

Not obvious to me, I think the opposite actually, namespace x exist iff there is a definition which prefix is x.

This looks like a footgun:

Module A. End A.
Import Namespace A. (* Module A is empty, and so is the namespace. Should it fail because the namespace does not exist? *)
Import Namespace A'. (* A' is a typo. Should it succeed? *)

silene avatar Sep 11 '20 14:09 silene

Not obvious to me, I think the opposite actually, namespace x exist iff there is a definition which prefix is x.

This looks like a footgun:

Module A. End A.
Import Namespace A. (* Module A is empty, and so is the namespace. Should it fail because the namespace does not exist? *)
Import Namespace A'. (* A' is a typo. Should it succeed? *)

If we are talking about namespaces and not modules anymore, doing Namespace A. End A. is a noop (like Section foo. End foo. is). And both commands should have the same output. In my opinion, it should be a warning "namespace is empty" or something like that.

CohenCyril avatar Sep 11 '20 15:09 CohenCyril

To be more precise, there is a difference between

Namespace A. End A.
Open Namespace A. (* raises a warning "empty namespace" *)
Open Namespace A'. (* warning  "empty namespace" *)

And

Module A. End A. (* assuming this create both the module and name `A` in the root namespace *)
Open Namespace A. (* warning "empty namespace"  *)
Open Namespace A'. (* warning "empty namespace"  *)
Import A. (* no warning, usual behavior *)
Import A'. (* error: no module A in the root namespace *)

CohenCyril avatar Sep 11 '20 15:09 CohenCyril

I'm still a bit puzzled

JustModule A. (* the low level primitive *)  (* \____  Module A   *)
Namespace A.                                 (* /                 *)
Definition x.
End Namespace A.
JustEnd A.

When one writes A.x what do we mean?

To me it is A::x (the term x in the namespace A) and when I write Module B := A, the A is the module named A in the empty namespace, namely ::A. It happens that module A contains a pointer to x, but it is not that x is in A because of a "matching prefix". If the namespace was named B, then B::x would still be inside module ::A.

Do we all agree on that?

gares avatar Sep 11 '20 15:09 gares

raises a warning "empty namespace"

The fact that Namespace A. End A. Open Namespace A. can cause a warning is just awful, in my opinion.

Do we all agree on that?

I agree that the semantics should be to try to add the symbol B::X to module A. Whether Coq should loudly complain in that case is a different question. I would tend toward making it an error, but perhaps there is a clever use case for it that I am not seeing right now.

silene avatar Sep 11 '20 16:09 silene