How do you think of clojure(-like) interpreters for scripting?

  • joker // a clojure interpreter
    • I’m using this one. The API around command execution is reasonably fine.
    • It takes 40 milliseconds to start. It’s not slow, but it can become slow with more code.
  • babashka // a clojure interpreter
    • It takes 10 milliseconds to start.
    • But, it requires clojure and graalvm to compile. So, it might be difficult to make a linux package for this.
    • It supports parallelism.
  • janet // a clojure-like language and interpreter
    • This one is similar to clojure.
    • It is a small self-contained C program that is easy to compile and package for linux distributions.
      • It is suitable for compilation on single board computers.
    • It starts extremely fast. I think it starts up faster than babashka.
    • But, I like janet as a language less than I like clojure
    • Its standard library seems a bit less polished around command execution than that of joker.
    • It supports parallelism.
2 Likes

I’d be curious to hear what your definition of “scripting” is?

My goto has been to use CLJS (via shadow-cljs :node-script) and just running the output using node. It is widely supported and can even run directly without “installing” (eg. npx create-cljs-project foo-bar). Of course the requirement is that node is installed which seems to cause anxiety for some people (just like java does for others).

I’ve never used any other of the interpreters you mention, I’d rather use Clojure directly for anything that goes beyond basic “scripting”. Whatever that means though … I’d say anything that runs longer than a minute is no longer a “script” and I’d be using Clojure.

2 Likes

People have been making linux packages for clj-kondo (arch, nixos) as well, so I’m not sure what the objection here is?
I’m building babashka on CircleCI for linux and macOS. The process isn’t that involved.

If you’re looking for a JVM-like Clojure for scripting (fast startup) that also supports writing your own libraries and interop with a wide selection of classes, then you may like babashka. It’s pretty low risk too. If you decide that babashka isn’t for you, your code will still run with the normal JVM Clojure.

I’d say anything that runs longer than a minute is no longer a “script” and I’d be using Clojure.

Agree with @thheller there, although I would say my threshold is only a few seconds.

1 Like

I’ve been spending a lot of time with babashka and I really prefer it over joker. The fact that I can make libraries that work for both babashka and jvm clojure is a huge win.

Unless you’re contributing to babashka itself you don’t actually need to compile, there are downloadable binaries on github.

Oh, and it helps that @borkdude is always creating great stuff for it and is super responsive and willing to accept PRs as needed. :heart:

2 Likes

You might also like to read: https://stuartsierra.com/2019/12/21/clojure-start-time-in-2019

1 Like

Since it is missing from the list of benchmarks here is one compiled CLJS :node-script running via node.

$ time node out/script.js 
Hello, world!

real    0m0.092s
user    0m0.031s
sys     0m0.063s

I guess compiling the script first no longer qualifies it as a script? Anyways, if you actually run the compile separately the resulting output runs quite fast (without the overhead of bootstrapping self-hosted first).

For completeness sake, this was done using npx create-cljs-project script-demo and then creating src/main/example/script.cljs

(ns example.script)

(defn main [& args]
  (println "Hello, world!"))

And adjusting shadow-cljs.edn to add the build config

;; shadow-cljs configuration
{:source-paths
 ["src/dev"
  "src/main"
  "src/test"]

 :dependencies
 []

 :builds
 {:script
  {:target :node-script
   :output-to "out/script.js"
   :main example.script/main}}}

And finally npx shadow-cljs release script.

3 Likes

Janet mention! That’s pretty cool.

I liked janet less as a language at first, but coming from a c background (decades ago), it was nice to work with the c stack again, vs the java stack, which I never really got

I think janet is a great language, scripting or not

2 Likes

I like babashka more than joker and janet. But, integrating it with Gentoo involves more work.

Using a distributed uberjar is ok, but I remembered that on gentoo, you want to apply patches to source codes while building packages. So, if I wanted to apply patches to babashka, I am going to need something that builds an uberjar without needing access to network. Gentoo wants to fetch its own dependencies and block access to network during build. In a sense, I need a Gentoo facility that mimicks leiningen or need to make such a facility myself. Gentoo currently supports plain java build and ant build.

Currently, building GraalVM on gentoo requires JVMCI-enabled JDK8 or JDK11. JVMCI-enabled JDK8 is not packaged on Gentoo, and JDK11 is disabled on it for now. Building GraalVM also requires mx which is not a python package, yet.

In order to fully build everything on Gentoo, I would need

  • a Gentoo facility that builds a clojure program without network access
    • Perhaps, I should learn to create an uber jar with build.xml
  • mx python package
  • JVMCI-enabled JDK8 or JDK11

Following Gentoo’s tradition is difficult. Packaging a Go program or a C program is relatively straightforward on it. I packaged dev-lang/babashka-bin which is just a binary.

On my computer, a compiled cljs Hello World with advanced compilation takes 400ms to run. Where as the same for joker is under 100ms.

But I have an old 2013 ultrabook :stuck_out_tongue:

But I did a compile directly with the ClojureScript compiler, which includes the goog module machinery which I suspects adds quite a bit of start time, since a hand written hello world in JS takes way less time to start.

What is NPX and the shadow-cljs node-script target? Do they do something that would alleviate some of that?

Otherwise, I haven’t tried babashka yet, but it being 1:1 with Clojure I do think means it eventually has the most potential. Also as GraalVM matures.

That said, I do currently use Joker and love it. In a weird way, I like that it’s not full blown Clojure, it’s very much focused on scripting and scripting only and I find that means everything about it makes scripts easy.

1 Like

If you compiled correctly with :advanced optimizations the output should be one file with no goog.* reference left? 400ms seems like a lot but dunno about 2013 ultrabook hardware.

npx is just used for running npm scripts without a “global” install, it comes with npm. The shadow-cljs command otherwise needs to be installed via npm install -g shadow-cljs first. With npx it’ll pick the install from the local project instead. It doesn’t affect the build compilation in any way though.

Unless you want to stick with javascript infrastructure, I recommend moving over to joker, babashka, or janet.

I want to give babashka a proper platform on Gentoo Linux. I am thinking about writing a minimal JVM build system for Gentoo package manager. The build system’s design can be extended to support any JVM language that produces class files.

The module for managing transitive jar dependencies and creating jar files out of class files.

The basic idea is to put all class files in build folder along with a minimal manifest. If I extract transitive jar dependencies in the build folder and compile babashka into the build directory and make a jar out of the build folder along with a minimal manifest, I would get a minimal uberjar. Package maintainers would be able to create a jar, an uberjar, or both.

Transitive depedencies are recursively read from /usr/lib/jvm-jar/program-category/program-name:program-version-slot/dependencies

The module for compiling each JVM language into class files in the build folder

For now, modules for java and clojure are sufficient. I can later add more modules for building other JVM languages.

An optional module for compiling a GraalVM binary out of an uberjar.

With this, I can get GraalVM binaries out of many JVM programs.

How to bootstrap babashka

  • Create the module for building jars and uberjars with transitive jar dependencies and the module for compiling java source files.
  • First, make JVM packages for clojure’s two java dependencies.
  • Make the JVM package for clojure
  • Create the module for compiling clojure source files
  • Wait for mx python package, and make a gentoo package for it
  • Make a package for GraalVM with mx.
  • Perhaps, make a module for compiling GraalVM binaries from uberjars.
  • Make JVM packages for babashka and its clojure dependencies. Build a graalvm binary for babashka.

The end result

  • Easily obtain GraalVM binaries for various JVM programs.
  • Ability to apply patches to each and every JVM module.
  • GraalVM binaries that work flawlessly on the target system without problems.

Janet is easy to deploy. I already made gentoo linux packages for janet and janet’s modules. jpm integrates well with linux distributions.