Yes, JVM startup combined with the overhead of loading and compiling clojure.core can take a few seconds. Part of the startup time with clj is building the classpath but that is cached so I suspect you’d find subsequent invocations of the script were faster (probably twice as fast).
If startup time is your biggest concern then you either need to look at something like lumo (which means ClojureScript and, I think, Node.js behind the scenes) if you’ve also ruled out GraalVM…
…but I think, realistically, you’re not going to find anything that satisfies you in the Clojure(Script) world for fast-starting scripts with everything else you also want.
I can live with a few seconds of startup time for certain use cases. But, I want to be able to edit dependencies with ease. Is boot script any better? I tried to write a boot script, but I couldn’t get myself to write one that compiles.
Perhaps, I should just write dependencies in shebang lines for now if I want to write scripts in clojure.
Do you know any fine programming language for command line scripting other than python or shells?
Boot doesn’t cache the classpath after construction – which clj does. Also, Boot creates a “shadow fileset” abstraction which also adds some overhead. Of Boot, Leiningen, and clj, the fastest repeated invocation is going to be clj.
As for alternative languages, I’m not sure how many of them I would consider “fine”. Things I’ve written scripts in before Clojure: lots of shell (which isn’t really so bad), some Python, Ruby, Perl… and a few obscure things that no one has heard of these days…
These days, my first choice would be shell for the simple stuff and Clojure via clj for anything more complicated.
I am playing a bit with Planck in these holidays, and I see that repeated invocation of a script, after initial download of dependencies and compilation, is around 500 to 700ms per run. That is 100x what GraalVM takes, but very acceptable in most cases.
For the right type of command line app, Ferret might be the right tool, building tiny, native executable that have no VM overhead.
Be warned: Though it hass access to the full clojure ecosystem during compilation/macro expansion phase, the restricted environment at runtime requires a change in thinking - for instances macros that use str are fine, but functions that use str aren’t!
I’ve been experimenting with using clojure for shebang scripting myself. I want something that is totally self-contained. That is, it doesn’t need a separate deps.edn file.
Here’s what I’ve come up with. It lets you have multi-line deps maps contained in the file. You can execute any Clojure code you want.
The biggest downside is that it is quite a hack. It uses the fact that #_ in Clojure creates a commented (ignored) expression, while # starts a comment in bash. However, for a bash script, it’s pretty good. I’ve got this one on my path, set to executable, and it runs great from the command line.
It now includes arguments, a place for other options, and it doesn’t depend on bash. I’ve included it below, but you can also find the most recent version in this gist.
#!/bin/sh
#_(
#_DEPS is same format as deps.edn. Multiline is okay.
DEPS='
{:deps {clj-time {:mvn/version "0.14.2"}}}
'
#_You can put other options here
OPTS='
-J-Xms256m -J-Xmx256m -J-client
'
exec clojure $OPTS -Sdeps "$DEPS" "$0" "$@"
)
(println "Hello!")
(require '[clj-time.core :as t])
(prn (str (t/now)))
(prn *command-line-args*)
(println (.. (Runtime/getRuntime)
totalMemory))
I later decided to port it to Haskell for fun but then it turned into more than one file. I’m still running that on my server: https://github.com/borkdude/balcony-hs.
Here’s an example of a standalone boot script that you can call with e.g. ./foobar -t:
If you already have boot installed and you want a single file standalone script, I’d say boot works well without crazy shell hacks. The startup time of clj might be a little better due to classpath caching. Lastly, Haskell probably has much better start times for scripts .