As I understand it, Nim's development has always been done with tooling in mind.
This has some wonderful advantages: uniform function call syntax, awesome IDE support built-in, unobtrusive syntax, convenient import system, etc. All of these would not be possible without proper tooling in the first place and it is a delight to code in Nim in vim with all these.
But at the same time, I am a bit worried the lack of namespaces (or any other strong form of encapsulation, by default). Let me insist on that last point: Nim can be as encapsulated as I would wish it to be, but the majority of the code out there just import Foo.
How is that a problem? In two specific cases:
- When you write code, you feel necessary not to use "common names" for your procedures: if I have a module dedicated to reading one kind of files, I would avoid using read/open/parse/etc. because the probability that read(string) is already imported somewhere is pretty high. The solution? Prefixing or more-explicit-but-far-less-obvious naming. This is exactly what's happening in Julia (which shares a lot of concepts with Nim) and the result is not nice: package writers change the names because package users cannot be bothered by the compiler saying they must use full name resolution for that specific function OR import it as something else themselves. From my perspective, I don't want to ever worry about some implementation details clashing with other unrelated packages: that's what namespaces are for.
- When you read code, you always need a full-fledged IDE. I am not arguing that Nim should be conveniently writable without an IDE... certainly not! However, I would like to be able to quickly be able to get an idea of what is coming from where when looking at a package on github instead of using a full-sized computer with a properly configured IDE each time I dare reading code. Not that it would work every time (that's what IDE support built in the compiler is for!) but at least it would give some useful hints. This is not a fundamental problem (i.e. there is an existing solution: proper tools) but doing this in Python, this is really convenient when skimming through a package.
That said, I am not for explicit import by default either: I think we may find some way to keep the convenience of the current system while improving on the two points above. What do you think?
Edit: I am well aware that similar issues has been discussed already with strong opinions about what the "solution" should be. This invariably ends by Araq stating that there is no problem since an IDE is just the right tool to code. While I agree with that, I could also see in practice that it is possible to ease our understanding of a code base through smart syntax or even tooling (go fmt comes to mind): in practice today, it's not hard to wrap one's head around almost any non-trivial Python code and figure out pretty quickly what we are actually looking for. Julia (in which I have more experience) and Nim really don't convey the same feeling. I find it harder (but not overwhelmingly hard!) to focus only on some subpart of a program: everything seems much more interwoven but that impression may vanish in Nim though (but dedinitely not in Julia).
To sum up, I am not saying that there is a problem here, I am just asking to think about ways to improve on encapsulation and readability for systems without IDE (github and the like most notably) "for free" (i.e. without making development in a proper environment a worse experience).
This invariably ends by Araq stating that there is no problem since an IDE is just the right tool to code.
Well to encourage further discussions: If a decent proposal arrives, I am willing to accept and even implement changes.
Thank you for taking the time to write this up. Unfortunately I don't have any suggestions on what we can do to improve the current situation.
The solution? Prefixing or more-explicit-but-far-less-obvious naming. This is exactly what's happening in Julia (which shares a lot of concepts with Nim) and the result is not nice: package writers change the names because package users cannot be bothered by the compiler saying they must use full name resolution for that specific function OR import it as something else themselves.
That's interesting. Could you give a bit more details about how this works in Julia, perhaps with some code examples?
Well, first things first, in Julia you have explicit modules. Which means that the vast majority of the files start with the module keyword and end with an end keyword (NB: blocks in Julia are ended by end).
When one wants to include some external module, one used to have to include the file containing the module (a.k.a. know where to look) and then import the module itself. Nowadays if module Foo is contained in the file Foo.jl, the file inclusion is done automatically (but the module boilerplate is here to stay).
Anyway, the official way to include a module now is by making use of either using or import. The first one, using Foo is Julia's equivalent to Nim's import Foo while Julia's import Foo is the equivalent of Nim's from Foo import nil.
Said otherwise, if module Foo is imported by import Foo in Julia, one can access any symbols from Foo using its full name (e.g. Foo.bar()) whereas using Foo imports the exportable symbols from the module in the current namespace. In Nim, you do that by marking any symbol with * while in Julia, there is an explicit export keyword. Note that there is no concept of privacy in Julia: exported symbols define what is imported in the current namespace by using; in particular, the other symbols are accessible if prefixed by the module name.
In practice, using Foo is almost always used, which can be easily justified by the fact that most tutorials and the quasi-totality of the examples in the manual use it exclusively. For Julia demographics, this is not a big problem as most "technical computing" people tend to write short length/lived scripts anyway so maintainance and ease of code reading by others is not really a concern.
As Julia is built on a JIT, most of its analysis is done at runtime. It used to warn the user about ambiguous definitions at import time but now it does only when one tries to actually use an ambiguous symbol. This has the extra advantage (for a JIT-ed language) that the complete function signature can be used instead of its name to determine whether or not this is a problem (just as Nim does).
In addition to the JIT, note that Julia actually allows to pass default values to functions which makes things even more complicated even when the signatures do not match to begin with:
function f(x::Int, y::Int=1) x.+y end function f(x::Int) 2.*x end
What should f(2) give?
From my perspective, it seems like Julia and Nim have quite a lot in common: they are definitely different but they made similar choices in their import system which is why I find it relevant to compare the two.
- https://github.com/JuliaML/Roadmap.jl/issues/8 (end)
Or even if they do, such warnings are considered bugs by developers and are therefore "fixed" by resorting to different methods (look at these pages if you are not convinced)... prefixing, opening bug reports on other developer's package, renaming or even not exporting:
Let's just not export them, then people can call Lazy.cycle etc. if they need it.
Prefixing for instance is quite common, e.g. (and I can assure you that this is only a minute sample, look for "export"-ed function names):
Is really wavread that much better than wav.read? For a Nimean (or whatever), the answer is most certainly "no" as wavread, wavRead and wav_read (to cite a few) are already considered identical in Nim. So, if one is ready to write wavread or wav_read, why not wav.read? Namespace prefixing has also another advantage: if all the functions are defined as wave_file_codec_* (wave_file_codec_read, wave_file_codec_write, ...), one would have to write a custom macro to rename them to something sane whereas a WaveFileCodec module can always be trivially renamed wav (wav.read, wav.write, ...).
But all that said putting namespaces everywhere, especially in small or "well-designed" programs, brings very little value with regard to the potential increase in verbosity.
And unfortunately I have no straightforward solution either.
This isn't the first time I've seen the module prefix debate.
I don't really see what the issue is myself: you can force prefixes with the "import ... nil" (IIRC) if you want. What really needs changing?
When you write code, you feel necessary not to use "common names" for your procedures: if I have a module dedicated to reading one kind of files, I would avoid using read/open/parse/etc. because the probability that read(string) is already imported somewhere is pretty high.
Hmm I don't seem to have this problem. Perhaps it's because I'm using VSCode (does that count as an IDE?)
I mean, one of Nim's strengths IMO is the ease of overloading. If I had a module dedicated to reading one type of file I'd probably have a type that I'd pass to it, aka:
proc open*(myFileObj: MyFileType, filename: string) =
or whatnot. Then I'd just call myFileType.open(filename).
But equally if you wanted to use myFileModule.open(filename) then why not? That's fine too.
The last thing I personally would want is to have to prefix modules all over the place, but you can always specify in the import if you want to force this, anyway.
When you read code, you always need a full-fledged IDE. I am not arguing that Nim should be conveniently writable without an IDE... certainly not! However, I would like to be able to quickly be able to get an idea of what is coming from where when looking at a package on github instead of using a full-sized computer with a properly configured IDE each time I dare reading code.
One time, I broke my nimsuggest (think it was when it moved back to main nim instead of being a nimble package or something?) anyway, that really sucked because I lost my precious control-click to go to definition, and parameter suggestions. But then, if you want your code to be specific without any of that, can't you just use module prefixes anyway?
I don't know, maybe I misunderstand but can't really see what the problems are? Are you thinking it'd be better if module prefixes are mandatory? Because then you can use the import nil or whatever it is, surely?
EDIT Can we provide aliases for module imports currently? Perhaps that would help being more specific without having to write long module names everywhere. Or, maybe it'd just lead to a.theproc b.anotherproc everywhere.
May I ask why you plan to make self implicit? Is this because your eyes are tired of seeing them or because your fingers are tired of writing them? (or both of them?)
Neither. Because relative addressing (param.foo) should not be less convenient than absolute addressing (global variable foo).
It also helps refactoring. The Nim compiler contains many global variables that are migrated to become fields of some context parameter, so that the Nim compiler can be used better as a library.
@coffeepot: Please don't make this a prefix vs. no-prefix debate. I may not have been clear but this is really not the purpose.
The problems were stated from the start:
- I, as a developer, don't want to ever think about interactions with other's code. As you said, I can enforce anything I want in my own code. The problem is that in a language which took a similar path, this has become a mess as the number of packages grew. I cited quite a few such issues to make my point. I also clearly stated that this is clearly not a limitation of Nim as a language as we both said. I said that this is related to the defaults which shape the behaviour of a community. Ever heard about someone indenting incorrectly in Python or Nim? Me neither because the language enforces "good" behaviour. There might be some way to guarantee that whichever name I choose for a symbol, no one will open a Github issue asking for "a better name that does not interfere with package Foo".
- I, still as a developer, would like to get Python readability, if possible, in Nim. (By that I mean useful "built-in" hints to understand the structure of the code if I ever have to read it in a limited environment) The "if possible" does mean that this shouldn't be done by sacrificing Nim's current convenience, precisely because the tools already work great so we should not get something worse just for the convenience of the ones who don't want to use the correct tools. That might not be possible, but if nobody thinks of it, it sure won't be.
About things I stated above, I was merely realising that namespace prefixes may not always be more verbose and actually have some advantages. Actually, up until an hour ago, prefixing the names of a procedure (like wav_read) seemed a good idea to avoid the problem until I "said" it out loud. But I am well aware that forcing people to use explicit namespaces just won't work, especially with Nim!