Chapel: Should import statements allow unqualified access? How?

Created on 14 Feb 2020  路  10Comments  路  Source: chapel-lang/chapel

This is a sub issue of #13831, #14790 and #14799.

We're still deciding whether import statements should enable only qualified
access (and have controls over that) or whether they should be able to also
enable unqualified access. If we follow the latter path, a few syntax strategies
have been thrown around:

  • import M only x;, import M only x, y;

    • Similar to use statements

    • But for that reason might be confusing. Only doesn't imply the qualified/unqualified switch very well

  • import M.x;, import M.{x, y};

    • Not the way we've typically indicated these lists in use statements

    • But is kinda reminiscent of enums or classes

  • import M only x local;, import M only x, y local;

    • Allows greater control over qualified or unqualified access

    • Local has been a keyword in the past (this is both a good and a bad thing)

Language Design

Most helpful comment

Sounds like I'm confused about the purpose of this effort.

This gets back to the question I asked in the deep-dive about what we think our motivation for import is, since I think deciding on that would help with a lot of the technical questions. Some options might be (and you could pick more than one):

  • provide a feature more like what people are used to in other languages (ease transition to Chapel from Python or Rust, say)
  • provide a feature that's safer / more defensive than use
  • provide a feature that's sufficient in and of itself for all namespace purposes
  • provide a feature that could replace use for some modes of programming, but not all
  • provide a feature that requires being mixed with use to be complete

I'm currently leaning toward the second bullet as being my main motivator in making decisions and am using bullet 4 as a sub-motivator, such that for a team that wanted to encourage a particular "safe house style" code containing only import might be considered to meet their goals.

I don't view a world in which import M.x; permits unqualified access to x to be inherently unsafe because the programmer is being very explicit about what they're bringing into scope. I'm currently advocating that we not support import M.*; or something to the effect of import M except x; for similar reasons (it's less safe because you're not stating what you expect to get from M).

It would seem odd to me if use M; prevented M from being available to the scope by default. For example, in the following example:

use M only x;
writeln(x);  // OK: this refers to M.x
writeln(M.x);  // compiler error: what the heck is M?

it seem really weird to me for the compiler to suddenly not seem to recognize M when it did just a few lines above in the use statement. It also seems arbitrarily (and slightly cruel) not to permit x to be referred to the more precise / fully qualified manner. (I also really wouldn't want us to have to update existing code to make this change). It's for these reasons that I think use M; should continue to permit qualified access through M and prefer use M as _; as the approach for the "avoid re-exporting M" scenario for use.

Similarly, if the only thing import could do was permit you to start referring to a module, it seems pretty toothless as a new keyword / statement, and like a pretty heavyweight mechanism to permit referring to module names.

In addition, it seems like a fairly arbitrary dividing line for import M.N; where N is a module to permit N to be referred to in an unqualified way, but not to have import M.N.x where x is a variable permit x to be.

So, to me, the import M.N.{x,y,z}; form has the benefits of:

  • succinctness / syntactic simplicity
  • power / flexibility
  • providing a safer/saner way to refer to symbols (they must be qualified or explicitly listed)
  • consistency in effect whether the symbols are modules or otherwise
  • feeling sufficient for those who want to use more precise naming (such that use would likely not be required in their programs)

All 10 comments

I'm currently feeling strongly in favor of option 2 for its brevity and lack of reliance on the only keyword (which, as @gbtitus argued, doesn't really suggest anything relating to "unqualified" to me). I think the asymmetry with use statements is arguably a strength since I think import statements are being introduced to contrast in purpose and effect with use.

It's not really called out in the issue description but one design direction we could go is to rely on use for unqualified access and have import only handle qualified access for modules. I'm not really sure I could articulate reasons to do that or not do that though. @bradcray - can you?

I also like the import M.x form, generally, if we want import to have such a feature.

Hi Michael 鈥斅燭he reasons that I didn't seriously consider that approach is due to:
(a) Python and Rust each supporting forms that enable unqualified access (so if we think of them as motivators for import, it would seem unfortunate to take away that capabiity?), combined with:
(b) wanting programmers (who chose to) to be able to live in an import-only world without feeling crippled (where I would think being forced to not be able to create shorthands for such things as being crippling.

But just to play with the idea, would that suggest that import M; or import M.N.O (where M, N, and O are all modules) are the only kinds of things that import could express? (I think you said in our deep-dive that you didn't think import M only x; as a means of permitting M.x but not M.y didn't make sense, which is why I'm wondering that?)

Python and Rust each supporting forms that enable unqualified access

Python and Rust have module systems that allow both qualified and unqualified access. Is it so bad if we use a different keyword for unqualified access than qualified? We would still support both as well.

wanting programmers (who chose to) to be able to live in an import-only world without feeling crippled

I believe that some programmers would like to live in a qualified-access-only world. Such programmers wouldn't be able to use imports with qualified access either, presumably. I don't think that programmers with this viewpoint are objecting literally to the syntax of use but rather to its tendency to make too many symbols visible. I think such programmers also would not want to use import M.* (supposing we had such a feature).

Now, such programmers might be OK with something like import M.x to bring in a specific symbol or use M only x. It's not clear to me that they would care that it was import vs use giving them this capability.

However, there is a potential difference between these two - namely, whether or not they bring in the symbol M. Today, use M only x also makes M available. However, I think that we need some way of expressing something to bring in specific (non-module) symbols for unqualified access - without bringing in the module - in order to enable re-exports (issue #13979).

We have brought up these possibilities for that:

  1. use does not bring in the symbol for the module used (#13978)
  2. use does bring in the symbol for the module used but doesn't make it available transitively (https://github.com/chapel-lang/chapel/issues/13979#issuecomment-532897747 )
  3. import has an unqualified access form that does not bring in the module (e.g. import M.x in this issue)
  4. use M as _ only x avoids bringing in the module

Maybe when the purpose is clear an the options are in front of us it will be easy to explain why we should go with one or the other of these.

But just to play with the idea, would that suggest that import M; or import M.N.O (where M, N, and O are all modules) are the only kinds of things that import could express?

Yes, that is what I would expect. Programmers would write use M only x rather than import M.x, say.

one design direction we could go is to rely on use for unqualified access and have import only handle qualified access for modules. I'm not really sure I could articulate reasons to do that or not do that though.

Pros:

  • Logical separation of concepts. Makes the divide between unqualified and qualified access very apparent

    • One could argue, though, that qualified access is just unqualified access to the module itself, in which case this distinction is pretty arbitrary. That argument lends more weight to the idea of having import support unqualified access to individual symbols in a module.

  • Allows greater control - could choose to limit the amount of qualified access available as well

    • That said, there probably aren't that many use cases for this, since any conflicts in the imported module would be a problem for the module itself, and any conflicts with the outer module name would just always be a problem.

Cons:

  • Requires multiple statements to accomplish both qualified and unqualified access

    • One could also argue that this makes reading someone else's code less confusing, in that you wouldn't have to mentally switch depending on the syntax used within the statement, only the first keyword.

I believe that some programmers would like to live in a qualified-access-only world. Such programmers wouldn't be able to use imports with qualified access either, presumably. I don't think that programmers with this viewpoint are objecting literally to the syntax of use but rather to its tendency to make too many symbols visible. I think such programmers also would not want to use import M.* (supposing we had such a feature).

I agree with this thinking

I believe that some programmers would like to live in a qualified-access-only world.

I don't think I've talked to any programmers asking for that perspective.

I believe that some programmers would like to live in a qualified-access-only world.

I don't think I've talked to any programmers asking for that perspective.

Sounds like I'm confused about the purpose of this effort.

The import epic (#13119) says:

As a Chapel programmer coming from Python who's sensitive to protecting my namespaces, I would like Chapel to support an import statement similar to Python's and opposite to Chapel's use. That is, by default, no symbols would be available without full qualification by default. Thus, import M; would be similar to use M only; or use M except *; in Chapel today.

So, coming back to this:

wanting programmers (who chose to) to be able to live in an import-only world

Why would a programmer want to live in an import-only world? Is it because they only want to think about Python's import rules which they are already familiar with? Or is it because they want a module system with default behavior that is less eager to bring in lots of symbols (so that they have to opt in to getting specific symbols, say), to reduce the chances of namespace problems in the future? Or is it that they don't like the use syntax for some reason? Or something else?

Sounds like I'm confused about the purpose of this effort.

This gets back to the question I asked in the deep-dive about what we think our motivation for import is, since I think deciding on that would help with a lot of the technical questions. Some options might be (and you could pick more than one):

  • provide a feature more like what people are used to in other languages (ease transition to Chapel from Python or Rust, say)
  • provide a feature that's safer / more defensive than use
  • provide a feature that's sufficient in and of itself for all namespace purposes
  • provide a feature that could replace use for some modes of programming, but not all
  • provide a feature that requires being mixed with use to be complete

I'm currently leaning toward the second bullet as being my main motivator in making decisions and am using bullet 4 as a sub-motivator, such that for a team that wanted to encourage a particular "safe house style" code containing only import might be considered to meet their goals.

I don't view a world in which import M.x; permits unqualified access to x to be inherently unsafe because the programmer is being very explicit about what they're bringing into scope. I'm currently advocating that we not support import M.*; or something to the effect of import M except x; for similar reasons (it's less safe because you're not stating what you expect to get from M).

It would seem odd to me if use M; prevented M from being available to the scope by default. For example, in the following example:

use M only x;
writeln(x);  // OK: this refers to M.x
writeln(M.x);  // compiler error: what the heck is M?

it seem really weird to me for the compiler to suddenly not seem to recognize M when it did just a few lines above in the use statement. It also seems arbitrarily (and slightly cruel) not to permit x to be referred to the more precise / fully qualified manner. (I also really wouldn't want us to have to update existing code to make this change). It's for these reasons that I think use M; should continue to permit qualified access through M and prefer use M as _; as the approach for the "avoid re-exporting M" scenario for use.

Similarly, if the only thing import could do was permit you to start referring to a module, it seems pretty toothless as a new keyword / statement, and like a pretty heavyweight mechanism to permit referring to module names.

In addition, it seems like a fairly arbitrary dividing line for import M.N; where N is a module to permit N to be referred to in an unqualified way, but not to have import M.N.x where x is a variable permit x to be.

So, to me, the import M.N.{x,y,z}; form has the benefits of:

  • succinctness / syntactic simplicity
  • power / flexibility
  • providing a safer/saner way to refer to symbols (they must be qualified or explicitly listed)
  • consistency in effect whether the symbols are modules or otherwise
  • feeling sufficient for those who want to use more precise naming (such that use would likely not be required in their programs)

provide a feature that's safer / more defensive than use

I think this is a reasonable goal and I'm comfortable with proceeding along the lines of import M.N.{x,y,z} / #14790.

I think I can safely say that the answers to this issue are "Yes" and "using either . for a single symbol or .{} for a list of symbols"

Was this page helpful?
0 / 5 - 0 ratings