Simple Command Line Tools with Scala Native

Kevin Greene

 9 min read

I've been enjoying writing Scala lately, but there are some legitimate cases where being on the JVM just isn't the right fit. Despite liking Scala more than alternatives, I've had to turn to languages like Go and Node.JS to write simple command line tools. With the introduction of Scala Native, that all changes.

Scala Native allows you to write idiomatic Scala code, but instead of compiling down to the JVM or JavaScript, it compiles to LLVM bytecode, then platform-dependent machine code. This makes it perfect for short-lived programs that need a fast boot time, like command line tools.

The Tool

Pretty regularly, I find myself in a situation where a decision needs to be made, but no one particularly cares what the result is. This could be which game to play with a group of friends, where to go for lunch, or what to read next. This can be solved pretty easily with dice or coins, or even an app like Chwazi, but all options are a little slower than a command line tool.

So I set out to write a tool that, given a list of words, could pick one randomly.

Getting Started

To start our Scala Native project, after making sure we have our environment setup, we can generate a new project using giter8 via

loading

By default, the giter8 template generates a Hello World program. We can test that it works with sbt nativeLink, which compiles and links a binary file. This is a long process the first time you run it, taking over half a minute on my machine. After the process has finished, we can test that it works correctly by running the binary.

loading

Adding Libraries

While there are plenty of programs we can write with just Scala, most work gets done with libraries. When looking for a good command line arg parser in Scala, I came across Scopt, which as luck would have it, already compiles to Scala Native. You can see what they did to add compatibility to their project in this commit.

Because they have a Scala Native version, I assumed I could the library the same way as a normal Scala library. It's not quite the same, though, as adding

loading

to build.sbt leads this error:

loading

This was my first road bump, and was fixed pretty easily with the help of my colleague Richard. Simply add another percentage sign.

loading

Just as two percentage signs tells sbt to use the right version of the library, three percentage signs tells sbt to use the right target environment, either Scala Native or Scala.js. In otherwords, the first version is equivalent to

loading

whereas the second is equivalent to

loading

Breaking Halfway Through

However, if the Scala Native compatible version didn't exist, we'd need to download and compile our dependencies as well.

In this case, while going through the steps for this blog again, I ran into a new issue. While Scopt has a version for Scala Native 0.2, there wasn't a version published for Scala Native 0.3. Luckily, it's not too hard for us to download and compile locally.

First, we clone the GitHub project.

loading

Then, we modify site.sbt, updating the version of the sbt-scala-native plugin to our desired version.

loading

Finally, run sbt publishLocal, which should push the artifact to our local Ivy cache. We can now continue on!

The Interface

Choosing random things in Scala is pretty easily. We can make an object Picker, with a single function choose, as follows

loading

With this helper object done, we can implement the Main object, extending App, to complete our CLI.

loading

And it works!

loading

Fun with Random

But, if we keep testing this, we'll notice a distinct pattern.

loading

Something seemed to be off. Indeed, when I ran Random.nextInt(10) on a fresh Scala Native process, it always returned 4.

XKCD RandomInt = 4XKCD RandomInt = 4

Digging in to the root cause, I was about at the point where I was ready to make an issue on the project, but less than an hour after I started the project, the same issue was reported.

Scala Native is definitely the bleeding edge, be prepared to run into cases where random isn't random at all.

While work is done on a reasonable default seed, we can fix our program's randomness by adding a Random instance, seeded with the current time. Picker should now look like this:

loading

Useful Options

Right now our Randomizer is pretty simple but effective. I've used it a few times myself. But I noticed a few patterns, e.g. flipping a coin with ./randomizer-out Heads Tails. Additionally, I found myself wanting to select random numbers, select more from a list, and more.

Luckily, with Scopt, all that is pretty simple to add. The first thing to add is a mode, which we can split on for commands. By default, this should be choose.

loading

Now we can use commands to branch to different functionality.

loading

Now, when parsing, we need to pattern match against all the possible options.

loading

So all that's left is to implement the Picker.roll function, like so

loading

After running sbt nativeLink again, we're at the point where we have a fairly flexible and useful cli randomizer!

loading

Thoughts

On one hand, Scala Native presented a lot of challenges. There were some unexpected hiccups, and bugs in standard libraries.

On the other, it was a delight to have some libraries just work. I thought integrating Scopt would be a much more complicated process, but it was relatively painless after the %%% trick.

I don't plan on writing much Scala Native code in production on the current version, but the quick pace of development has me hopeful that it will be a compelling option in the near future.

All code is available on GitHub