If you read enough dev blogs, and have a touch of imposter syndrome,
it starts to look like our field is full of Uber-coders who can constantly
switch gears between, say, writing machine learning code in Python,
These people probably exist. I am not one of them. I've coded professionally
in many languages, everything from C, to PL/1, to Java, to Lua. But for day-to-day work, I specialize. I like Clojure. And I'm terrible at switching gears and languages, unlike my mythical Uber-coder.
Clojure is intensely good at what it does: REPL oriented development,
functional thinking, data transformation, and leveraging existing Java libraries.
But you don't write shell scripts in Clojure, even if you are as hopeless at Bash scripting as me.
Not that you don't want to, it's just that startup time is an obstacle: all the JVM startup time, plus all the Clojure startup time, plus all the time to load whatever libraries you need.
It adds up.
If you are working on an application, you may only fire up your REPL once in an entire day, so this startup time is inconsequential. But if you are writing a script ... either a throwaway, or something to help you automate your environment, you don't want to be twiddling your thumbs waiting for that script to load (even a one second wait can feel like forever).
That's why Joker is such a revelation; Joker is an interpreted dialect of Clojure, written in Google's Go, intended just for scripting: we use it internally on my team for all of our automation.
Nearly everything that makes Clojure amazing exists in Joker, including immutable data types, macros, and dynamic typing, and the vast majority of Clojure's core library.
Joker starts up very quickly:
> time joker -e '(println "There and back again")' There and back again real 0m0.050s user 0m0.038s sys 0m0.017s
That's virtually instantaneous. By comparison, Clojure has a noticable start up time:
> time clj -e '(println "A price must be paid")' A price must be paid real 0m1.011s user 0m1.695s sys 0m0.124s
Better yet, Joker is batteries included: it has a selection of built-in libraries, that cover all of your typical tasks, including sending HTTP requests, JSON and YAML encoding, basic cryptography ... all the things you need to, say, work with Amazon AWS.
One feature near and dear to me is
joker.tools.cli, a port of Clojure's
clojure.tools.cli library. This is first class support for GNU-style command line options.
Let's say you got yourself a sweet job working for the Department of Defense, and wanted to create a script to control a few things at NORAD:
You can name this file
norad, and make it executable (
chmod u+x norad).
Because we made this a first class script, with properly parsed options, we can ask
norad what it does:
> norad --help norad OPTIONS -d, --defcon VALUE Set DEFCON level -v, --version Show version information -h, --help Show this summary
That's a relief; a sloppy script that expected the user to remember its non-standard options might have changed the DEFCON to null, or launched some missiles or something. Real options can be a safety net.
And even though this script is non-trivial, it's still hella fast:
> time norad --defcon 3 Setting DEFCON to 3 real 0m0.086s user 0m0.088s sys 0m0.020s
And as someone who crusades for good feedback from my tools and libraries, it's great to see this:
> norad -d 0 norad OPTIONS -d, --defcon VALUE Set DEFCON level -v, --version Show version information -h, --help Show this summary Errors: Failed to validate "-d 0": level must be 1 to 5
... even in a throwaway script. More safety net.
That first line:
That's standard Posix for "find the
joker command and run the rest of the file through it". Any additional arguments to the command will be provided in
*command-line-args*, a sequence of strings.
Because this is a script, and not a program, there's no
main; there's just Clojure forms, evaluated one after another.
ns form is convenient for bringing in the other built-in libraries that are needed. We gave this namespace the name
script, but that's pretty arbitrary.
The next few forms define constants and utility functions needed to parse command line options and perform actions based on them. A real implementation of
set-defcon might, for example, send an HTTP request to update the big display in the war room.
The last form, the
let block, is where the real work starts; this is where those command line arguments are parsed into options, and then actions are dispatched. This is all very familiar to any Clojure programmer.
But that's it. No
main. No classes. No compilation. No CLASSPATH. No overhead. Just your code, in (very nearly) The One True Language, but running instantly.