Dominik Picheta's Blog

About Nimrod's features

written on 27/10/2013 22:10

Nimrod is a relatively new programming language that is severely underrated in comparison to other new programming languages. This article aims to change that, its goal is to show off Nimrod's excellence and to raise awareness about this brilliant programming language.

Nimrod is a compiled programming language with a static type system which puts it in the same category as the Rust, C++ and Go programming languages. The reason that I was attracted to Nimrod in the first place was because of its syntax which unlike the syntax of the 3 languages mentioned above is not C-like. There are no braces and no semicolons. Instead Nimrod's syntax is similar to Python as it uses whitespace to delimit scope.

Let me demonstrate this by showing you a quick code example.

proc fizzBuzz(x, y: int) =
  for i in x .. y:
    if i mod 15 == 0:
      echo("FizzBuzz")
    elif i mod 3 == 0:
      echo("Fizz")
    elif i mod 5 == 0:
      echo("Buzz")
    else:
      echo(i)

In the example I declare a new fizzBuzz procedure which takes two parameters of type int, in the body of this procedure there is a for loop which iterates from the value of x to the value of y. This classic FizzBuzz example allows me to demonstrate a lot about the language. What is immediately apparent is the indentation based syntax. At first glance you may think that this code looks very much like Python and while on the outside Nimrod is just that, once you explore it further you will see that it is so much more. The example also shows that Nimrod is statically typed, and I will talk about that at length later in the article.

Now that I have given you a quick taste of what Nimrod looks like. It is time to talk more in-depth about the core features of the language, its compiler and the surrounding tools.

Nimrod is compiled

Nimrod code is compiled ahead of time. First the Nimrod code is translated into C code by the Nimrod compiler and then it's compiled using a C compiler into a binary. This means that Nimrod software is very portable and that wrapping C libraries is a joy. Thanks to this you are able to write software which will work on a wide range of hardware, I have found that my applications work perfectly on both my desktop and my Raspberry Pi. The resulting binaries are also dependency free and very fast as they can take advantage of all the optimisations provided by GCC or other C compilers.

These features make Nimrod suitable for low level systems programming, allowing for example the creation of operating systems.

Another important feature of the compiler is that it knows exactly which modules your application depends on. It compiles them all for you so there is no need for makefiles, the compiler implements the build system. Compiling code is condensed into a single, simple command: nimrod c file.nim.

The Nimrod compiler also supports other compilation backends including JavaScript, C++ and Objective C. This allows you to write scripts which will work in your web browser and apps which will work on iOS devices.

Realtime Garbage Collector

Nimrod's GC is perfect for real-time software like games. It uses a deferred reference counting algorithm which can have pause times as small as 1ms. The GC is also deterministic, it will only run whenever you try to allocate memory on the heap. The memory usage is very small.

Two functions in the standard library allow the customisation of the GC's behaviour. These include the GC_setMaxPause function and the GC_step function. The former allows you to tell the GC the maximum amount of time that it is allowed to use to perform a collection, this ensures that the GC will not pause your application for longer than you have specified. The latter function allows you to explicitly perform a collection, so you can disable the GC's automatic collections and be in full control of when the GC does its work.

The GC can be switched off by using the --gc:none compiler flag if you are really determined to manage memory yourself. However it is important to note that memory can still be managed manually even while the GC is enabled.

Static typing

Nimrod is, unlike the language it resembles, a statically typed programming language. Some may think that static typing hinders their productivity, I however will not share my opinion about this ongoing dispute in this article. Instead I will focus on the features that Nimrod's type system offers.

Let's say we want to implement a Maybe[T] type. That is, a type which either holds a value of type T or nothing. This may be familiar to people coming from functional programming backgrounds, specifically Haskell.

type
  Maybe[T] = object
    case hasValue*: bool
    of true:
      value*: T
    of false: nil

var x = Maybe[string](hasValue: True, value: "Hello World!")
echo x.value # Output: Hello World!

This example shows Nimrod's generics and object variants at work. The hasValue field determines whether the value field is present in the object. If it were to be set to False then accessing the value field would cause a runtime exception. Nowadays in some cases this error can be detected at compile-time.

This is not the extent of the power of Nimrod's generics.

proc intOrString[T: int | string](x: Maybe[T]) =
  assert x.hasValue
  when T is int:
    echo x.value + 5
  elif T is string:
    echo parseInt(x.value) + 5

In this example I define a function intOrString which takes a value of type Maybe[T] where T is only allowed to be an int or a string. If I were to pass a type of Maybe[float] to this function I would be met with a compile-time error. The is operator is used in the body of the function to determine the type of T at compile time. Depending on this type different code is generated at compile-time.


I will mention quickly that Nimrod supports a range of different data types, these include sets, tuples and subranges. Nimrod distinguishes between traced and untraced pointers. A ref T is a traced reference to type T, these references point to a garbage collected heap and are therefore memory safe. Untraced references (or pointers) point to manually allocated objects and are unsafe but they are necessary for systems programming and wrapping of C code. By default references can be nil but a not nil annotation can be added to reference types which will instruct the compiler to ensure that the type is never nil.

Procedures

Procedures in Nimrod are synonymous to functions in other languages. They can be overloaded and support named arguments which can have default values. Operators are also supported, with the most commonly used being the $ (dollar) operator which returns a string representation of a type which implements it.

Anonymous procedures are supported and a special do notation is also provided:

import algorithm
var x = @["foo", "123456789", "i", "doloroo"]
x.sort do (x, y: string) -> int:
  cmp(x.len, y.len)
# x = @["i", "foo", "doloroo", "123456789"]

The sort procedure takes a sequence of any type and sorts it based on the results that the procedure that is passed to it generates. The do notation is syntactic sugar for a closure being passed as the last argument to a function. In this case it will pass an anonymous procedure which will compare the lengths of the strings. The cmp procedure is in the standard library and it returns a value which is less than 0 if x < y, a value of 0 if x == y and a value greater than 0 if x > y. It is this value which determines the way the sequence will be sorted.

A unique feature in Nimrod is that it forces you to explicitly discard returned values from procedures if you do not wish to use them.

proc ret: int =
  result = 5
discard ret()
echo ret()
ret() # Error: value returned by statement has to be discarded

This may look annoying but it is in fact very useful. Especially when working with C functions where the return value indicates a success or a failure in which case this feature encourages you to implement error checking.

The example above also demonstrates another unique feature of Nimrod. The implicit result variable. This variable is automatically declared for you whenever your procedure has a return type.

Procedures can be called in one of two ways, f(a, b) is the same as a.f(b). The latter is referred to as "method call syntax".

Effect system and Exceptions

Exceptions in Nimrod with an effect system which provides exception tracking.

Nimrod exceptions are very traditional. Exceptions can be caught using a try: .. except: .. statement and they can be raised using the raise keyword. Exception tracking is a great addition to this, a procedure can be proven to be exception free by specifying that it should raise no exceptions. This is done by specifying an empty raises list in the procedure declaration:

proc unsafeCall() =
  raise newException(EIO, "Foo")

proc safeCall(): bool {.raises: [].} =
  try:
    unsafeCall()
    result = true
  except:
    result = false

The compiler performs an analysis to determine whether the safeCall procedure will raise any exceptions. In the example above the try statement ensures that there will be no exceptions raised and so the requirement is satisfied. If the try statement is removed then the compiler will refuse to compile the code.

The effect system goes much further than that with tag tracking. One of the tags supported is the FIO effect, it is further split up into a FReadIO and FWriteIO effect. This allows you to prove exactly what type of IO your function can perform. Tags can be user-defined and the stdlib also contains many more.

Iterators

Like many language Nimrod supports iterators.

type
  Foo = tuple[x,y,z: int]

iterator items(a: Foo): int =
  yield a.x
  yield a.y
  yield a.z

for i in (1,2,3):
  echo(i)

# Output:
# 1
# 2
# 3

The for loop in this example implicitly calls the items iterator for the Foo type. Iterators can be overloaded just like procedures and so overloading the items iterator for a custom type will allow you to iterate over it using a for loop like in this example.

Metaprogramming

This is possibly one of Nimrod's greatest features. Nimrod's metaprogramming support consists of templates and macros. Templates are a simple form of macros where you declaratively specify the code that you would like to be generated. Whereas macros work on the AST allowing it to be transformed at compile-time.

The combination of the two allows DSLs to be written. The htmlgen module defines a DSL for generating HTML:

import htmlgen

echo h1(a(href="http://picheta.me", "My Blog."))
# Output: <h1><a href="http://picheta.me">My Blog.</a></h1>

Templates have many uses, they can remove boilerplate and they allow you to extend the language easily.

import macros, strutils
template table(name: expr) {.immediate.} =
  proc `del name`() = db.exec("delete table " & astToStr(name))
  proc `insert name`(values: varargs[string]) =
    var vals = values.join(",")
    db.exec("insert into $1 values ($2)" % [astToStr(name), vals))

table test

insertTest("foo", "bar")
delTest()

In this example the table template generates two procedures. The procedures use the table's name as part of their own name which ensures that you will not get the table's name wrong at compile-time.

Standard library

Nimrod has a very rich standard library. Ranging from parsers for JSON and XML to HTTP clients and database wrappers. There are also many wrappers for C libraries available, many written by the community and available as a Babel package. A full list of modules in the standard library can be found here.

Native Unicode

Go makes the fact that it supports unicode very prominent with a unicode "Hello World" on its homepage. Nimrod can do the same thing:

echo("Hello, 世界")

Unlike Go however, it manages to do it in a single line. You can also use unicode characters in variable, procedure and type names.

Tools

There is plenty of nimrod tools, some of which I have created personally.

c2nim

This tool allows the translation of C header files into Nimrod code which makes generating wrappers very easy. Recently it also gained support for C++.

endb

This is the Embedded Nimrod Debugger and acts as an alternative to GDB. It provides a more natural debugging environment for Nimrod applications.

Babel

This is a package manager that I have written. It is currently considered the official Nimrod package manager and is home to many packages created by the Nimrod community.

Aporia

This is a Nimrod IDE written in Nimrod. Another project of mine. It uses GTK+ as a widget toolkit.

Community

The community is great. The creator of Nimrod hangs out in an IRC channel on Freenode (#nimrod) and is very open to user's suggestions. The development is very transparent and you can learn a lot by even idling in the IRC channel.

Conclusion

Nimrod is definitely a good alternative to many programming languages out there. If you are a Python user looking for a replacement like I was many years ago then I would suggest trying out Nimrod. Even if you're not, Nimrod is still an extremely well designed language which is still evolving and your input could help make it even better.

Even though this article is long it is important to note that I have not mentioned many features that this language has. A comprehensive list of everything can be found in Nimrod's manual.

Thanks for reading.


Discussion on Hacker News and Reddit.


This article has been tagged as Nimrod , programming