What makes Nim practical?
hookrace.net
hookrace.net
As a Python user, I loved it. Every single problem I had with Python, Nim seemed to have solved elegantly. Performance, distribution, typing... Everything looked perfect, and none of the Python expressiveness seemed to have been sacrificed.
But it was transpiled to C, and the abstraction was leaky. Every once in a while the manual would mention that certain structures were not supported, and it was clear C was the culprit. I think the most glaring example were nested functions, or something similar.
I thought to myself "this will bite me in the ass sooner or later" and gave up. Maybe it's time to try again. If they plugged the abstraction holes, this will be a killer language, with applications everywhere.
proc a =
proc b =
proc c =
proc d = echo "Hello World"
d()
c()
b()
a()
Works fine.Not necessarily a problem, but unless implemented as something other than nested functions in C, there may be some portability issues. Worth investigating.
EDIT: Actually this raises the question of how it handles closures, since the generated code does not seem to provide for preserving the surrounding environment data. Though I may just need to dive in now that it's catching my interest.
In your example, it could be something like this:
struct variables_in_a_that_are_visible_in_d {
int aa;
};
void d_nested_in_a(struct variables_in_a_that_are_visible_in_d *ascope) {
// Variable dd, not accessible from a
int dd = 42;
// Increment a's aa
ascope->aa += 1;
}
void a() {
int aa = 0;
// Pack up variables for d()
struct variables_in_a_that_are_visible_in_d ford;
ford.aa = aa;
// actually call d)
d_nested_in_a(&ford);
// Unpack after calling d()
aa = ford.aa;
// Continue with a()
// ...
}
I'm a little surprised people are so hung up on this. They don't call C "portable assembly language" for nothing. If it can be done compiling to native code or some virtual machine assembly language, it can be done compiling to C. proc a =
var x = 10
proc b = echo x
b()
a()
Comes pretty close, the struct is further up: https://gist.github.com/def-/c496cd42774617fd0271#file-nestc...The struct may have holes that contain variables that aren't used by the nested function, but that doesn't matter. Also, you would have to make sure to flush values from registers onto the stack, and, depending on the ABI, would have to explicitly push arguments passed in registers onto the stack (that would have the struct contain a return address, but that's not a problem, either), if you need them to pass arguments to the nested function.
Anyway, I think I'm overdue on reevaluating the language. I can't wait to replace Python/Go/C/Java with this.
All the transpiler has to do is invent a globally unique name for each function, for instance. Of course they might not have implemented it yet, but there shouldn't be any firm reason why it can't be done.
I would expect nested functions to close over their context. Still implementable in C of course (or ghc -fvia-c wouldn't work), but not as trivially.
From the manual: http://nim-lang.org/manual.html#closures
Nim's compiler converts the AST into an intermediate representation that can be compiled to several backend. The primary backend is C source code, but it also supports outputting Javascript (experimentally) or interpreting the intermediate representation ala Python.
The difficulty of developing the IR -> C transformation certainly influences the features of the language, and certain features, like tail calls, can't be implemented because Nim expresses functions as C functions for the benefit of foreign code. I wouldn't say it's a leaky abstraction though, not any more than C is a leaky abstraction over machine code.
It looks like work/research in this area has been going on for at least ~20 years.
int f(int a, int b){return f(b,a);}
compiles into
int f(int a, int b){start: int t=a; a=b; b=tmp; goto start;}
Seems a shame to make such a pedantic and insubstantial comment on such an interesting article about a language I'd not come across, but thought the author might like to know.
It looks like Nim has some ability to reason about arithmetic identities natively, which is neat. In practice Rust uses iterators to encode the same sorts of common patterns in the type system.
pcwalton's remark is excellent but "automated proof technology" is not a well defined term. What I mean by this is that it goes beyond what a traditional type checker can do. I don't think Rust can do exactly the same things via its borrow checking and its iterators, but I might be wrong. Note that the very same analysis also proves your index bounds are correct.
Nim's disjoint checker is so experimental that its docs are indeed very terse and we only have a couple of test cases for now. That said, the disjoint checking is restricted to the 'parallel' statement, so its complexity only affects this language construct and not the whole language. You can think of it as a macro that does additional checking.
Reading http://nim-lang.org/manual.html#parallel-statement point by point (disclaimer, I think Nim is very cool, but safe parallelism is one of Rust's strongest points):
> Every location of the form a[i] and a[i..j] and dest where dest is part of the pattern dest = spawn f(...) has to be provably disjoint. This is called the disjoint check.
The type system guarantees disjointness when necessary: a mutable reference `&mut` is guaranteed to be the only way to access the data it points to at any given point in time and iterators over mutable references preserve this guarantee, so disjointness-for-writing is automatic.
> Every other complex location loc that is used in a spawned proc (spawn f(loc)) has to be immutable for the duration of the parallel section. This is called the immutability check. Currently it is not specified what exactly "complex location" means. We need to make this an optimization!
Rust generalises immutable to "safe to be used in parallel"; everything that is (truly) immutable satisfies this, but so do, for example, memory locations that can only be used with atomic CPU instructions, or values that are protected by a mutex. There's no way to get data races with such things, so they're safe to refer to in multiple threads.
This is captured by the Sync trait (types which can be used from multiple threads in a shared way implement it): http://doc.rust-lang.org/nightly/std/marker/trait.Sync.html
> Every array access has to be provably within bounds. This is called the bounds check.
Rust's iterators give in-bounds automatically, but there's also no restriction about requiring bounds checks or not. (What does this rule offer Nim?)
> Slices are optimized so that no copy is performed. This optimization is not yet performed for ordinary slices outside of a parallel section. Slices are also special in that they currently do not support negative indexes!
I'm not sure what this means in the context of Nim, but passing around a Rust references never does a copy (even into another thread).
(Disclaimer 2: it's not currently possible to pass a reference into another thread safely, but the standard library is designed to support it, the only missing piece is changing one piece of the type system, https://github.com/rust-lang/rfcs/pull/458 , to be able to guarantee safety.)
More of that please!
They can be made to work, but you regularly stumble on some more or less obscure bugs. The short of it, is that you have no guarantee that the compilation step won't introduce bugs in your program, so you need a solid test suite for the compiled binary. It's feasible, but clunky, and you really get the feeling that the compilers aren't first-class citizens.
I never had the opportunity to try it out myself though.
It's not really competing in quite the same space as say, D or Rust. It's like a statically typed scripting language.
It's easy to write (a) an unsafe language that has no GC, or (b) a safe language that relies on GC for safety. It's also easy to write a language that is in category (b) but lets you drop down to category (a) in an unsafe dialect. It's a very difficult problem to write a safe language that does not rely on GC, and I know of no other industry language that has done it. Unfortunately, people often lump Rust into categories (a) or (b), without realizing that what makes it interesting is that it isn't in either. Nim may be in category (b) (if the thread safety issues in the GC have been solved, or you use Boehm, or your app is solely single-threaded) with the ability to drop down to category (a), but it is not in Rust's category.
While Ada doesn't provide the parallelism safety mecanisms from Rust, is is pretty much a safe systems programming language, specially the SPARK dialect.
And I do conceed that using RAII or memory pools is a bit more cumbersome than in Rust.
EDIT: Forgot to add that for me systems programming safety has been for a long time what Modula-3, Oberon and Ada offer. Only recently it became clear to me that Rust safety module is more broad.
In general Ada code, deallocation is considered unsafe and requires specializing the Unchecked_Deallocation() procedure. This is because Ada 83 allowed for optional GC.
In Ada 83, the safe alternative was to use memory pools.
With the newer revisions, support was added for RAII and Ada the ability to define custom refereced counted data structures, similar to how they are doing in C++.
So speaking of Ada 2012, you can get Ada's safety in terms of contracts, data types, numeric ranges, constrained types, access types (Ada pointers).
For heap related safety, it is possible if RAII, memory pools or RC access types are used. But like C++, this is one area where the compiler doesn't force the developer to use it.
That's not an advantage
It is a mistake to conclude that because language X has feature Y, the entire community around language X thinks that feature Y is a positive feature.
I can tell by the downvotes. Also, you can't prove that.
Yes it is. I have been programming Python, C++, Java and C# over the last 10 years.
Python's convention for white-space beats the other ones hands down both when writing and reading large code bases.
Rust is objectively much better, but I suspect that for the next 5 years, Rust will only remain popular for systems programming but not application/web dev, and Go will only remain popular for what it's currently doing but not systems programming (with some semi-exceptions like Docker and Kubernetes, though that's not really systems programming).
It's useful to distinguish between "the language" and "the tool". Some programming languages are terrible from a language design perspective (PHP, JavaScript, MATLAB), but they still have great tools (IDEs, build tools, package managers) and libraries to help programmers create good and interesting software.
However few people will ever need this feature, and erlang/elixir probably does it better, though at a the cost of speed.
Go is much more mature than Nim and encapsulates a huge amount of thinking and experience in language and compiler design. Nim would like to be Go. But it isn't.
You might also be interested in NimBorg, https://github.com/micklat/NimBorg , which supports embedding Python or Lua in a Nim program.
# Table created at compile time
const crc32table = createCRCTable()I jest.
But seriously, D, Rust, and C++14 are about as familiar to me as Nim.
fn foo(n: i32) -> i32 {
n * 2
}
const y: i32 = foo(3);
error: function calls in constants are limited to struct and enum constructors [E0015][1]: https://github.com/Araq/Nim/blob/master/lib/pure/collections...
At the moment small projects and libraries should be fine but the lack of IDE would be an issue for bigger projects.
What exactly does that mean? Does the end-user of my binaries require a C compiler? Which operating systems are "we" interested in. I'm interested in Windows and specifically Windows CE.
Were my assumptions accurate?
And as you say, pretty much every platform in existence has a C compiler targeting it. In many cases, you use what's a called a cross-compiler, which lets you generate code for, say, Arduino using tools run on, say, Linux. But one way or the other there'll be a way to compile for it.
Net result is that as long as the code isn't doing something that is specific to the platform (memory layout assumptions, asm blocks, Windows API, etc.) the same C code will run on many different platforms. You'll have to recompile it for each one, but you won't have to change the source (much).
If I understand correctly, Nim ultimately generates C source (among other options) which then goes through the above process. So it has the same level of portability.
- Significant whitespace, but tabs are forbidden
- No block comments, save for `discard """ ... """`
- Identifiers are case and underscore-insensitive (FOO_BAR === fooBar === fo_ob_ar)Here's the reasoning: https://github.com/Araq/Nim/wiki/Whitespace-FAQ#tabs-vs-spac...
If you still want tabs you can add this at the top of your files and they work:
#! replace("\t", " ")
> - Identifiers are case and underscore-insensitive (FOO_BAR === fooBar === fo_ob_ar)This changed recently, now the first letter is considered case sensitive: http://nim-lang.org/manual.html#identifier-equality
Honestly, there are less drastic solutions to these problems. Some languages (forgot which) simply forbid mixing tabs and spaces when the tab size makes the code ambiguous. And their C example will generate an "ambiguous else" warning in D, which you can disambiguate by adding braces.
I believe a different syntax for those will be added soon: https://github.com/Araq/Nim/issues/1535
In the language design stages who in the world thought this would be a good idea? Even PHP doesn't do that.
foo_bar = blah
foobar = blah
fooBar = blah
That's someone you really don't want to be coding anywhere near, because it leads to really, really confusing code. So nim is just enforcing not being able to do it it as a language.
go format is a much better solution to the same problem.
We use "special tools" for every other language (Visual Studios, Eclipse, etc).. Calling a language feature, which give programmers style freedom, a "tortured lexical structure" is not an objective argument. Especially since we have a tool (nimgrep) which addresses the issue and takes < minute to learn. Once Nim has better IDE support, no one will be grepping in the first place.
Sure, I can use nimgrep, but I can't use _my_ grep, _my_ freetext indexer, _my_ editing constructs, and so on. I'm not going to make my environment, which works fine for almost all programming languages, bend over backwards to support your special snowflake of a language.
I'd rather just use something else.
Please, be serious.
> but I can't use _my_ grep, _my_ freetext indexer, _my_ editing constructs
Oh, but you can! These are _your_ tools, you can easily extend them by either scripting or modifying source, no problems there. Really, how much of a problem is adding a switch for underscore insensitivity to your program? Because I assume case insensitivity you had already coded.
Oh, unless by "_your_ grep" you meant a tool that someone else wrote and you're using without any real understanding of how it works and without required skill or knowledge to modify it. Right, this can happen, you're a busy man, have many obligations and no time at all to fiddle with grep. I understand.
But that also makes you completely outside of a target group of early adopters of new programming languages. So maybe stop commenting on them?
The best case for usability is that this "feature" never used, which makes it an odd design choice. The worst case is that the feature is used frequently, there is no clear language norm, visually scanning for variables is harder, search and replace requires dedicated tools, and subtle bugs abound. Far better (in my personal opinion) to make identifiers case sensitive and enforce consistency with style guidelines and code "prettifiers".
The current Nim approach is being called cs:partial (case sensitive partial) and makes the first letter case sensitive but all others insensitive. This like an awkward compromise, and needlessly complicated. The best proposal I've seen suggests making "_x" equivalant to "X" (underscore is an alias for "next letter is capital"). But I don't see it as better than case insensitive plus strong code conventions.
For a new language, I feel like one could have just forced one style unto the users, but that's just me talking.
Many of the so-called 4th generation languages (i.e. business shits derived from Cobol with DB support bolted in) are totally crazy in this sense. Not only are identifiers often case insensitive, they can also be abbreviated!
Yet people make massive amounts of money with them.
Does that mean one can not use GPL libraries in non-GPL programs? With other languages you can get around that by linking dinamically.
It's one thing to make your compile stuck it's head while doing #include "aux" in windows, but completely different to treat source code as "shell"-script.
(I see the point, and it's a great feature, but with too much power).