Real World Nim Adventures

Written by bagossyattila | Published 2017/05/01
Tech Story Tags: programming | nim | hackathons

TLDRvia the TL;DR App

This weekend, I started my first project in Nim and wanted to share my experience with the community. This includes those who are curious about Nim but are yet to try it and the more involved alike. So, let’s start with the story behind!

Background

I’m a Computer Science BSc. student in Hungary, Debrecen, mostly experienced with Java and JavaScript and with some background in functional programming (F# to be precise).

These can be seen as mainstream languages with excellent tooling and a vast number of third-party libraries. Basically there’s a lib for every task one can come up with. And in most cases, not just one lib, but at least a handful of them. Apart from the actual language features, the available libs and tools play a crucial role when comparing languages. I’ll talk about this later, but wanted to start with it.

What’s Nim?

Nim is a general-purpose statically typed and compiled programming language with a strong metaprogramming support. It’s super-fast (both the compilation and the execution) and has a nice, clean syntax. The language goes to great lengths to support interoperability with other languages (for example C or C++) through the Foreign Function Interface. That way, although Nim has its own package manager, one can easily utilize libs written in other languages.

This is just a short introduction, and I could write a lot more about Nim but it’s better if you check out the official site:

Nim programming language | Nim_The Nim programming language is a concise, fast programming language that compiles to C, C++ and JavaScript._nim-lang.org

RealWorld

RealWorld is a fairly new initiative by Thinkster. It’s intended to show you how to write the exact same app (a Medium clone actually) using different frontends and backends. These all adhere to the same API specification therefore can be mixed and matched freely. More information at here:

Introducing RealWorld 🙌_🏅 Exemplary fullstack blog apps powered by React, Angular, Node, Django, and many more — it’s like TodoMVC, but for…_medium.com

This offers a great way to compare a wide spectrum of platforms and learn the best practices of the tech of your choice.

Anyone can contribute but only seasoned developers are advised to do so. RealWorld puts a strong emphasis on readability and the use of well-established best practices. Inexperienced devs might not be able to produce implementations that follow these principles.

Weekend Hackathon

Motivation

Approaching the end of my university studies I’m a bit overwhelmed by more important stuff to do, yet I wanted to take this as a weekend hackathon challenge. As I’ve written before, I haven’t really worked with a native language since ages (apart from being a computer graphics TA and using C). The features of Nim have really appealed to me, and I wanted to give it a shot. Of course, I’ve written the mandatory Hello Word & co programs, but that’s not a serious test drive. However coming up with small scale projects is not easy for me, that’s why I was immediately attracted by the idea of RealWorld.

Still, I shouldn’t have started an official Nim implementation just for this reason. “How am I going to use the best practices with my lack of experience?” I asked myself. The answer to this question was the real fuel to my project: “There are no best practices — yet.”. As a consequence, I’ve relied on my intuition and Java/JavaScript experience.

The implementation is on GitHub at:

battila7/nim-realworld-example-app_nim-realworld-example-app - Nim implementation of the RealWorld example app_github.com

Environment

I downloaded the latest published version of Nim directly from the official site. The compiler is nim 0.16.0 and the package manager is nimble 0.8.2. The dev machine ran a 64 bit Ubuntu 15.10.

Choosing the framework

One can work directly with Servlets in Java or write vanilla http code in node. But that’s rather the case, in most situations we choose a framework that’s going to do the heavy-lifting for us. In Java I’ve been using Spring/Spark and hapi/express in node respectively. Of course there is a tremendous amount of frameworks but in my opinion these are some popular ones.

Nim offers the same: We can roll vanilla (asynchttpserver) or choose the right tool. Nevertheless we can only find two web frameworks, Jester and Rosencrantz.

Jester is the more popular option and is developed actively by a core Nim contributor, Dominik Picheta (who wrote the excellent Nim in Action book). Seems like a sensible choice, yet I settled with Rosencrantz. As far as I know, Jester won’t let me split my route definitions, they must sit in the same file (see this issue). This can be seen as an advantage, but when developing a larger or even medium scale application this is clearly not beneficial. The combinator approach of Rosencrantz and its wider range of features out-of-the-box also helped my decision.

Best Practices

The essence of this article is to start a community process which can result in an agreed-upon set of best practices about Rosencrantz and in general Nim code and can make its way to the — currently short-spoken — Nim Style Guide.

import judiciously

The way we import other modules is crucial to make our code readable. I came up with the following:

  • Use from module import x, y, z at first place. That way it’s always clear where different procs and types comes from. For self-written code, this rule should almost always apply.
  • When importing the standard library, or importing a large number of items from the same framework, use naked import for conciseness.
  • Use except in favour of the module you’re actually using, ie. exclude from the standard library.
  • When you find yourself excepting a lot, or using qualified names, use the from module import nil statement and force qualifying everywhere.
  • Alias imports only with the previous syntax: from module as m import nil .

import order

import modules in the following order so it’s always clear, where the modules come from:

  1. Standard library
  2. Third-party packages
  3. Self-written code

import vertical spacing

Put an empty line between the aforementioned sections and leave two empty lines after the last section.

Emphasize the public interface

Sometimes the documentation of a module is not enough to understand the way it works or the way it should be utilized. Consumers of the module can only access its public interface. These are the declarations marked with the export symbol.

When placing stuff around in a module, always put an emphasis on exported declarations. For example, forward declarations of the exported procs should come before module-private procs. Again, exported types should precede private types, furthermore they should be declared in different type sections.

Write your templates/macros

Instead of boilerplate code, don’t fear to write a template or a macro. Although this puts an additional layer of indirection on top of your code, it saves you time and if written correctly, increases code readability. Since metaprogramming in Nim is quite easy, anyone can come up with clean solutions to ugly problems.

However writing useful and readable templates/macros takes practice. Use these constructs to solve problems, don’t bend the problem to them. Make them look like an intuitive clean syntax, and not like some black magic you can satisfy your inner computer science pervert with.

Result first

The implicit result variable is a quite handy feature. Most of the times, it can be used as a fallback return value in conjunction with naked return s. In that case, setting the value of result should be the first statement of the procedure body.

Return Option[T]

Use exceptions for exceptional cases. Sometimes not being able to produce a result is perfectly acceptable. In situations like that, use the Option[T] type which can indicate whether a result was produced or not. This approach is superior to returning a tuple of type (bool, T) or throwing unnecessary exceptions.

Fail them Futures

Async code can fail too. One might be tempted to use Future[Option[T]] but I consider that a bad practice because Future can itself express failure. However in order to fail a Future, an exception of some type must be passed to it. My advice is to either use descriptive exceptions if the failure deserves that or use some general NoResultAvailableError when you’re not interested in the reason behind the failure, just the failure itself.

let where you can, var where you must

The title speaks for itself. Using one-time assignable variables makes the code easier to reason about and can some hard to get all-nighter bugs. Ideally, you should find yourself using var rarely. Const is even better, but it has different use cases.

do the do notation

Passing anonymous procs to other constructs should always be done (when possible) using the do notation. It eliminates unnecessary syntactic noise (proc and parentheses) which results in pleasant reading and writing experience.

Best Rosencrantz Practices

Compose with -> instead of [ ]

Rosencrantz provides to operators to compose handlers: -> and [] . The documentation states that the [] syntax is nicer when composing a large number of handlers. I think the opposite.

-> expresses composition better because it looks like what its effect is: composing handlers after one each other. It doesn’t have a closing pair as [ does therefore won’t produce as much noise, seems like more idiomatic Nim code.

Split routes: never use -> and ~ together

Rosencrantz offers another operator ~ which can be used to create alternative paths. Using -> and ~ together is great, but results in a hard to read and even harder to maintain code. We all know, that typing a bit more now will save us from bigger problems in the long run. With that in mind I advise the following.

Create (with all verbosity) all handlers in a let statement (using only -> operators) and then combine them with the ~ operator. Somehow like this:

letgetSingleUser =get ->pathChunk("/api/users") ->segment do (username: string) -> auto:ok(username)

getUsers =get ->path("/api/users") ->ok("Users")

let handler =getSingleUser ~getUsers

All routes stand on their own and can be freely modified. Also, because they are assigned to individual variables, the name of these variables can act as a small piece of documentation. This style might be verbose, but it will definitely pay off.

Create your own handlers

Rosencrantz can definitely do some cool stuff, but most of the exercise is left to the developer. When creating a web API, the topmost layer should be responsible for input validation, authentication and authorization, input parsing and the correct representation of the output. These tasks should be done in all of the application’s routes and rewriting over and over is cumbersome and error-prone.

Fortunately, Rosencrantz is easy to extend with custom handlers. Use this feature to extract the aforementioned tasks into custom handlers. That way routes will only include the necessary and specific logic.

Bad Parts

I’d also like to point out some of the currently bad or missing parts that should be enhanced in order to provide a more streamlined developer experience. This is just a quick and dirty list, and most of the things might be already fixed on the bleeding edge branch.

Unique module names

You cannot create two modules with the same name. So using model/user and service/user is prohibited because user is not unique. This restriction should be removed.

import failure

When importing a module that contains a declaration that has the same name as the module, the compilation fails. Importing handler from article/handler crashes the compiler.

Cryptic template/macro error messages

Not as bad as the C++ counterpart but the error messages produced by erroneous template instantiation or macro execution should be more friendly.

Third-party packages

This is not a problem with the compiler or the tooling, but an overall problem with the Nim ecosystem. For a specific task, only one or two solutions exist or no third-party package exists at all! Even though C libs can be used, that does not compensate the lack of pure Nim or even wrapped C libraries.

Conclusion

Nim has a huge potential because it’s fast, clean and easy to use. However the compiler and the tooling is still not stable and the number of third-party packages is quite small, and most of them is not even maintained any longer. It also misses well-established best practices and agreed-upon methods. These are the biggest obstacles preventing Nim from getting more popular.

And now about RealWorld… The weekend is over, and so is my hackathon. I’ve set up the environment (frameworks, DB) and developed a stable foundation which I’ve also implemented the /api/users routes upon. The profiles and articles routes are still to be done, but unfortunately I won’t have too much time for them. For that reason contributors are warmly welcomed :)

Taking everything into account, I had a great time working on this project. Nim feels like a breath of fresh air after all those VM/managed/interpreted languages. Sometimes it requires a bit of plumbing, but overall provides a pleasant development experience. Highly recommended!

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!


Published by HackerNoon on 2017/05/01