Hacker News new | past | comments | ask | show | jobs | submit login
Babashka Clojure nREPL as a system interface (yogthos.net)
115 points by Borkdude on Nov 27, 2022 | hide | past | favorite | 23 comments



Babashka looks great (and perhaps the one thing that will make me try Clojure. I am allergic to the JVM), and I didn't know of osquery.

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


> I am allergic to the JVM

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.


I can speak to this a little, as I have the same aversion to the JVM. Despite wanting to get into Clojure multiple times, I always get hung up on getting going with it. How do I install Java? Is this the "right" Java? How do I now install Clojure? Which project management thing do I use? There's this Leiningen thing but also this new Clojure CLI. Then it comes to IDEs, and they all work rather differently.

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.


<your-pkg-manager> install openjdk

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.


I don't mean to deny your experience or sound rude, but it genuinely baffles me that you would have difficulty installing Java. I do not understand how it is different than installing literally any other programming runtime. How is installing the .NET SDK any easier than installing Java?


That's not exactly what I said though. For Java in particular, is it Oracle JDK or OpenJDK, what's the difference, and why should I care? The latter is the same sentiment of the person I responded to. I don't care, but I'm presented with the different options depending upon which Clojure install guide I am following.

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.


> For Java in particular, is it Oracle JDK or OpenJDK, what's the difference, and why should I care?

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.


Thank you for that information! It is helpful.



I have an irrational fear of it because of the days in the late 90s/early 2000s when Windows desktop software would sometimes require booting a Java VM which guaranteed two things: the UI would be weird / not native, and the boot time would be super slow. I'm still scarred.


That's fair. I remember those damn Java applets that froze the entire computer for a minute while they started.

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).


I'd still say that even today Java desktop apps are much slower, laggier and generally still look worse than their native counterparts. It's just that, yes, hardware is so much better which sort of hides most of the Java problems.


Uglier is relative, but I will give you that neither unthemed Swing or JavaFX are too fine looking.

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 did not expect anyone to be offended because I have never been a fan of the Java world. I have no real reasons, never had a need to use the language, I always saw it overly verbose and associated with boring corporate culture and didn't enjoy my time configuring Tomcat and other Java projects 15 years ago.

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.


I mean, you said you were allergic to the JVM, not to Java syntax. Those are clearly different things, and especially nonsensical in this context as Clojure is not Java (indeed Clojure tends to be one of more terse languages in practice).


The list of tables is available here - https://www.osquery.io/schema/5.5.1/


I hear near universal praise for repl development but in my very limited experience with it, found it nearly impossible. Maybe someone can clarify?

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.


In Clojure, you don't typically type code into the REPL, but write code in a file and then evaluate that code in a REPL connected to your editor. We don't just type into a console window, then reboot for some OS updates and forget about our previous efforts :). Combining this with a static analysis tool / linter like clj-kondo will also help since it will remind you that you removed certain functions, while they may still be there in the REPL.


> What I saw was that the repl leads to a runtime state that is invisible and therefore hard to predict

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).


In the case of Clojure and many other Lisps one makes changes to your source code, and evaluate each one of those changes automatically, or through some key command (ie highlighting the s-exp and evaluating it directly).

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).


> 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?

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.


One nice thing about this setup is that it's not specific to Clojure/Lisp; any scripting language with a REPL will do this well:

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"}]


That said, one nice advantage with a Lisp editor is that you can keep code in a file, and send it for the evaluation in the REPL instead of actually having to use the REPL as your editor.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: