Simple Command Line Tools with Scala Native
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
loadingBy 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.
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
loadingto build.sbt
leads this error:
This was my first road bump, and was fixed pretty easily with the help of my colleague Richard. Simply add another percentage sign.
loadingJust 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
loadingwhereas the second is equivalent to
loadingBreaking 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.
loadingThen, we modify site.sbt, updating the version of the sbt-scala-native plugin to our desired version.
loadingFinally, 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
With this helper object done, we can implement the Main object, extending App, to complete our CLI.
loadingAnd it works!
loadingFun with Random
But, if we keep testing this, we'll notice a distinct pattern.
loadingSomething seemed to be off. Indeed, when I ran Random.nextInt(10)
on a fresh Scala Native process, it always returned 4.
XKCD 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:
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.
loadingNow we can use commands to branch to different functionality.
loadingNow, when parsing, we need to pattern match against all the possible options.
loadingSo all that's left is to implement the Picker.roll function, like so
loadingAfter running sbt nativeLink
again, we're at the point where we have a fairly flexible and useful cli randomizer!
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