Babashka Clojure nREPL as a system interface
yogthos.net
yogthos.net
But am I blind, or is there no documentation of what osquery modules/tables are available?
https://osquery.readthedocs.io/en/stable/
EDIT: rewritten the comment so it's less of a rant
Ok, I have to admit I am "allergic" to this take. Why do you mind (or even care about) the JVM? Do you care about the innards of Python's VM, or any other language's runtime?
Leaving aside which language you use on top, the JVM is likely the single most-developed and most-improved VM ever created. Uncounted bazillions of man-hours of development went into it. Corner cases were found, performance was tweaked, just-in-time optimization was implemented, novel garbate collectors were developed. It is an impressive piece of machinery, which is why I find it slightly annoying when it gets a dismissive treatment "just because".
Of course I might be wrong and you might just be that person that spent the last three weeks chasing an obscure JVM issue that happens in 0.01% percent of production servers, in which case you do have legitimate reasons for being "allergic" to the JVM.
But that was 20-30 years ago, literally. A few things happened: SSDs, and Java startup time.
A warm-start Java "Hello World" is 200-300ms. A babashka (bb) "Hello World" is 30-40ms, which is faster than Python3 "Hello World" (40-50ms).
But I would say that e.g. IntelliJ looks quite good and that is a basic theme available without much tweaking, plus it can be very snappy (when jetbrains doesn’t ship it with a decade old JVM with shitty default settings. It is fixed in recent versions).
I am well aware it is a very productive ecosystem, though I feel I am entitled to like or dislike any technologies I wish to, however irrational it might be. It was a figure of speech mostly intended as tongue-in-cheeck, not a statement of fact that I need to defend.
The first time I tried Clojure, I tried with Leiningen, but it dumped literally hundreds of lines of Java stack traces when something went wrong. So I stopped.
As a comparative example, how do I install F#? Well, you install the .NET SDK, and then you get F# automatically. When something goes wrong, I get good error messages that are tailored to F#.
I think it's a little misleading to ask "why do you care about the JVM?" because the answer is that "Clojure makes you care about the JVM".
Of course, maybe it's me. But I have no trouble using F#, Racket, Elixir, Erlang, or several other languages. Yet, I have struggled every time to get going with Clojure.
It's really the experience in totality. There are choices, but for someone new to the Java ecosystem, it's confusing why and why should I care. Because in the end, I don't care. I just want to use Clojure, but there's decisions to be made at every step of getting going that aren't clear to someone new to the ecosystem.
This same thing happened with .NET as well. At one point in time, there was .NET Framework, .NET Core, and Mono and a question of how to get F# in all that. That was just as, if not more so, confusing. However, it's since been cleared up: install .NET 6 or higher.
I know it’s mostly about a sentiment, but if you or anyone is curious — there is basically none, OracleJDK is a fork of OpenJDK (which is the reference implementation) with basically negligible changes. License wise OpenJDK uses the same one as Linux so you can use it as you seem fit, while the current LTS OracleJDK (17) is free to use until the next LTS release + 1 year, but unless you need corporate support, you really have no need for that.
Pretty much whatever you do, you will get OpenJDK (all these options are forks with minuscule changes for the most part). If you do happen to need multiple JDKs I can recommend sdkman. But I think the java platform is pretty suckless, especially that it has probably the very best backwards compatibility.
What I saw was that the repl leads to a runtime state that is invisible and therefore hard to predict. For example, you define a function in the repl, or assign a value to a variable. You’re just supposed to keep the existence of those declarations in your head? I must be missing something.
I'd also add to what Borkdude said: it also helps that Clojure strongly encourages you to keep runtime state localized (not spread all over the places).
This allows you to hot reload changes to the source code, or fire off events directly from your editor. In Clojure one often has commented out code that one can test functionality, or make state changes directly.
Very little work is done at the REPL command line, instead it is fired off to the nREPL (network accessable REPL).
If you're using a LSP server, which btw uses the "clj-kondo" which borkdude mentioned, it's totally independent from the REPL and will tell you immediately if your source file is missing a function definition or a variable.
Now it could be argued that you could have a wrong version of a function declared in the source file (and hence LSP / clj-kondo would still be happy) while the correct version got only defined at the REPL but...
That's not how I develop: typically I test stuff at the REPL but only define my functions in the source file. And from the source file, I "eval" the function and its hence available directly in the REPL.
Now I don't know about others but in my case I regularly write anonymous functions at the REPL, which I test directly on some data at the REPL, and then I copy/paste the anonymous function to the source file and give it a name there.
REPL> ((fn [x] (* x 2) 21)
42
OK, good, it works now, I cut/past (fn[x] (* x 2)) to the source file and change the anonymous fn to a defn.But I'll also sometimes just directly write the function in the source code file.
In addition to that, unit tests can help too.
So it's REPL + unit tests + LSP (which uses clj-kondo) and all three are totally independent.
And yet although they're independent, when LSP tells me something like: "Function x is called with only argument but expects two", I can fix it, eval the correct function, and the REPL is immediately aware of the correct version.
I've always got both the REPL and the Clojure LSP server running, independently, simultaneously.
> or assign a value to a variable.
Clojure really tries to put the emphasis on functional programming so you don't typically have lots of mutable variables to juggle with. I'd say I have more of a "global application state" at the REPL than different variables.
> I must be missing something.
Maybe you're simply not aware that you can code in the source file, eval there, and the eval'ed functions are directly available from the REPL?
I'm not sure my explanation are very clear but I hope it helps.
Python:
import subprocess
import json
def osquery(query):
return json.loads(subprocess.check_output(["osqueryi", "--json", query]))
#>>> osquery("select * from routes where destination = '::1'")
#>>> [{'destination': '::1', 'flags': '2098181', 'gateway': '::1', 'hopcount': '0', 'interface': 'lo0', 'metric': '0', 'mtu': '16384', 'netmask': '128', 'source': '', 'type': 'local'}]
Node: const { execFile } = require("child_process");
async function osquery(query) {
return new Promise((resolve, reject) => {
execFile("osqueryi", ["--json", query], (error, out, _err) => {
if (error) {
reject(error);
return;
}
resolve(JSON.parse(out));
});
});
}
//> await osquery("select * from routes where destination = '::1'");
//> [
//> {
//> destination: '::1',
//> flags: '2098181',
//> gateway: '::1',
//> hopcount: '0',
//> interface: 'lo0',
//> metric: '0',
//> mtu: '16384',
//> netmask: '128',
//> source: '',
//> type: 'local'
//> }
//> ]
Ruby: require "open3"
require "json"
def osquery(query)
out, err, exit_status = Open3.capture3("osqueryi", "--json", query)
if exit_status != 0
raise err
end
return JSON.parse(out, symbolize_names: true)
end
#irb(main):1:0> osquery("select * from routes where destination = '::1'")
#=>
#[{:destination=>"::1",
# :flags=>"2098181",
# :gateway=>"::1",
# :hopcount=>"0",
# :interface=>"lo0",
# :metric=>"0",
# :mtu=>"16384",
# :netmask=>"128",
# :source=>"",
# :type=>"local"}]