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.
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.
To start our Scala Native project, after making sure we have our environment setup, we can generate a new project using giter8 vialoading
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.
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 addingloading
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.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 toloading
whereas the second is equivalent toloading
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
sbt publishLocal, which should push the artifact to our local Ivy cache. We can now continue on!
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.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 = 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:
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 soloading
sbt nativeLink again, we're at the point where we have a fairly flexible and useful cli randomizer!
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
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