Hey,
I've recently finished worked on an article compiling my thoughts on current state of system programming. Nim is mentioned there. Rust is hated there. I've posted it in the Telegram chat previously and gathered some helpful feedback, and even updated the Nim section.
Would be glad to hear your thoughts on this matter, or just have a good laugh on how monstrous C++ and Rust are. Enjoy:
https://vladfaust.com/posts/2020-08-16-system-programming-in-2k20
not see the real objective criticism on Nim
Are you sure about that? I even put links into the bullet list describing why Nim is not the best choice for real system programming.
I worked a very long time on ROO systems and not OO or OOP, RelationalObjeObjet, so far in Nim I find my account, I have also worked with C / C ++, and I reconsidered the choice of orientation on a language which would be coherent, and respecting security (by omitting the hackers) allowing me to carry out a project or the Objects (program, data, interact with each other) of course Pascal or Ada, the logic of Nim is healthy the documentation could certainly be deeper, but people are already tackling this problem and starting to dissect the subject well. Everything is not in the basic doc I grant you but your analysis on "NIM" is for me too superficial and does not put forward with concrete examples ...
That doesn't prevent you wanting something other than C / C ++ in any way, I can understand that.
I too would like a language that would allow me at the same time a hard logic and at times something random which would enrich the program depending on the problem encountered ...
come on, ;) I add "Nim" is very easy to read and having someone else read it again is a great advantage, which I did not find while reading your code (very concise it is true but a syntax ouch ouch) and no damage doc.
Why going lower-level inherently means "growing up"?
This is not what I meant: He doesn't pretend he's perfect, but he hasn't said his last word
A language exists for a purpose. Nim is great for game development and otherwise general applications requiring more performance than dynamic languages.
You see I have found by using in a certain way to make dynamic, the functional of a program and in some sort to apply the theory of granular sets of objects, you see in the questions asked, there Of course there are requests to correct, but also answers to which we ask ourselves and which are nuggets, I call this something like technology watch ....
Stopped reading at this point:
:
It has a Python-like indentation-based syntax
It's an excuse.
Sorry, but your attempt to look cool and smart utterly failed.
I'd like to counter some of you blab^H^H^H^H elaborations but there is not even a clear line nor does your "thinking" carry the weight to deserve it.
As for your language Onyx (btw. do you call everything you do "Onyx"?) I'm acutely disinterested to even look at it. Nothing in your article, that you so proudly linked, created enough interest - and trust in your competence - to at least glance over it.
Btw, Nim does have quite a few (and more than most common languages - incl. Rust) features that enable good quality and safe code. About the only accusation that could be made against Nim's inventor and BDFL is that he didn't paint them yellow and black to be immediately spotted by even very superficial observers like yourself.
I wish you lots of fun and even success with Onyx but kindly refrain from telling us more about it. 90++% of Nim developers are quite happy with Nim. btw, FWIW, my field is IT security and I consider Nim the next best language to (and much more comfortable than) Ada.
[Personal note: Strange, the Russian colleagues I've had the pleasure to meet have been intelligent, competent and smart ...]
It's very hard to take the article seriously due to the deliberate misspellings, memes and introducing pop art in a technical culture. There is a line between the ice-breaker and and overdoing it and unfortunately this article is too much on the other side.
Now let's address your points
Besides embedded developers working on AVR platforms or STM32:
I'm probably the one who used Nim low-level features the most so let's address your points:
It has a Python-like indentation-based syntax with less flexible import semantics.
The indentation syntax has been criticized or praised ad nauseam. Regarding the import semantics, less flexible compared to what? People have been crticizing it for being too flexible (i.e. allowing non-namespaced imports).
It lacks proper object-oriented features like interfaces and mixins.
Arguably, a system language should be ABI compatible with C this means that OOP which contains runtime information is better replaced by contexts and/or callbacks field which Nim supports. That said, interfaces are a fair remark but not limiting for low-level system programming in Nim as:
I don't see the point of mixins in OOP. In Nim all functions are freestanding, they are not tied to an object, mixins seem to be an artificial construct due to how Java requires object for every function (which is a devolution from C).
It does not have lower-level features like pointer alignment, address spaces etc.
Not true, I require pointer alignment for SIMD programming like AVX512 or creating my own memory management scheme, see: https://github.com/mratsim/weave/blob/2e528bd2/weave/memory/allocs.nim#L101-L122
It does not have explicit safety concepts.
The only language that has more safety concept than Nim is Ada/Sparks. I'd argue that while Rust is more safe on the memory front (borrow checker), it is less safe on the type front (distinct, range) and the future Z3 theorem prover integration is very exciting to bring Nim to Ada/Sparks level (https://nim-lang.org/docs/drnim.html) and also enable formal verification of Nim programs (https://github.com/nim-lang/RFCs/issues/222)
That said, you can relatively easily wrap a C/C++ library in Nim for lower-level features such as GPU programming, but who writes that C/C++ library? In that sense, Nim is similar to Python and other higher-level languages with FFI.
You don't need to wrap C or C++ library to do GPU programming. Arraymancer doesn't, you can inline the C/C++ code fragments and then directly use them in actual Nim:
You can even create a compiler that ensures Nim will generate valid Cuda code:
Beyond GPU computing, Nim is also the best language to write cryptography libraries in. Those are very stringent demand that make most of languages unsuitable:
Assembly support: for both speed and security. Compilers are extremely bad at optimizing big integer arithmetic (compared to say floating point as used in scientific computing), also some of their "optimizations" like introducing branches compromises the security of a cryptographic library. As a simple example, it is impossible to ensure that a CMOV is emitted for constant-time conditional copy.
I would be pleasantly surprised if there is a language besides Nim that has as flexible assembly support for generic cryptography. For example, here is a DSL for constant-time big integer multiplication. The Assembly code is generated by the Nim compiler, integrated in the rest of the library for any required bitwidth and use the latest MULX/ADCX/ADOX instructions that compilers never use: https://github.com/mratsim/constantine/blob/9976ac70/constantine/arithmetic/limbs_asm_mul_x86_adx_bmi2.nim#L16-L197.
Hi, congratulations for the passion and the efforts you are putting into this! I wish you best of luck in this ambitious project of creating a new programming language and at the same time trying to get open source sustainability out of that. I am sure there will be plenty of opportunities for growing both professionally and personally along the way.
Regarding feedback on your views of Nim I do absolutely agree on your main point:
Nim is a magnificent piece of art.
It is also a very practical, enjoyable and carefully designed language. Sometimes, it is easy to misunderstand some of its features from shortcomings. For example (from my limited perspective [1]):
But: It has a Python-like indentation-based syntax with less flexible import semantics.
I would amend this with:
and on top of that it has a Python-like indentation-based syntax with a simple and clean import semantics (currently disallowing circular imports).
and this:
It lacks proper object-oriented features like interfaces and mixins.
could be changed to:
it correctly and gently pushes away users from object-oriented programmings (while at the same time allowing for great stuff to be built with OOP); it is currently trying to nail down an improved design for interfaces, called concepts in nim.
Regarding having a good laugh on how monstrous C++ and Rust are: I am always up for an innocent laugh about a language shortcomings (when the joke is to the point and I get the joke).
Finally, I think that the toughest part for a programming language success (especially one that starts from the ground up, like Nim or Onyx) is to build the community around it. And in order to do that IMHO the best strategy is not to diss other languages or communities (the occasional rant is fine), but instead trying to learn from their successes and their mistakes and most of all to be welcoming to people from other communities (they are prospects for your language, not competitors).
[1] hey, I am a Python Data Scientist, I am supposed to know about software engineering just a bit more than the average statistician. I do not think I am supposed to know what system programming even means...
it correctly and gently pushes away users from object-oriented programmings
I wish people would stop linking to this talk to show that OOP is bad. The talk shows gross examples of how to misuse OOP, but that doesn't mean OOP is bad per se.
This is like showing a convoluted Nim macro that does something you could do with generics, and then conclude that Nim macros are generally useless.
OOP is just a tool in the programming toolbox and can be used and misused, like any other tool.
First of all, thanks to everyone who put their time into reading my article(s).
🙏
I by no means pretend to be a person having deep knowledge in any programming language, nor programming concepts in general. I do not have a PhD in computer science nor did I commit a single line into LLVM.
What I objectively have is an experience in a wide selection of programming applications from games to web frameworks and database drivers.
Note that experience does not necessarily mean writing. Reading also gives knowledge.
I can read other peoples' writings. Instead of fighting the borrow checker in person, I'd rather read about it and learn how it can be improved. I'd rather learn from others' mistakes.
Or, I can read documentation to have a full understanding of a technology.
Claiming that one is required to have practical experience in a technology in order to judge it is simply a gatekeeping because if I change my mind, it would then mean either initially poor documentation or emotional bias after use.
Most technologies involved in the topic of system programming have good documentation. Therefore, I'm not required to have years of practical experience in either one to be able to see its downsides. Although, I do in Crystal.
I did examine existing system programming languages, learned how machines work on the deepest level and came to a conclusion that right now there is a lack of human-friendly system programming languages able to reveal full machine potential.
I see that ubiquity of suboptimal applications leads to waste of resources and thus stagnation of the human civilization.
I believe that popularization of system programming by introducing a (more) human-friendly language would lead to an increase of system programming applications, such as medical researches. This, in turn, would improve my own quality of life, as well as move the entire civilization forward, faster.
After more than a year spent on designing the language, I'm revealing my work in progress. Just to say "hey, I'm working on such a big thing, join me or support me financially". It is your choice either to follow me or to ignore me.
I see Nim as an "ally" of Onyx, akin to Python and Ruby, as they are being orthogonally different in terms of concepts, syntax and overall mindset. In fact, Onyx is Crystal made right. And Crystal's friends with Nim, isn't it? So is Onyx.
To be honest I'm surprised by the amount of effort some of you put into the replies. And these kind words... Thank you.
I still believe that Nim is not suitable for real lower-level programming, though. All the examples by @mratsim imply wrapping C/C++ function calls, even aligned allocations. As I've already stated in the article, wrapping C/C++ (even inline, as in the CUDA kernel example) is not a solution, because it's still C/C++.
Honestly, I could not understand @mratsim's argumentation regarding interfaces. Please, fix your punctuation. What I can say is that in Onyx, the language is a black box with export and import points, so any crazy stuff like complex types (which are related to interfaces implementation in Onyx) is allowed without any ABI constraints.
I don't see the point of mixins in OOP.
That's a statement I don't agree with you on. The composition is a crucial feature allowing to have an expressive hierarchy of behaviour.
I believe that OOP is must-have for big projects. FP is indeed fancy, but it is not usable in real-life applications. I understand that it is a holy war topic, but that's my opinion. As OOP is not a part of Nim, I do not find it fit the utopia language idea.
Also thank you, @mratsim, for fancy-schmancy wording about safety. This is new to me. Maybe I take it less seriously. For me, safety is then a program behaviour is defined in given circumstances. In Onyx, unsafe means undefined behaviour, while fragile means possible data races in a multi-threaded environment. In Rust, there is unsafe keyword with similar semantics, am I wrong? Nevertheless, Nim lacks this semantic at all, and there is no way for a user to distinguish between "safe" and "unsafe" areas of code. And I do judge a language from a user's perspective. Nim allows me to shoot my legs, that's unsafe.
Then,
You don't need to wrap C or C++ library to do GPU programming
you can inline the C/C++ code fragments and then directly use them in actual Nim
Come on, isn't it the same thing about still having to write C/C++?
I also do not understand why you pitched Nim's application in cryptography to me. With all the downsides I mentioned in this reply and the article, you'll still have to wrap C code to do basic tasks like alignment, which is crucial for performance.
In conclusion, I want to thank all those who responded again, and repeat myself in that Nim is great with its Python syntax and FP bias. Let it be.
I, no doubts, have a bias towards OOP and C-family syntax. That's why, in my world, Nim is not ideal.
And I want you to explore other solutions because exploring means development. Maybe you'll pick up something new for Nim from one of my articles, who knows?
OOP is just a tool in the programming toolbox and can be used and misused, like any other tool.
That's true but if we had computer science, here is my bet what it would show: OOP produces more error-prone and hard-to-debug systems much more often than the "competing" paradigms do. "Competing" here means old-school imperative procedural programming, functional programming and maybe generic programming (to the extend that "generic programming" competes with OO).
I don't have expertise to comment on "Control over memory ordering." in nim, but I commented on things that I'm relatively experienced in
Again, I'm really not qualified to comment on things related to memory managementt, move semantics and other related things, so I mostly skipped since I'm afraid to write something wrong/misleading
Have fundamental design with shared concepts and minimum exclusive cases to keep in mind.
Due to easy creation of DSL it is not necessary to introduce custom keywords/features to the language. Core development team is mostly working on fundamental features like memory management, concepts etc. Everything else - async, custom trait derivation, pattern matching, support for whatever-programming-paradigm you find interesting this month can be implemented as DSL with minimal friction.
UFCS and style-insensetivity also helps on this one since it completely eliminates syntactic difference between OOP-style and procedural code. Support for custom operators is built into the language ad as easy as proc +(a, b:string): string = a & b - if you want to use + for string concatenation for example (a lot of people complain about it for some reason). Remember drama about := operator in python? <=> operator in C++?
I think the macros and UFCS actually is the thing in nim. You don't hardcoded features into the language, instead you just write macro
All other languages have features hardcoded. When you hardcode something into your code everyone thinks it is a 'bad practice' and you should not do this. When something is hardcoded into compiler everyone is perfectly fine with spending hundreds of hours talking about this, writing RFCs and writing articles on this one. I mean. of course some things just cannot be generalized (just as you can't write generic code that does everything at once at runtime) but still, the less features you have to manually add & maintain the better it would be in the long run.
Why lisp ideas are still useful today begin so old? Because there is no enforcement of particular coding style (well, assuming you want to deal with (((((((((()))))))))). I don't partiularly like lisp itself (not a fan of dynamic typing etc.) but that's just part that is hardcoded in concrete implementation.
Guarantee safe, defined behavior by default, but still provide tools to write and abstract away unsafe code when needed.
Nim has raw pointers for interfacing with C-level code
Long-term maintainability of programs written in the language. This includes problems of inheritance, function overloading and so on.
Has single inheritance and function overloading
Package management.
Has standard centralized package management
API documentation format.
Documentation comments are a part of AST, compiler comes with built-in documentation generator
Onyx introduces powerful macros written in Lua. It allows to re-use existing Lua code and have full access to the compilation context thanks to easy debugging with Lua.
This might be easier from implementation perspective, but I think writing the whole program in one language is superior soltuion due to lack of switching. Nim macros are written in subset of nim (quite large btw: you have pointer semantics, callbacks, almost all oop features (can't do casting though))
Classes may have a finalizer defined and thus have automatic resource control.
Nim has destructor hook for objects - proc =destroy(obj: var T) = ...
Onyx implements real traits as composable units of behaviour thanks to powerful function management tools like aliasing, implementation transferring, un-declaring and renaming.
There is no automatic #[derive()] like in rust (although it can be implemented as a library using macros (my proof-of-concept one https://github.com/haxscramper/nimtraits)) and language has concepts https://nim-lang.org/docs/manual_experimental.html#concepts that are currently being reworked to make use easier - https://github.com/nim-lang/RFCs/issues/168 (currently == literally right now)
Classes and traits together impose object-oriented capabilities of the language.
Onyx has a concept of generics. Specializations of generic types may have different members and evaluate delayed macros. Specializations of functions with generic arguments may return different values and also evaluate delayed macros.
Nim has support for generics - typeclasses, concepts and type inference. Nim macros can be invoked after generic instantion and have access to the AST of type definition. You can, for example, get code for implementation of the type passed as generic parameter and iterate over all it's fields
Functions may be overloaded by arguments and return values.
Nim can only overload by arguments, not return valueus, but I would argue that overload on return types is actually counter-intuitive and does not fit into 'mostly imperative' programming paradigm of nim
Onyx has a concept of annotations, which may be applied to variables and functions.
You can use custom pragma annotation in form of {.yourAnnotation.} that can be used by other macros. Pragma itself might be a macro - for example {.async.} (support for asynchronous programming in nim) is implemented as macro https://nim-lang.org/docs/asyncdispatch.html#async.m%2Cuntyped and allows to write almost the same code for both syncrhonous and asynchronous code
The language defines a set of now-commonly used arithmetic types, including SIMD vectors, matrices and tensors, floating and fixed binary and decimal numbers, brain and tensor floats, ranges and ratios.
Nim does not have support for this, but in my opinion this is a library feature. If we try to drag things not-commonly-used things into the language we just end with feature creep. Thanks to UFCS (yes, again) and macros you can integrate absolutely anything into the language and make it feel completely native
Onyx contains several utility types, such as unions, variants, tuples, anonymous structs, lambdas and runnable blocks of code.
Nim has tagged unions (case objects), they also serve as sum types. Or you can have typeclasses in form of type Hello = float | int | string. Nim has regular tuples (int, char) and anonymous structs (also called tuples) - tuple[fld1: int, fld2: char].
More on lambdas/maps:
Lambdas - yes - actually there is little to no difference between semantics of regular procs and lambdas. If you want to write callbacks for example:
import sugar
# using regular proc
echo @[2, 3, 4].map(proc(a: int): string = $a & "ee")
# using `sugar.=>`
echo @[2, 3, 4].map(a => $a & "ee")
@["2ee", "3ee", "4ee"]
@["2ee", "3ee", "4ee"]
@["2ee", "3ee", "4ee"]
https://nim-lang.org/docs/sugar.html#%3D%3E.m%2Cuntyped%2Cuntyped
Another possible alternative to function calls in this case is a simple template mapIt. I adapted implementation from stdlib but made it simpler for this example.
template mapIt*(s: typed, op: untyped): untyped =
type Out = type((var it{.inject.}: type(items(s)); op))
var result: seq[Out] = @[]
for it {.inject.} in s:
result.add(op)
result
echo @[2, 3, 4].mapIt($it & "ee")
You can easily write own template for filtering/mapping/folding etc. without requring user to only write expression like $it & "ee" in the map body.
The mapIt expands into regular for loop to avoid function calls (it does not look really pretty though as it uses hygienic variable names to avoid collision). MapIt is actually a template - simplest form of codegen, close to C preprocessor (but without it's drawbacks like inability to take (1, 2) as a single argument, lack of hygienic variables and so on)
echo [
type
Out`gensym3646038 = type(
var it: type(items(@[2, 3, 4]))
$it & "ee")
var result`gensym3646039: seq[Out`gensym3646038] = @[]
for it in items(@[2, 3, 4]):
add(result`gensym3646039, $it & "ee")
result`gensym3646039]
It is not clear what macro actually operates on: tokens, ast, or something else - since there is no type annotations on macro signature. And the whole things is just dynamically typed lua embedded in other program which is written into statically typed language.
I don't think it is much cleaner than simply AST. Since you accept token you basically have to either use very primitive ones (like integers for example) or just reparse whole argument body (if you want to have some kind of DSL for a macro).
And now programmer has to use two languages at once - one untyped, second one is typed, they have different semantics, different manuals. For a large project you have to maintain codebase in two languages. What if a macros needs to be more complex and it's implementation is split into several modules? Now you have two different module systems in the same project. I think concept of get-ast-return-ast is much, much cleaner. You write everything in one language. If you know how to split string in compiled nim then you know how to do it in macro. If you need to declare a type you do it the same way. Same syntax, same semantics, same results.
Reimplementation of compile-time fibonacchi:
import macros
macro fib(a: typed): untyped =
a.expectKind(nnkIntLit)
proc aux(arg: int64): int64 =
if arg < 2:
arg
else:
aux(arg - 2) + aux(arg - 1)
return newLit(aux(a.intVal))
echo fib(20)
6765
NOTE this is my persona opinion for the most part.
I actually think it is a good decistion to make language easy to parse & work with too. Except for style insensetivity nim as pretty simple grammar. This makes writing own tools much easier - if you wanted you could roll your own parser. Grammar does not require to have type resolution like in C++ for example. Which means it is not a good thing (IMO) to allow multiple different languages int the same file.
The OP obviously has an existing agenda, and an extreme bias. It does not read like objective critique, and the tone is over confident, to put it mildly.
Claiming that one is required to have practical experience in a technology in order to judge it is simply a gatekeeping because if I change my mind, it would then mean either initially poor documentation or emotional bias after use.
I never rode a bicycle before, but I read all about riding bicycles, so I am an expert bicycle rider now? No matter how good docs are, you cannot say docs are sufficient to understand something. You need both...
@mratsim easily addressed all of your criticisms of the language in his post, so I won't repeat that here, except to add a few things.
Come on, isn't it the same thing about still having to write C/C++?
You want to allow inline assembly in your language, but you are criticizing inline C/C++? That seems...arbitrary. Nim allows both btw....
Nim is similar to Python and other higher-level languages with FFI.
This statement just comes off as naive. You took a quick look and dismissed the capabilities out of hand.
Wrapping a C library for Python and wrapping it for Nim are extremely different experiences. Nim doesn't have a big runtime that requires management like those languages, the FFI is much better than those languages, and macros allow you to refine it even more (by refine I mean make both safer and more ergonomic).
I would like more built in pragmas for alignment, but it's honestly a very minor wish. Again, as mratsim shows, it can be easily worked around. It's no different than having to write some small inline assembly in places.
I also do not understand why you pitched Nim's application in cryptography to me.
Because the biggest and most successful application written in Nim (other than the compiler itself) is a a crypto applicaiton.... Status. Which continues to invest in Nim. If a successful application actually running in production by a company isn't obvious to you as a measure of success, what is success to you? Really smart people chose Nim for this domain. They chose Nim for a reason, it's well suited for this task.
I could also point out there are other languages in this domain that the OP seems to have either ignored or is not aware of (which means they didn't do very good research, and shows again that they are biased.) Have you been to the Handmade Network? There is Odin, D, Jai.... Other very smart people are working on these problems today. If you want to try your own attempt, that's great, good luck, but ignore all these other efforts at your own peril.
The difficult problem of paying for open source is important, and your "Source-On-Demand" idea is interesting, though I have much reservations about it. No offense, but smarter and wiser people than you have spent years trying to solve this problem without success. Read The Cathedral and the Bazaar. That being said, world changing ideas sometimes come from determined upstarts.
Even your responses here are over confident. It's hard to take you seriously when you are so confident that you can't seem to accept criticism of yourself. You are so convinced of your own solution, then why are you here? The more I read your writing, the more I think you are just trolling.
So I say good luck! Don't let the door hit you on the way out...
Nevertheless, Nim lacks this semantic at all, and there is no way for a user to distinguish between "safe" and "unsafe" areas of code.
That's simply wrong, the unsafe features are ptr, addr and cast, all keywords. In fact, most other of your points are equally wrong and it feels like you're not interested in correcting your assumptions. That's close enough for a "troll" to me, bye, bye, good luck spreading your lies.