I strongly disagree with the rationale for not supporting unsigned integer types by default. In fact, I think unsigned should be the default, and the advice on the Escher Technologies blog is harmful.
Other than that, Nimrod has been a pleasure to use. If you need a metaprogramming language to generate C, use Nimrod.
I'll assert that a majority of C programmers do not understand unsigned types and misuse them.
It's quite common to end up needing to use both signed and unsigned types in the same expression and to end up invoking undefined behaviour trying to protect yourself. C overflow semantics (i.e. undefined) in particular.
Signed types should be the default, and overflow should be defined as wrapping two's complement. There's very good reasons why C# and Java chose these defaults.
The places where unsigned numbers are justified are incredibly rare: bitwise operations, and working with >2GB heap sizes on 32-bit machines. That's about it. In almost every other circumstance, using a signed type (the next size up if the range is inadequate) is better.
Especially pernicious is using something like size_t for things like container entry counts. It's very common to add and subtract with container counts, and very easy to end up inadvertently wrapping or mixing with signed types in common container algorithms.
PS: my experience was deeply coloured while working on the Delphi RTL, making it overflow safe. It was not trivial.
PPS: it was revealing that the comments on the article arguing in favour of unsigned types repeatedly made programming errors and invoked undefined behaviour. That's because unsigned types are misunderstood and misused.
Third case, dealing with raw data from networks, from devices, from flash memory etc. where you actually need to manipulate every bit accurately.
Fourth case - situations in which you have a scalar quantity, and do not care about positive or negative.
Overflow is a separate issue and you must either make sure you either know the range of the data and pick an appropriately sized storage type, or explicitly check for overflows.
> Third case, dealing with raw data from networks, from devices, from flash memory etc. where you actually need to manipulate every bit accurately.
I just started working on this kind of application (well, USB protocols, but same issues) in Nimrod.
First of all: just do "import unsigned" and you can work with unsigned just fine. I don't see why this is an issue, any more than that you have to do "import libusb".
Second: I found that, knowing that unsigned types are a second-class citizen lead me towards writing a much better library. It encourages you to expose only signed integers to the next level up in the app. The next thing I realized is that this will very quickly cause Nimrod programmers to write and adopt a "Protocol Buffers"-like library for dealing with network protocols, binary files and similar things. And that will be a much better solution than dealing with things like unsigned integers and endianess in manual code.
> In that loop one should check that len is at least 1 first...
No. The code is correct in that regard if you use int for i and len. Having to check len is an artifact of using unsigned.
> I read that page, or at least some of it, it mostly seemed to be opinion rather than solid reason.
I'm not talking about the comments. I'm talking about what Bjarne Stroustrup (the designer of C++) and Chandler Carruth (Google's LLVM lead) are saying in the video (and which is summarized in the top comment).
Only if a sub 1 len is valid input, which it may not be. Either way the example is contrived and does not cover all other situations in which a scalar quantity is required. Not all are loop controllers.
I did not watch the video, no time right now. The top comment tells us what they think, not why.
The problem lies with len-1 underflowing, not the loop that uses the result. For another example, to test congruence of x and y modulo m, you check if (x-y) % m == 0. This also blows up for unsigned integers if y > x unless m is a power of 2.
Even iteration is not always over containers or some other structure in a way where the boundaries can be inferred and do not have to be computed, and not always in an order that is implemented by an iterator (for example, some in-place sorting algorithms).
It's a problem with C for loops. There is really no good way to write a counting down loop using a for loop. The fundamental problem is that a for loop is: test a condition, run a loop body, then run a step function. This is suited to counting up loops but when counting down what you want is: test a condition, run a step function, then run a loop body. If you use while loops instead of for loops, you will see the two loops are symmetrical and there is no underflow for unsigned types. See http://ideone.com/1ABiCw
I don't like C for loops, either, but this can happen with other types of for loops, either. E.g. in Pascal:
for i := 0 to len-1 do begin ... end
This is counting up, not counting down, and len-1 is still going to be either 2^k-1 or result in an error when you're using unsigned integers.
While loops also don't fix it (not to mention that they have their own problem, such as forgetting or misplacing the iteration step). The primary issue is that len-1 is underflowing. Yes, you can avoid calculating len-1, but by that token, no error is ever a problem, because they can all be avoided with enough diligence and foresight. In that universe, Heartbleed never happened and the Ariane 5 never blew up. It is, however, not the world we live in.
That's a representation problem. C/C++ use half open ranges and have no problems with unsigned integers.
This is also a non-issue in C++ as you have iterators and counting loops are easy to abstract away using counting iterators or <algorithm> style functions.
Even iteration over explicit ranges of unsigned integers can be handled by a Python range()-style iterator, without the risk of this bug (since the iterator itself is responsible for avoiding this gotcha).
Only if the language has ONLY iterators that are of the [closed, open) kind of interval. If you have, e.g., Smalltalk/Scala-like to:/to iterators (even if you have also iterators that exclude the upper end), you get the same problem in conjunction with unsigned integers (except that these languages use signed integers, so the problem doesn't show up).
It seems unreasonable to me that you need to write out the iteration variable 3 times in a C for-loop; I had a bug a couple weeks ago that took me longer to figure out than I care to admit. I wrote `for (int j = 0; i < N; ++j)`.
The point is that, "bare metal" is really a special case of programming.
Most general purpose programming languages don't have features specific for web-programming imported by default.
Nimrod supports bare metal programming just as fine as C. You just have to import the relevant modules, just as you'd import the URL module if you were writing a web-server.
Nimrod aims at being approachable to some of the Python/Ruby crowd as well, who are used to numbers that are always signed and can't overflow. Defaulting to the biggest available signed integers, and discouraging library writers from throwing unsigned integers at you for no reason is a decent compromise.
I think you hit the nail on the head: the problem is education. Generally, a C programmer who took the time to understand integer types and their behaviour won't make mistakes with either signed or unsigned types. But making unsigned types second class only reinforces bad habits (or what I consider bad).
> But making unsigned types second class only reinforces bad habits (or what I consider bad).
What do you mean by that? Don't you think that having to do "import unsigned" before they use unsigned types, make some people think twice? Don't you think a few people might think "Do I really understand this feature? Should I perhaps read the documentation for this module?"
Fully agree. Lot of pogrammers misunderstands signed types which results in different bugs when combining signed with unsigned.
More importantly though, not having unsigned types, as in Java, means that we have to use 16-bit int to store a byte, 32-bit int to store a 16-bit unsiged etc.
Btw, if you check any C code, you'll notice that is vast majority of cases negative values of int are actually invalid from the business logic point of view.
Every time i look at this language feature, it seems like a dream come true.
Yet, i stopped believing in santa when i was 4 so could anyone here points at its ugly parts ?
As someone who's played with it extensively (to the point of trying to build a new type system for it, though its slower going than I'd hoped as I lack the necessary skills currently, though I'm still working on it) -- it's library situation isn't as robust as I'd like. It's community is small but very fervent and super helpful (Araq and Dom are both amazing guys and will help you out in IRC whenever you need it). I'd like to see it take off more. Think of it like Go but with even more control over execution. It's a little rough around the edges, there are bugs, but it's usable right now. It's an awesome language and I love playing with it, even if you don't use it for anything in production I highly suggest using it just for the experience.
Have you tried using it in production (for some value of "production")? I wrote a web API "Hello, world!" in Jester about half a year ago as a way to learn a little Nimrod and at first glance found it to be a nice web framework.
Looking at Nimrod's AST macros (which I haven't used) I imagine Julia as a closer competitor to it than Go. They both appear to come from a "programmable programming language" mindset, though Julia arguably more so because it's homoiconic.
This is the first time I've heard of Nimrod and being an amateur C user these are the parts I don't like:
- No curly braces around anything.
- Whitespace seems to be important, just like python.
- Some nonsense about unsigned integers.
The best thing I saw was that it has C's explicit-size integers. The one thing I wish every C program would use rather than having configure guess which basic type it should use.
A "C" type programming language with no curly brackets (or equivalent) and significant whitespace is a huge negative for me and utter deal breaker. This is the first time I have heard about Nimrod and was very interested to see more. It was all looking so good until I came to this part. Literally I stopped browsing the Nimrod site and closed it down (with some regret) as soon as I saw this.
Is inconsistent. Cause bugs. Can be write like brackets don't exist (http://www.andromeda.com/people/ddyer/topten.html), nullify your argument. Pick the above link and count how much problems are masked with the illusion of the brackets.
Is unnecessary (add noise just to help the parser), and python, haskell and other languages show that could be done better.
From Python-land, if you find that no brackets are meaning it's hard to tell where conditionals & functions end, it's usually a good sign that you're logic is getting too nested.
Yes, it's a pain to work with massively nested logic, but it's actually python's way of saying, "dude! keep your functions small and simple!", which I've come to appreciate.
It's one of the things I like about python, actually, is that it really tries hard to encourage you to write good code. Once things start looking ugly and hard to understand, it usually means that the logic & method you're trying to implement isn't a great choice.
Yesterday I reported a bug which had already been fixed several weeks ago but reappeared in a new version.
After looking through the original patch and the current version, I found that the bug was caused by significant indentation. The maintainer had decided to add comments and accidentally indented the code one more level, making it part of an if branch. Braces would have prevented this.
Not the curly brace itself but I find it a good marker for a block of code. Something should mark the start and end of a loop or conditional. Bash has esac, fi, and done, for example.
I saw that you can even generate C++ code from nimrod and that you can automatically generate c wrapper with c2nim, but how does it play with C++? Can you easily use C++ libraries in Nimrod or do you have to write a C interface?
C wrappers are still generally better supported, or at least that's what I assume because that's what most people still use. I haven't personally tried wrapping any C++ code yet. c2nim does support C++ now so in theory it should be just as easy as wrapping C.
It's not "easy" per se, but totally doable (if a little rough last time I tried, but that was 6 months ago) to interface with C++. There is even a C++ backend from what I recall. Pop into the IRC or just have a play yourself :)
Rust focuses very much on low/no-overhead memory safety (especially memory safety without a GC), while Nimrod seems to be more focused at more convenient low-level programming, but without quite such a strong push for memory safety and reliable very high performance (e.g. GC is compulsory for safety).
That's not to say that Rust isn't convenient, but the quest for strong memory safety & performance guarantees doesn't come for free: satisfying the borrow checker can get a little tricky at times.
An example of this: Rust's type system is designed to allow things like building a safe shared memory abstraction[1], that is, a shared memory type that enforces non-thread-safe data can't be shared without being inside a mutex. My reading of the Nimrod FAQ[2] ("An unsafe shared memory heap is also provided") implies this isn't (as easily) possible in Nimrod.
(Disclaimer: I know a lot of Rust, but very little Nimrod.)
> My reading of the Nimrod FAQ[2] ("An unsafe shared memory heap is also provided") implies this isn't (as easily) possible in Nimrod.
Nimrod's GC is not thread safe, so you have to use raw unsafe allocation and deallocation in order to share objects between threads (last I looked anyway). This is in contrast to Rust, for which memory safety is important even in multithreaded scenarios.
Right now, Nimrod uses thread-local heaps (similar to what Erlang does). Threads can communicate via channels, and you can access global variables or the shared heap unsafely if that is needed for performance.
My understanding (from talking to Nimrod's author) is that the concurrency model is still being finalized, but that safe shared memory is going to be part of it.
Many people see them sitting in a similar space, but all things considered Rust's goal is safety, well Nimrod is more interested in metaprogramming and other features that take precedence over safety.
All in all, I have looked into Nimrod a few times. It is very, very cool. One guy is even using the Obj-C bridge to run a core of an iOS app on it. You could theoretically now write a web API, an Android app core (not the GUI), and an iOS app core (not the GUI), and have them running all off the same logic written in Nimrod.
I highly recommend checking out the forum and see the caliber of stuff these guys are putting out. Very impressive stuff.
Rust provides memory safety with no garbage collection. Like every other industrial language, Nimrod requires garbage collection in the form of (deferred) reference counting to achieve safety.
As the person who wrote that: the speedup here is primarily due to using a smarter algorithm, though Nimrod helped in keeping the constant factor low.
I expect that one could get the same performance in C, though unrolling the top levels of the recursive search would be a bit more cumbersome and having efficient bound checks for arrays in Nimrod helped in debugging some variants of the algorithm.
In short, the benefits of Nimrod here were expressiveness and safety rather than raw performance (though having performance of optimized code that is competitive with C hardly hurts, either).
I love working with Nimrod. What it lacks is some documentation. For example, if you want to use PostgreSQL with it, the only documentation you can find is this: http://nimrod-lang.org/postgres.html
Why would I use Nimrod and not C++? C++ matured quite a bit over the last 20 years and I'm not sure what advantages Nimrod has over it. The tutorial mentions C++ a lot of times but does not mention any advantage for Nimrod.
I only know a little bit about Nimrod. My understanding of it is limited.
That being said, I believe a big draw is its metaprogramming abilities. In Nimrod, metaprogramming is done using Nimrod. In C++ you have the very limited, backwards compatible to C, C++ preprocessor and template metaprogramming. C++ is progressing slowly towards metaprogramming in C++ with constexpr. constexpr support is much stronger in C++14.
D is similar. In D, the metaprogramming language is (I believe) D itself, or at least very similar to it. D also has strong support for compile time function execution.
Nimrod also does a lot more to guarantee safety. C++ can only do so much, due to its (mostly) backwards compatibility to C.
Other than that, Nimrod has been a pleasure to use. If you need a metaprogramming language to generate C, use Nimrod.