Tuesday, February 24, 2015

Nim is the best programming language!


Many may discount the idea of "best programming language" with "all languages have positives and negatives and that no one language could be the best". But I believe, given a set of criteria we could compare various programming languages and attempt to figure out which one is better in most criteria. I will attempt to do so in this article.
And to those who may say that "languages don’t matter; only algorithms do" - may I remind you - COBOL is (was?) a programming language.
Just so you know where I am coming from - I am very familiar with C (I have used it to implement x86 kernels) and Perl (I've read and love the Camel) and have studied the following languages - Haskell, Clojure, Go, Rust and Nim. (I'll leave Java and C++ out of the discussion because they make me bitter). I consider myself as a power vim user - I switched to Emacs about 4 years ago after being bitten by the functional bug.

Non criteria

Static vs Dynamic typing

One argument that supports Static typing is that it helps in proving correctness. The argument against it is that one can only prove that the types line up - whether the program is correct from the programmer intention point of view is questionable. Second, it is argued that static types help with refactoring. Again, to the extent of types aligning. Remember
sum = foldr (-) 0
is correct Haskell code - as in it compiles.
The argument for dynamic type system, in my opinion is flawed - they talk about "compiler does not come in the way" etc. - as in, one is allowed to write "incorrect code" and are told about it only at run time.
Anyway, my point is - if you have to shoot for correctness and speed (development and runtime) - shoot for a good design first. The rest will follow.
Design is a somewhat diluted term in the software industry so I'll go with Rich Hickey's definition - which is, I paraphrase here - the art of breaking down a problem such that it can be composed back. Essentially, your implementation must contain tiny modules each of which do one thing. This, will help writing "evident code" that is "obviously correct; instead of, without obvious mistakes"[0]. The type system of the language per se does not help or come in the way of design.

IDE support

I'm just saying that I am not considering that as a parameter for language comparison. If you think that's a grave omission, you should stop reading this article here.

Indentation level as block structure

I can totally get it if you hate the idea. I myself did not like it one bit. It was only after I studied Haskell that I learnt to bear it and later love it.
So I feel - "Don't judge a language by it's choice for block structure"!

Software Transaction memory, CSP style channels

Clojure has demonstrated that these things can be built as a library when your language is powerful enough.

Criteria

Higher order function / Function as first class value

Lambda calculus is "Turing Complete" and it contains nothing more than function definition and function application. There are plenty of articles on the internet that demonstrate how, using just the capability of function definition and function application, one could build all the computing constructs - starting from numerals, booleans etc.
Even in "Why function programming matters", John Hughes demonstrated how vital higher order functions are. Of course, he also said that lazy evaluation is vital - I disagree there since one can build "lazy evaluation" once equipped with higher order functions [2].
Therefore, a programming language that has first class functions is more powerful than a programming language that does not have it.
Almost all modern languages (new versions of old languages) support hugher order functions in some form or other.

Macro support

If the word macro evokes "C macro" / "conditional compilation" in your mind, your probably also believe macros are bad. On the other hand, folks from LSIP side believe that macros give them that extra power (super power of programs that write programs [4]).
Therefore, a programming language that does not have a "powerful macro" (access to AST at compile time) support is inferior.
Languages (that I know of) that support macros are - Lisp/Scheme, Clojure, Rust, Nim,

Strict (Applicative order) evaluation

According to "Why Functional Programming Matters" - Lazy evaluation is one of the two key features that is necessary for a language to facilitate modularity. The other feature being higher order functions. Two points to keep in mind here
  1. Lazy evaluation can be implemented in strict languages - http://matt.might.net/articles/implementing-laziness/
  2. Laziness mixed with IO is unpleasant (to put it very mildly)
So I conclude that a language that has Laziness by default and a tedious way to do/reason about IO is undesirable.
The only "mainstream" language that has lazy evaluation by default and unreasonable (as in hard to reason about) IO is Haskell.

Reach

These days I almost always have to deal with Mac, Linux and Windows and switch between them. On top of that I play with Raspberry-pi. So if I have to invest "10,000 hours" I'd like to it to be in a language that I can use across all machines (of today and tomorrow).
A language that limits my reach is inferior to the one that does not.
Only C and Nim are the good languages in this category.

Ability to target machine code

It is a good idea to program an abstract machine, instead of a real machine. However, not at the cost of never being able to program a specific machine for it's cool features - or having to jump through hoops to do it.
Generating native binaries also make it easier to use it with other systems. Almost all languages (that don’t generate native code) provide a mechanism to load shared libraries (DLL's) and invoke functions.
Now, you may say that, so long as your job gets done, it does not really matter if the actual machine code is executed vs iterpreted by another layer of software. Also, you may argue that virtual machines provide additional opportunities for optimization. I don't agree with either.
First of all, I'd like to waste as little cycles as possible. Now, battery life etc are legitimate reasons for it but I argue that it is actually the essence of using computers - improving efficiency. So, it does matter to me if I am not using optimum memory and CPU cycles. I mean, if you use up more than required resources in "getting your job", the remaining resources will limit what more can be achieved.
About optimization opportunity - yes, it is possible to demonstrate some kind of runtime optimization that may not be possible on today's hardware - but that can never be something that cannot be fixed in the next version of the processor/compiler.
So, if your compiler does not emit native code, it's inferior to the one that does.
Haskell, C, OCaml, Rust, Go and Nim are the good languages in this category.

Memory management / Garbage collection

Garbage collection are of two kinds [5] - tracing and non-tracing. The tracing variety is the most common and it involves keeping track of which objects are "reachable" from root. This introduces a fair amount of non-determinism in the runtime of the program. Typically, the languages that have tracing garbage collector do not give direct access to memory. So, if a language uses a tracing garbage collector and does not give you a mechanism to handle memory directly then it's inferior to the ones that give you direct access to memory and either don’t have garbage collection or use a non-tracing garbage collection.
C, Rust and Nim are the good languages in this category.

Summary

Based on the criteria that I came up with, Nim is a clear winner :)

References


14 comments:

  1. Interesting post! I found your opinion on dynamic languages rather unique and worth thinking about :)


    Noticed you missed a few things about Rust in the post :)


    > Therefore, a programming language that does not have a "powerful macro" (access to AST at compile time) support is inferior.
    ...
    >Languages (that I know of) that support macros are - Lisp/Scheme, Clojure, Nim,

    Rust actually is the most powerful in this category, really. Not only does it have hygenic macros (http://doc.rust-lang.org/book/macros.html), it also can do expansions on the AST by running arbitrary code at compile time (http://doc.rust-lang.org/book/plugins.html).


    --


    Also, Rust *can* target most targets (everything that LLVM supports, which is a lot). Just that you might have to build your own compiler for it since they aren't shipped. I used to think the process was complicated but it turns out that with the right configure flags you can get it up and running (--target, --host, --build).

    But yeah, a language that supports most targets out of the box is much better.

    ReplyDelete
    Replies
    1. I believe Nim's macros can do expansions on the AST by running arbitrary code at compile time as well.

      Delete
  2. Thanks Manish - I inadvertently missed out mentioning Rust. I'll correct it.

    I had settled down with Rust as "the" language until I saw Nim. I believe Rust is an important language since it showed that it is indeed possible to step off the "safety vs control line" and get both.

    ReplyDelete
  3. Non criteria:
    Static vs dynamic types: The main point about static vs dynamic is how many bugs are catched in compiler-time rather than in run-time. And static types helps a lot. Static typing for me is an extra bonus. Nevertheless, IMHO a good language should have ways to deal with some kind of dynamic types.

    IDE: it's not related to the virtues of language. Period. So the "reach" criteria is not either.
    Nevertheless having a good ecosystem surrounding the language (framework, IDE, libraries, users...) is an important point. It also includes the target platforms (CPUs and OSs).

    In the criteria I think you have missed an important point: exceptions.
    IMHO A language without exceptions is crippled.

    By the way. You have missed pascal family: pascal (Freepascal), Ada, Modula 2-3,

    ReplyDelete
  4. Thanks svampa,
    I am personally biased towards static typing with good inference mechanism in place - so that types don't feel ritualistic.

    I mentioned IDE mainly to filter out those folks who say - "so what if my language is unreasonably verbose - my IDE takes care of it and I don't have to type it all".

    I think I understand when you say that reach is not a criteria - perhaps I should phrase it better. My idea is - everything else being same, a language with a wider reach is superior to the one with narrower reach.

    Thanks for pointing out about exceptions - I will write about it.

    I have some exposure to Pascal and am under the impression that it's not that different from C. I do not have any exposure to Ada/Modula - do you think they offer a new dimension to the criteria?

    Regards,
    Kashyap



    ReplyDelete
  5. I am also biased towards static typing, I have written the opposite of what I meant ;-). I think static typing is a "must have". Static typing detects many errors in compiler-time and is faster than dynamic typing.
    Nevertheless I also think that a language should have some elegant way for dealing with variables of unknown type at compile-time. (example: dealing with databases).

    About reach criteria. It also applies to IDE: "Everything else the same, a language with a good framework, libraries etc is superior to that with no IDE etc in the market" Also there is a full scope of applications with GUI, not everything are servers, embedded systems or algoritms demos with console, so some kind of GUI, or bindind to GUI must be there.

    Another point important for a language is communication with other languages, dynamic libraries etc.

    About pascal. It is very different from c since the beginning: It's hard typed, no inline assignment, booleans, sets, records, enumerations etc. Although old pascal ,as c, didn't have many modern features: no first class functions, no exceptions and no objects.

    Modern pascal "object pascal" (mostly from Delphi) has nothing to do with old Pascal (let alone C) Check Freepascal or his RAD Lazarus.

    New criteria: Talking with other languages, generics, ultra-hardtyped, multitasking

    ReplyDelete
  6. Hello, I prefer Rust over Nim.

    ReplyDelete
  7. Nice post! Like the clear conclusion :) I'm missing an inclusion of FreePascal in the comparison though. It is a statically typed, native code producing language without GC, with readable syntax, and excellent cross-platform support (it has THE GUI builder, in Lazarus, supporting the three big desktop platforms). It is doing excellent, in spite of its relative obscurity (seemingly mostly stemming from people's bitter feelings about what happened to Delphi), and my own unscientific tests show it is at least in the same ball park as other compiled languages in terms of speed: http://saml.rilspace.org/moar-languagez-gc-content-in-python-d-fpc-c-and-c

    ReplyDelete
  8. May I ask how You think (beginning of 2017) about NIM (and contenders)?

    ReplyDelete
  9. I'll write an update shortly ... I've moved on from Nim to Gambit scheme though ... It's not that I love Nim less but I love Gambit more ;)

    ReplyDelete
  10. Hope You find time to write an update. Looking forward. BR

    ReplyDelete