A few months ago I had a look at Nimrod the language, a statically typed, compiled, garbage collected language with Python like syntax.
I promised I'd give it another look once they added threading support and just a few days ago I noticed it was there in the change log. I remember the website talking about a STM model, however that does not seem to be the case. Instead they went for separate heap memory per thread, much more similar to how Java does it. However with more lightweight objects, more like how boost or Python does it, just classifying a function as "thread", and sending a function pointer to createThread(). I'll get back to this later.
There has been a while since I last looked at Nimrod and I wanted to refresh what little I picked up, I decided to port a Connect 4 game I wrote in Python just yesterday for no particular reason. I was thinking we could walk over the results here, discussing the whats and whys on Nimrod for you who've never seen or heard of it before.
from strutils import parseInt, splitLines const size: int = 5 tokens: seq[string] = @["X", "0"] var currPlayer: int = high(tokens) field: array[0..size-1, seq[string]] type position = tuple[x,y: int]
These first lines simply sets up some stuff we need for the game, two constants, two variables. The first being a simple int and the other what Nimrod calls a sequence, basically a simple linked list. In Nimrod all collections are type-safe and bounds checked, bounds checking can be disabled by a compiler option. The function high() takes a collection and returns the highest valid index for that collection. This is because arrays doesn't necessarily begin with index 0. So I could just as well have indexed the field var from 1 to size, however this would just confuse me. The field variables is, as we can see, an array of sequences of strings. This creates a 2D game board. The a..b notation is just sugar for notating the interval between a and b.
The two last lines defines a custom type, just like typedef from C++. A tuple, just like we know them as std::pair in the same language.
proc get(x, y: int): string= if 0 <= x and x < len(field) and 0 <= y and y < len(field[x]): return field[x][y] else: return " "
This piece defines a procedure taking two ints, x and y, returning a string. The if -part is not quite as neat as it would look in Python, but apart from that, the code is identical. Yeah even the indentation rules are the same, except Nimrod does not allow tabs, at all. It also gives compilation errors if you indent your comments wrong.
proc place(): position= var col: int = 0 while true: try: col = parseInt(readline(stdin)) -1 break except: echo("Invalid input") if col < 0 or col > high(field) or len(field[col]) >= size: echo("Invalid placement") return place() field[col].add(tokens[currPlayer]) return (col, high(field[col]))
This procedure reads a single int from the standard in, does some bounds checking and if everything is valid it adds the current players sign to the game board by adding it to the end of a particular sequence. We return a tuple of our custom type "position" Arrays and sequences behaves just as you might expect. Very similar to Python.
proc display()= var buff: string = "" for i in countdown(high(field), 0): for j in 0..high(field): buff = buff & "|" & get(j, i) buff = buff & "|\n" echo(buff)
The display function loops through the array, using the get function to get either a player token or a space. We use a built-in called countdown which does exactly what it's name implies. The next thing worth to note is the '&' string concat operator. A bit unnecessary since the language is statically typed if you ask me. It would have made a lot more sense to do something like this in say Python, where I am just sick and tired having to write stuff like "number: " + str(i).
That covers just about everything unique about this program, you can check out the entire script on gist and compare it to the Python original further down on the page. The code is very similar indeed. The creator of Nimrod has really succeeded in bringing Pythons simple syntax together with a C-class compiler. The entire binary lands on 114kB with no dependencies.
I enjoyed porting this little script, the languages are very similar and enjoying Python, one really enjoys Nimrod as well. Coding in vim went really well, despite no auto completion and stuff like that. It reminded me of when I developed a lot of Python a year ago.
Like I mentioned earlier Nimrod got threading support implemented just a while back and that's what I really wanted to check out. I just did a simple example and I thought we could check it out here.
const numThreads = 500 var threads: array[0..numThreads-1, TThread[int]] dataBlob: array[0..64, array[0..64, int]]
The concurrency model in Nimrod is based on message passing, and the template of the threads decides what kind of messages the threads accept. In this case. Here we set up an array with 500 (to be) threads accepting ints. We also set up a dataBlob consisting of a 64x64 int array.
The next part is where the magic happens. First I'd like to mention a concept Nimrod calls Pragmas. A smart way to send additional information to the compiler without introducing a bunch of new key words. The only Pragma I've used so far is the "thread" Pragma, which makes the compiler check all heap manipulations made by the anoted function, in order to give warnings about any unsafe operation. (such as composing a data structure made up from memory allocated from different threads) From what I've gathered so far is that this warning is to become an actual compiler error in later versions. With this I hope the beforementioned STM system make a show.
proc print(runs: int) {.thread.} = var n: int = runs tid, temp: int while (n >= 0): temp = dataBlob[n][n] tid = cast[int](mythreadId[int]()) echo("I am thread #", tid) dataBlob[n][n] = temp + 1 n = n - 1
Here we see the "thread" pragma in action, although in this particular example it does absolutely nothing. I also show of a simple cast, the way you do it in Nimrod. We take the thread id (a pointer) and cast it to an int so we can print it. Everyone with a keen eye will already have noticed a horrible atrocity in this code. The completely unsafe manipulation of the dataBlob global variable. This is however entirely on purpose, wanting to see just how badly it screws up. And yes indeed, on my dual core system we do loose 3-4 updates per run. Nimrod however does provide a simple method called atomicInc() which takes an int and updates it atomically. You can also use something called a TLock, which works just as a normal non-reentrant lock in Python.
for i in 0..high(threads): createthread(threads_, print, high(dataBlob)) jointhreads(threads)
Continuing the program this is how you create threads. You take the TThread object created earlier, give it a function and initial parameters. It just starts working afterwards. We join all the threads so they finish before we continue.
This was an initial view at it. I must say I do like Nimrod and will definitely check it out more soon and there will be more material on the blog soon. If you want to know more about Nimrod I really recommend reading the manual, a great read. Getting started with Nimrod is also really easy and a step-by-step is provided on the language website.