Thank you for this detailed essay.
There are a couple of minor points I would disagree with, but I will keep quiet and see what others have to say.
Btw import lenientops for mixing integral types like float and int.
Ok, I'll start with the minor note:
As a minor note, the CLI interface feels weird to me, I think I've used the --opt:value syntax with Mono long ago. I think this just adds a bit to the barrier of using the tool easily for no real gain. I would love a new nimc binary that follows posix best practices for the CLI though.
"Posix best practices" means ambiguous CLI switches that require a symbol table to parse. Not much of a "best practice" here, it's just bad design that you prefer because it's slightly easier to type.
That’s a great write up, and you’re English is infinitely better than my knowledge of your mother tongue :-).
These are my thoughts as a very experienced dev (20+ years, primarily Java, Scala, Clojure erd) but a complete newbie with Nim.
For me, it felt like there is a base set of knowledge that the existing documentation assumes. As it is a “systems” language (whatever that nebulous term means), maybe that is OK. For example, it compiles to C so it should be obvious how to cross compile, as that is done at the C level, not the Nim level....except I last did C 20 odd years ago.
I also found the fact you can specify the same thing a number of ways, made learning it significantly harder, because different examples are in different syntaxes. I don’t claim the existing docs use different syntax, but it would help enormously if the documentation used the same syntax (without necessarily proposing a blessed syntax), with a page up front warning against macros AND describing the various syntaxes.
For tooling, my main attraction to golang was the batteries included stdlib, and the ability to build a single static EXE for Linux and Windows was just great. It _almost made up for the compromises in the rest of the language! A “blessed” tool/process for cross compiling would be awesome. I put together an example docker for Linux and Windows (https://github.com/yatesco/docker-nim-dev-example) but it is nowhere near ready for production.
It would also be worth writing a “LSP” adapter to the features in the nim conpiler.
For maturity, the fact there are posts around from 2015 saying v1.0 was near, yet we still don’t see v1.0 made me very nervous. I think a highly visible roadmap to v1.0, even if the roadmap is for another 12 months! would give the sense of calm and confidence that the language is a maturing and ongoing concern. At the moment it feels like a kernel of gold surrounded by mostly platinum borders but some borders being cheap and frilly plastic :-).
The community, in my experience is the main hook. It is an overwhelmingly welcoming and supportive place with people ready to step in and help (see the PRs to my docker project for example).
Finally, @araq and team - you have built something _really amazing here, and I take my hat off to you!
I look forward to be part of the Nim community over the next 5 years as we slowly liberate programmers everywhere from the clutches of mediocrity and frustration from lesser languages :-).
(Typed on an iPhone with chubby thumbs)
I also found the fact you can specify the same thing a number of ways, made learning it significantly harder, because different examples are in different syntaxes. I don’t claim the existing docs use different syntax, but it would help enormously if the documentation used the same syntax (without necessarily proposing a blessed syntax), with a page up front warning against macros AND describing the various syntaxes.
Examples here would be most welcome. Where is that confusing syntactic diversity to be found?
This is actually a very well written critique. Usually we get things like "MOMMY! T-THAT GUY'S LANGUAGE IS NOT CASE SENSITIVE! M-MAKE HIM STOP!!!"
However, as Araq said, there are a few things which are incorrect. It's not actually your fault: in fact, it's actually a documentation problem. But let's go over them one by one.
That's why I think that any language that offers these [metaprogramming] capabilities must make them gradual (i.e. generics vs templates vs macros)
Well, that's exactly what Nim does. But more on this later.
have awesome reporting/debugging stories for it
It does. Have a look at treeRepr, lispRepr, etc. and their relative dumpTree, dumpAstGen, etc. macros. These are totally equivalent to the same tools you get with other languages that support macros. Unless you're referring to some other very specific feature which I don't know of.
offer always runtime alternatives to the meta-programing so the language is useable and productive without them
What exactly do you have in mind here? I'll just note that Nim's GitHub page states:
Nim is a compiled, garbage-collected systems programming language with a design that focuses on efficiency, expressiveness, and elegance (in that order of priority)
Again, I don't know what you're referring to with a "run-time" substitute for macros, but it sounds like it would be very slow. Metaprogramming is necessarily a very resource-intensive task. That's why macros are usually a compile-time only construct, and the few languages which lets you modify the AST at runtime don't even try to be efficient (eg. the Lisp family).
Once you start with the language, the first 5 minutes feel great, if you're familiar to the significant whitespace syntax it makes you feel at home and with a promise of great performance! But then the illusion breaks, [... cutting for saving space ...] you might not even know that you can override operators in Nim at that point!
This paragraph leads me to think that you're coming to Nim from Python. Now, while Nim's syntax certainly is very similar to Python (so similar that something like this is very possible and it works wonderfully), trying to be a substitute for Python isn't Nim's main goal. Neither its second, third or fourth goal. That is, you shouldn't approach Nim as a "compiled, statically typed Python", but rather more as a "C/C++ replacement with modern syntax, package system, metaprogramming and much more". Essentially, Nim is not to Python what Crystal is to Ruby.
Unfortunately while some higher level constructs like generics are well defined and understood at large by many users, full blown AST macros is a different story. It's a very powerful tool with lots of sharp edges and probably only seldom needed. In Nim however you're almost encouraged to use them and I think that's a mistake, there is already a great higher level general purpose meta-programming tool in the form of template (without term rewriting), that should cover most use cases so any reference to real macros in the docs should be prefaced with there will be dragons.
This is just not true. The manual itself states the following:
Style note: For code readability, it is the best idea to use the least powerful programming construct that still suffices. So the "check list" is:
1 Use an ordinary proc/iterator, if possible.
2 Else: Use a generic proc/iterator, if possible.
3 Else: Use a template, if possible.
4 Else: Use a macro.
Also, neither Nim's homepage nor its GitHub page have any reference to macros whatsoever. I don't know what makes you thinks that Nim forces metaprogramming down your throat. In fact, it would be very helpful if you could point at which piece of information lead you to believe that so that we could change it.
But in any case, Nim is not Lisp. The vast majority of Nim's projects are written in a procedural style, with minimal OOP / metaprogramming added on top, and only when needed. Yes, there are things like Jester which heavily use macros to build their own specialized DSL, but they are a minority. And in any case, I find Jester way more readable than its Ruby equivalent, Sinatra, if only because in Jester the DSL is "announced" with the routes keyword.
Anyhow, the main issue with the language is that from the very beginning you're confronted with a huge cognitive load that is not easy to handle. Look for example at the Official Tutorial, the surface of the language is too big and while I'm not in a position to argue about the need or not of all those features, I think that some of that complexity should be initially hidden, offering discoverability paths as the user progresses.
While I agree with what you're saying, a few things should be noted:
Regarding all your other points, they're mostly right. Yeah, there's lots of stuff in system.nim, it's an acknowledged issue, it needs pruning. Yeah, stdlib is too big, it will be pruned too. Yeah, documentation is definitely not final (remember, Nim has yet to hit 1.0).
I disagree strongly about the GC stuff being useless though. Nim has by far one of the best GC implementation when compared with other mainstream languages. It's very useful to game programmers (and there are lots of us in the Nim ecosystem).
I also disagree about the community being unpleasant on GitHub. Technical discussions should not take into account feelings. See Linus and its methodology: it works. You should be able to separate between criticism aimed at your work and personal offenses.
Only via google search results. I don’t recall any variation inside the official docs themselves. My suggestion was more around a newbie thinking “ah OK, so I can express this a few different ways, but all things being equal I should use the ‘default’” is probably more helpful than thinking each variation is valid.
If somebody explicitly wanted to use a different form then they can make that decision. For a newbie like me, I just want to be told what to do in the first instance, until I can make an informed decision (which would almost certainly be to stick the the ‘default’.).
That way, the community gets more consistency and it opens the way for formatting tools around the ‘default’ style.
ATM, I didn’t get a sense of a ‘default’ style other than the fact a consistent style is used in the docs.
This is all about reducing the number of decisions a newbie clambering up the “on ramp” needs to make. Give me a style guide, a source code formatter and we are good to go :-).
"Posix best practices" means ambiguous CLI switches that require a symbol table to parse. Not much of a "best practice" here, it's just bad design that you prefer because it's slightly easier to type.
I'm not interested in arguing about the merits of each approach. My point is that if you're writing a new OS then it would make sense to introduce a better alternative to command line parsing, but Nim is a programming language, it will be used in multiple operating systems and all of them seem to be converging into Posix style CLIs.
I think grep is always a last resort tool as it doesn't understand any programming language...
Sorry, I think I didn't explain myself correctly. I'm not referring to the grep tool in particular. It's very common among users to navigate code by searching, specially if you're looking at the code of others, which can be with grep, any of its more modern alternatives, editor in-built search feature, etc. Many modern languages are easy to grep since they prefix declarations with a keyword but in Nim this is slightly harder, I'm not advocating to change the language for this, just that it might be worth to guide new users with best practices to navigate a Nim code base.
You can productive in Nim without templates and macros, but we can't offer "runtime alternatives" for these features.
Totally agree, and I would say that the language already provides ways to implement most software without having to use templates or macros. But somehow my initial intuition was that it wasn't the case, I think that perhaps the issue is having several keywords with compile-time semantics, maybe if the language had a prefix keyword to introduce them the on boarding would be simpler:
I'm not interested in arguing about the merits of each approach. My point is that if you're writing a new OS then it would make sense to introduce a better alternative to command line parsing, but Nim is a programming language, it will be used in multiple operating systems and all of them seem to be converging into Posix style CLIs.
Yes, Nim works on many OSes and the colons mean tools like nimble can do perfect argument forwarding. No merits of the status quo are known. ("I cannot type colons" does not count.)
Sorry, I don't have to invent a new OS in order to improve the status quo.
Alternatively I could also argue that the Posix way of dealing with this is underspecified and Nim adheres to it already... ;-)
static: const block
proc ... {.compileTime.}: const proc
I strongly disagree about these two. What's wrong with the static keyword? It comes from C and that's what has always been used to signal things that should be either included into the binary itself or be executed at compile time.
Also, you don't need to annotate a procedure with {.compileTime.} in order to use it statically. For example, you can write:
import hashes
const myhash = hash("foobar")
static:
echo "Compile time: ", myhash
echo "Run time: ", myhash
All you need to do is use the const keyword and Nim will try to run the proc at compile-time.
On the other hand, I can understand you feeling weird about when, since it's a very rarely used keyword and it has completely different semantics in C#, but if you really want to propose a different syntax, wouldn't something like static if make more sense? The keyword const usually refer to a value, and if statements in Nim don't return values (there are if expressions, but those are a different thing). But let's be honest: is when really a problem? The manual explains it very well and in a very synthetic manner I don't think it would be a good thing for Nim to change its syntax to appease every single guy who comes along.
Nim is statically typed, unlike Python if you call a function from the wrong module you will not compile.
Always prefixing with module name does not play well with the unified call syntax of Nim.
Lastly, on big projects you can choose to use modulename.function(arg0, arg1, ...) in your codebase. But like code style, linting and code organization, this is a social contract between all contributors.
Nim gives you the choice between using the Python syntax or a lightweight syntax, you're free to enforce the module.function(arg0, arg1, ...) in your code.
Also, C has been doing it that way for 30+ years.
Now that said, I agree that it's less easy when reading random code on Github, however just looking at arguments' types should help knowing which module the function came from. On your machine you have your editor that does that for you.
Hello , I've been with NIM for 4 weeks (big full days;))
I come from IBM38 / AS400 43 years of practice and C / C ++ 30 years of practice, and Pascal I kept the structure and method. my reaction to hot foundry: Nim is much simpler than one imagines, very often I leave with a concept C ++, finally of account it is enough for me to remain open to understand what I can do with Nim and not to want to impose to him to do, I think like that, later I could think about imagining Nim thinking. I quickly got tired of the tutorial, I prefer to have a concrete project but that did not stop me from returning to the tutorial on the contrary, more by doing the introspection of imports, I understand better what we can in shoot . With practice I turn to the "template" but I'm not there yet it comes slowly as I advance. I have practiced a lot of POO or rather the ROO programming relation object object, I think that the language so ready finally I will see.
On the other hand I bump into the real difference between func and proc, because I feel that proc is enough, and then the pointers history is the right solution and can we use it as in C / C ++, I still bump into simple things like data type conversion, the structure of the program is an excellent thing and allows to quickly have a style, the tutorial answers this question, I would have liked to write in English , so my translation Google! ( poo = OPP loll) I hope to be clear although these are concepts.
ps: the doc should support with more concrete examples, i know easy to say and it takes a lot of time.
I sympathize with @perfecto25 on global imports. They make a new codebase hard to learn because it's never clear where anything was actually defined.
I usually start with from foo import nil. When needed, I add specific symbols, e.g. from foo import []=, %, ... And eventually, at least for standard modules, I might end up importing an entire module here and there.
However, there is a very good reason for the Nim default, as @mratsim says: "unified call syntax".
In C++, you often call instance.method(x, y). With that syntax, instance is an implicit extra argument to method(this, ...). I'm not sure that's good style, but everyone is used to it by now.
Nim allows that, but it also allows method(instance, x, y), which is more explicit. And in Nim, they are synonymous. This "unified syntax" has subtle advantages (which I won't describe here).
If you did not import "method", then you must specify the module, mymodule.method(instance, x, y). In that case, the "instance.method()" syntax would not work at all.
Also, Nim templates are powerful and convenient, but they often expect you to have imported the necessary symbols already. As a result, if you use a temple from some other module, you need to know the implementation of the template so that you know what you need to import. You can usually learn these from compiler warnings, but it's often easier to import globally and be done with it.
So this "problem" with Nim imports is actually a result of features of Nim that are generally not available in other languages.
Regardless of having an IDE or editor:
Specifically for the standard library:
In your own code:
This last category of tips helps others to read and understand your code, even without looking up the definitions of procs/templates/macros you're calling.