-
Notifications
You must be signed in to change notification settings - Fork 424
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
[Design] import statement, for qualified access to symbols #13831
Comments
This comment has been minimized.
This comment has been minimized.
Conversation with Michael:
Me:
|
Following up a little more on that thought: I'm more against the idea of |
This comment has been minimized.
This comment has been minimized.
Doesn't this just push the problem down one level in the hierarchy (and so not really solve the problem so much as kick the can down the road)? I would expect the author of A to control whether or not I think it's desireable than a module author can provide the same interface with a public submodule or by an public import/use. |
I'm generally against using I'm going to try writing some examples to help clarify that. Case 1: Bar is used by Foo module Foo {
use Bar;
}
module Bar {
proc someFunction() { ... }
} 1a) Another module imports Foo import Foo;
// I don't like this at all
Foo.Bar.someFunction();
// I'm not a fan of this because there was never an explicit import of Bar,
// but I like it way better than the previous line.
Bar.someFunction();
// Obviously this shouldn't work.
someFunction(); 1b) Another module uses Foo use Foo;
// Are you arguing that this should work? It does not work today, there's no
// true hierarchy between Foo and Bar. If Bar also used Foo, would you expect
// to be able to write Foo.Bar.Foo.Bar.someFunction()?
Foo.Bar.someFunction();
// I believe this is okay because Bar is made in scope by Foo's use
Bar.someFunction();
// Obviously this should work.
someFunction(); Case 2: Bar is imported by Foo module Foo {
import Bar;
}
module Bar {
proc someFunction() { ... }
} 2a) Another module imports Foo import Foo;
// I don't like this at all
Foo.Bar.someFunction();
// Bar was imported by the module we imported so I guess I'd be okay with this being legal
// I would also be okay with needing an explicit `import Bar;` as well
Bar.someFunction();
// Obviously this shouldn't work.
someFunction(); 2b) Another module uses Foo use Foo;
// Are you arguing that this should work? I don't see why we would prefix Bar with Foo in this
// circumstance - I don't know that a normal Chapel user would think to do so, though a Python
// user might.
Foo.Bar.someFunction();
// Since the import of Bar is public, it seems reasonable to me for this to work
Bar.someFunction();
// Obviously this shouldn't work, since there was no use of Bar
someFunction(); |
How would a user access secondary methods defined in an imported module? E.g. Say I want to call the transpose _array.T method when importing the |
That is a very good question. I would propose that importing a module makes the methods defined in that module available to use, similarly to how creating a class/record instance from a type that was defined in an imported module doesn't prevent the instance from being able to call its methods? I'm open to other suggestions as well |
Responding to #13831 (comment) Thanks @lydia-duncan for the examples:
This is an interesting discussion but it's clear we have different mental models. I'm coming from #13536 (comment) which @bradcray worked through in #13536 (comment) -
This viewpoint leads to different answers for most of your examples. However, I will focus on the two that seems the most surprising to me here:
I think we could choose not to make If we chose not to make
I really don't think this one makes sense in this pattern. As you said, there is no explicit import of
See my comments about |
That seems reasonable, although we lose the benefit of using import where we can identify the origin of all identifiers without leaving the source file. The alternative is that secondary methods are not accessible when just using // Import all the secondary methods on _array type
from LinearAlgebra import _array;
// Import all the _array.T secondary method overloads
from LinearAlgebra import _array.T; I'm not sure which approach is a better fit for Chapel. |
I find that convincing. I think I understand more now about why it would be okay to support |
I don't know the implementation details, but to provide some motivation, I require some way to re-export symbols as if they are in some other module hierarchy because of long-term code maintenance when undergoing refactor efforts. As I move symbols in my module hierarchy, I need the ability to re-export those symbols so my library's users do not perceive an API-breaking change. |
I think at this point I can say that we're going to follow the Rust proposal for import statements, replacing
It sounds as though we will not support We can support things like We're going to wait to decide on relative imports of nested modules (e.g. importing M.N from within M) based on discussion around submodules-as-files. For re-exporting symbols, the general consensus is to use |
This is a proposal to support enabling qualified access to symbols in a new scope.
Background
In Chapel today when a module is included in a program, its symbols are always available for qualified access. However, there are some inconsistencies about when a module is included in a program - modules with the same name as the file in which they are defined can be referenced and found without explicitly listing the file in the compilation command, if the file is in the same directory as a source file that is already listed. There's been a desire among the community for a way to ask for only qualified access.
Main Feature
This proposes an
import
statement for symbol visibility. When used with a module name, it allows the public symbols within that module to be accessed using full qualification, similarly to the behavior ofuse <module> only;
oruse <module> except *;
. E.g. for a module Foo with a variable bar,import Foo;
will allow bar to be accessible usingFoo.bar
.import
statements can list multiple modules, e.g.import A, B;
. Import statements, likeuse
statements, are all considered to occur at roughly the same time, regardless of their place in the scope (though an import can allow another import to occur, e.g.import Foo; import Foo.Bar;
). A qualified access of a symbol could be written “before” theimport
statement that allows it in a scope, e.g.M.something(); import M;
.Unless the module being used for qualified naming is visible to the scope in which the naming is occurring, it is expected that
import
statements will be necessary for qualified naming going forward. This means that the addition of theimport
statement would be a backwards compatibility breaking change. (Edit: this is no longer a backwards breaking change ifuse
statements still enable qualified access, as we have already disabled qualified access when nouse
statement is present)use
statements no longer enable qualified access. There is precedent for unqualified access being linked to qualified access (see Haskell chart).Proposed Extensions
The following extensions are not necessary for an initial implementation and will not break backwards compatibility if they are added later. However, I believe we will want to implement them before considering this feature complete.
import
s should be able to be declared eitherpublic
orprivate
. Whenpublic
, fully qualified naming will be available to any module thatuse
s the module with theimport
statement. Fully qualified naming will not be available to modules thatimport
the module with theimport
statement. (In Python, import chaining causes a hierarchy, so for Foo that imports Bar, you would needFoo.Bar.baz
to utilizeBar.baz
. This seems confusing to me given that we already use.
for nested modules – looking at a qualified call, you wouldn’t be able to tell where the definition ofBar.baz
was). If an import isprivate
, modules thatuse
the module with theprivate import
will have to perform their ownimport
to utilize the symbols with full qualification.public
toprivate
.import
statement. This will be done using theas
keyword. For instance,import MyModule as M;
will allow the symbols defined in MyModule to be accessed using onlyM.
as the qualification -MyModule.foo
will not be legal unless another import ofMyModule
is present that does not rename it. Every module being imported in the statement can be renamed, e.g.import MyModule as M, YourModule as Y;
.use
statements) Note thatuse
statements do not current support renaming the module itself. If we decide that theimport
feature means thatuse
statements should no longer also enable qualified access, there is no point to enabling renaming of the module in ause
statement. Ifuse
statements continue to enable both qualified and unqualified access, then I would move to add renaming touse
at the same time.import MyModule as M; use M;
, and as a result adding renaming touse
would not necessarily be as high of a priority. It just seemed easy to accomplish at the same time.except
andonly
lists would be an add-on feature not essential for the first implementation. If they are included, it is expected that they would limit the symbols that can be accessed using qualification -import Foo only bar;
andimport Foo except baz;
would both preventFoo.baz
from resolving, while enablingFoo.bar
to work.import Foo only;
andimport Foo except *;
are not valid statements (we could alternatively allow them but warn that the operation is a no-op).only
lists also allow renaming of the symbols being accessed in a qualified manner, similar to its use inuse
statements. It is possible to rename both the module and a symbol to access from it in the same statement, e.g.import MyModule as M only foo as f;
allowsM.f
.except
andonly
lists, it should be legal toimport
the same module multiple times, with different privacy settings orexcept
/only
lists. It might be nice to warn if something brings a symbol in multiple times in the same way.Edge Cases
The following are cases to be aware of during implementation.
import
should not break this) Calling constructor of a class within a module requires explicit importing of the module #6483use <modname> only <typename>
#12298Open Questions
import
as an option mean that qualified access is not enabled by ause
statement? Put another way, for a moduleM
with a symbolx
, shoulduse M;
mean we can write bothM.x
andx
or should it mean we can only writex
?use
.import
and/oruse
impact the declared privacy of a module?import
/use
s of a module even in the same scope, I am against allowing the privacy to be altered by these statements.import
of a module be required prior to anyuse
of it?import
be able to be applied to an enum, likeuse
is?use
- I would guess that in practiceuse myenum;
happens more often on enums that are defined by the current scope, and qualified access of these enums is already possible.use M only myenum;
would be equivalent toimport M.myenum;
and is slightly less characters, though.Precedence
as
.from a import b
makesb
available asb
instead ofa.b
.from a import *
brings all symbols froma
into scope with unqualified access. Symbols that are imported can be referenced as if they were defined from the scope of the module being imported (even if they were made available in that module via an import).from
is used, only the symbols specified are available (sofrom foo import bar
doesn’t meanfoo
can be referred to, justbar
)using
statement functions like ouruse
statement, so far as I can tell.using Mod: x, y
is equivalent touse Mod only x, y;
, though Julia does not allow you to extend types obtained in this way (no secondary methods, though maybe you can still do that with the original module’s full path, e.g.Mod.x.whatever
?). Julia also has animport
keyword, which is used to bring one symbol into scope only, and that symbol can be extended. Explicitly bringing in a symbol withusing
orimport
means the module cannot define a symbol with that name.using
orimport
without naming specific symbols. It seems the way to make just it available on its own is toimport
it.use
serves to shorten a path for the referenced symbol -use a::b::c;
allows unqualified access toc
and qualified access toc
’s symbols withouta::b::
beforehand. You can add unqualified access to both b and c usinguse a::b::{self, c};
and unqualified access to all symbols in b usinguse a::b::*;
. It allows relative uses as well (we allow you to access symbols in higher scopes for use statements, so this isn’t necessary).only
andexcept
lists, at Brad's suggestion. This is an updated version of that chart for the current proposal (I don't want to try editing it for good formatting here)The text was updated successfully, but these errors were encountered: