Hi all,
When I'm writing a module, I sometimes worry about picking symbol names that are "too good" (short, convenient) lest they cause ambiguity for client code that uses my module. In short, I don't want to pollute the client namespace with short, cryptic (convenient) proc & macro names.
This is something I definitely miss from Python: You always know where a particular symbol came from, because it's either import foo and then foo.bar, or from foo import bar. (I find this symbol-source clarity is particularly convenient when I'm maintaining long-lived code.)
Nim already offers the from module import nil statement, if a client module wants to import a module and use symbols with full qualification.
I propose an export marker + that is similar to the current * export marker except that by default it requires the symbol to be accessed using full qualification in any client module that imports my module (unless that client module uses a from import statement).
Then I would use + for short, convenient constructor names for my types (myarraymodule.create(...)), and * for operators (once the client code is already using my type).
Since Nim is statically typed, you can get more qualification from overloading.
type MyArray = something
proc create*(a: typedesc[MyArray]): MyArray = something
# in another module
let arr = MyArray.create()
Hi @yglukhov,
But then what's the difference between MyArray.create and createMyArray? Surely that just brings us back to where we started...
I don't like the idea, because it's basically about a module dictating workflow preferences to its clients. This is independent of whether it's a good idea in your case or not.
Whether you want to qualify an identifier is generally the choice of the module that does the import (or, more specifically, of whoever sets the rules for how code should be written in your organization). Not that of a third-party module.
In practice, finding the definition related to a use site is the job of nimsuggest (or your other symbol lookup tool of choice, I typically use vim+tags myself). Python can't do this reliably because it lacks type information.
This is something I definitely miss from Python: You always know where a particular symbol came from, because it's either import foo and then foo.bar, or from foo import bar. (I find this symbol-source clarity is particularly convenient when I'm maintaining long-lived code.)
That's only true when your Python code calls no methods. But typically easily 90% of all calls are method calls in Python code. (Where is the foo in obj.foo is declared?)
Hi @Jehan: I don't disagree with any of your first paragraph, but I disagree with your second paragraph:
Whether you want to qualify an identifier is generally the choice of the module that does the import (or, more specifically, of whoever sets the rules for how code should be written in your organization). Not that of a third-party module.
I would argue: Who knows the third-party module better than its author, so who is in a better position to determine the behaviour of that module than the author? Who is in a more-informed position about which symbols exist in the third-party module?
Currently, when a module is imported, all the symbols are dumped into the client module. There's no choice about that for the client code (unless the client module uses from module import nil). If the third-party module author adds new symbols, client code will automatically receive them without warning. So the maintainer of the client code is forced to stay on top of all developments in all third party modules she uses, or risks exposing her code to unexpected developments over time. And there's nothing the third-party module author can do about this, even if he wants to.
On the other hand, with an additional export marker like + (so that symbols are not automatically dumped into the client code), the third-party module author can take responsibility for the symbols in his module, and be a "good module-writing citizen".
It could become a matter of style that your creation functions for your types (that take only builtin Nim types as parameters, and return instances of your own types) are exported using +, while your operators for your own types are exported using *.
Hi @Araq: When you asked (Where is the foo in obj.foo is declared?), did you mean that to be Nim or Python code?
If it's Python code, then I think that's an easy question: Surely it's declared in class X of which obj is an instance... or more generally, in a parent class of X. (It can't be anywhere else... right?) But I don't see this example as a problem. Method foo is already constrained to the namespace of methods of X.
If it's Nim code, then I agree that it's slightly more tricky due to UFCS and the absence of single-dispatch methods that are declared/defined within their classes. But since this proc foo takes obj (of the imported module's type X) as its first parameter, then stylistically I would consider it like an operator anyway, and use * rather than +.
If it's Python code, then I think that's an easy question: Surely it's declared in class X of which obj is an instance... or more generally, in a parent class of X.
Yes, but the class X of which obj is an instance of is not known since Python is dynamically typed...
I would argue: Who knows the third-party module better than its author, so who is in a better position to determine the behaviour of that module than the author? Who is in a more-informed position about which symbols exist in the third-party module?
That would quickly backfire: Library authors who care about this are likely to mark every new symbol with + making a pessimistic assumption ("cannot break code this way") and patronizing their users ("thank you, nanny, why is that not imported like the other symbols?").