Fork me on GitHub
#clojure
<
2020-10-27
>
Cj Pangilinan01:10:06

Good day! Does it makes sense if I use Clojure from Java? The reason is I am one of the team members in a project that uses Spring Boot and I am thinking of including the clojure jar as one of the maven dependencies. Then I will code some of the business logic in Clojure and call it from Java/Spring Boot while the other team members code in Java. I don't know yet how to call Clojure code from Java. Is it just using Reader, IFn, Clojure.var, etc? What are the recommended ways to mix the use of these 2 languages in a project if the team is composed of Java developers and Clojure developers?

noisesmith02:10:17

@cjpangilinan it's quite easy https://clojure.org/reference/java_interop#_calling_clojure_from_java - clojure automatically initializes when you use it

👍 3
noisesmith02:10:40

As regards to how to organize things, I'd say it's similar to how any two subprograms are combined, though you should put more thought into it up front when crossing languages. Use require / IFn to get at and use clojure stuff from java, make sure the java side doesn't force you to do concrete inheritence or annotations which are clumsier on the clj side

emccue03:10:46

@cjpangilinan You very much can, but if I had to guess it probably won't be the best way to introduce clojure to your team

emccue03:10:21

Clojure really isn't "full duplex interoperability" like kotlin, and that tends to show

👍 3
vncz03:10:23

Is there a better way to write this?

vncz03:10:59

I find myself often in this situation where based on the result of an if I should either return X or a modified version of X

noisesmith03:10:51

for starters (if-not t ...) unless you expect false

noisesmith03:10:42

how about #(cond-> % t (assoc :response {:status ... :headers ...}))

seancorfield03:10:47

Yeah, I tend to reach for cond-> straight away for conditional modification of something.

seancorfield03:10:40

We have a condp-> macro at work that is a variant that threads through the predicate, so I'd probably write

(condp-> t
  some? (assoc :response ...))

Mikko Harju05:10:03

I upgraded to OS X Catalina and in the same time upgraded my brew packages, when I invoke clj -A:nREPL -m nrepl.cmdline -i I get

WARNING: When invoking clojure.main, use -M
– I could not find any references when I searched around a bit. What is this related to? Thanks!

orestis05:10:16

@mikko can’t find the change log, it’s a new release of the CLI tools: https://clojure.org/reference/deps_and_cli

Mikko Harju06:10:17

Oh, so it's clj -M:nREPL -m nrepl.cmdline -i now!

Mikko Harju06:10:26

Using that, the warning went away.

Hankstenberg08:10:24

Hi guys, I'm trying to use a Clojure function that returns an Array of Strings in Java. In the clj file I have: :methods [#^{:static true} [myfunc [String] "[Ljava.lang.String;"]]) (defn -myfunc [arg] .... (into-array (map str (keys some-sequence)))) When I try to invoke it from inside a Java class I'm getting: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String; Does anybody have an idea why?

thom08:10:16

You want (into-array String ...) to give you a string array instead of an object array.

Hankstenberg08:10:41

Wow, thanks! It even says that in the documentation of into-array. Wouldn't have thought that there's any special coercion functionality in that function. Much obliged!

Dosbol09:10:04

Hi guys, is there any open-source ERP/CRM solutions in clojure(or wrapper) ? Thanks

vncz12:10:30

@noisesmith @seancorfield Thanks for the pointer. I was also thinking about cond-> but I guess I haven't understood how to use it properly yet since it didn't look like it'd fix my use case

vncz13:10:39

Oh ok I think I understand now

slipset14:10:59

How come (= 3) ;; => true but (=) throws. This bites when (apply = xs) and xs happens to be nil or empty?

dpsutton15:10:43

should (=) return true or false?

andy.fingerhut15:10:00

Unlike + or *, = doesn't really have a natural identity value, mathematically speaking.

slipset15:10:39

But how does then (= nil) make any sense?

dpsutton15:10:52

yeah i agree that the single arity doesn't really make sense

andy.fingerhut15:10:17

Not saying (= nil) makes perfect sense, but it makes more sense than defining a return value for (=) , IMO

dpsutton15:10:36

but i'm not sure i agree that boolean logic doesn't have identities. seems false is the additive identity and true is the multiplicative identity, just like 0 and 1 under + and *

andy.fingerhut15:10:02

I think identity elements for or and and make sense, but less so for =

dpsutton15:10:04

x || false = x for all x and x && true = x for all x

slipset15:10:11

Thats like saying (> (* 2 (/ 1 0)) (/ 1 0)) 🙂

andy.fingerhut15:10:36

There is no value you can add to a list of (= a b c <insert-something-here) that preserves the return value for all values of a, b, c

dpsutton15:10:38

oh you're right andy. i'm conflating things

andy.fingerhut15:10:49

You can do that for or and and (modulo short-circuiting behavior)

dpsutton15:10:57

good point. thanks

slipset15:10:58

That last one was to @U0CMVHBL2 claiming that (= nil) makes more sense than (=) 🙂

andy.fingerhut15:10:29

I don't understand the logic of your statement.

slipset15:10:29

To me, saying that (= nil) makes more sense than (=) makes as much sense as saying that (* 2 (/ 1 0)) is greater than (/ 1 0)

andy.fingerhut15:10:56

I said "Not saying that (= nil) makes perfect sense". If you want me to back down from that, well, it isn't exactly a firm conviction in the first place, as I tried to convey (apparently unsuccessfully).

slipset15:10:39

It was the , but ... that followed I was commenting on.

slipset15:10:11

But I'm far away from being certain on anything in these matters. You actually wrote the guide on equality in Clojure.

andy.fingerhut15:10:20

I think it makes 0 sense to define a return value for (=) . I think it makes 1% sense to define a value for (= x) as true.

quoll15:10:40

While I agree with this, the multi-arity = returns true when: ∀x∈S, ∀y∈S : x = y or ∀x∈S, ∄y∈S : ¬(x = y) When S is ∅ then both the above statements hold. So I think it’s valid to return true when applying = to the empty set

andy.fingerhut15:10:09

I can't exactly get worked up about the 1-arity = case in any particular direction, and it is less than 1% likely to change in Clojure.

slipset15:10:40

I guess what I get worked up about is that this leads to unexpected runtime errors.

slipset15:10:54

Much like the two-arity reduce can do.

andy.fingerhut15:10:06

Lots of things in Clojure lead to unexpected run time errors, if your expectations do not match the Clojure implementation. This is a fact of life 🙂

andy.fingerhut15:10:03

I am not thereby arguing that no one should ever ask about these things, or attempt to see if they ought to be changed. I've filed more issues than most people about nits like this.

slipset15:10:29

This is also true, but this specific class of errors, applying a function which doesn't have a zero-arity variant to a list that sometimes empty, is not a class of errors I'm often considering.

andy.fingerhut15:10:46

@U051N6TTC That is almost true. Technically, it is when adjacent pairs all return true for =. There is no checking of all pairs performed.

slipset15:10:50

I guess what I'm saying is that I'm totally fine with (apply < [1 2 \a]) throwing, but I'll always be surprised when (apply < xs) throws because xs is nil

andy.fingerhut15:10:21

You can write your own less-than function that doesn't surprise you.

slipset15:10:50

I could write my own language that doesn't surprise me 🙂

quoll15:10:55

(defn apply= [s] (or (empty? s) (apply = s)))

andy.fingerhut15:10:14

Sure can. Everywhere is walking distance if you have the time 🙂

quoll15:10:04

> That is almost true.  Technically, it is when adjacent pairs all return true for `=`.  There is no checking of all pairs performed. I believe that is an implementation artifact, and not part of the definition

andy.fingerhut15:10:15

The definition you gave is a perfectly logical mathematical definition. It isn't part of the implementation, although the implementation is certainly guided by such ideas at some point.

quoll15:10:21

I have lecture notes from 1989 that show a proof for: > if l=m and m=n then l=n We spent an hour on it, proving it via 2 methods. I only remember the second one. This was the class that convinced me that I was not interested in pure mathematics

andy.fingerhut15:10:22

If someone finds a case where = is not transitive, though, and (= a b c) returns true but (= c a b) returns false because of the implementation, and you point out that definition, what do you expect will happen? My expectation is "fix the non-transitive = implementation, if you can. If you can't, sorry"

quoll15:10:01

My perspective here is that = is implemented in a way so as to express the mathematical concept as closely as possible. The implementation does not cover the case of (=) and the question raised was, “What does a zero-arity = even mean?”

andy.fingerhut15:10:41

Yep, and we talked about 4 mathematical functions that have very natural choices for identity elements, but = isn't one of them.

quoll15:10:10

I proposed an answer to that. It doesn’t actually matter for the implementation, since the implementation is not going to change. But it should guide anyone who is wanting to apply = to an empty sequence

andy.fingerhut15:10:56

It looks like a fine definition to justify someone's choice of what their (my-custom-equals) function returns.

seancorfield16:10:24

That argument would certainly persuade me that (=) should be true if anyone ever implements it...

andy.fingerhut16:10:57

Note that a change to (=) behavior could be considered by some as a breaking change, i.e. they expect it to throw an exception, because it has before. I have no knowledge of whether the Clojure core developers consider that a concern or not, for such changes, but it might be. Sometimes backwards compatibility means things that were error before remain an error in the future.

andy.fingerhut16:10:57

This description of Hyrum's Law is a good short read, if you haven't seen it before: https://www.hyrumslaw.com/

seancorfield17:10:15

Yeah, I have had cases in projects where I've fixed a bug and then had users complain that they depended on the previous (buggy) behavior... and I've usually introduced some sort of flag that allows folks to choose which behavior they get (HoneySQL has several such flags at this point!).

quoll17:10:16

I think in this case that it’s not about (=), since no one would call that. But rather what happens when = is applied to a seq and the seq happens to be empty. Which means that it just needs a function for applying = to a seq, and not changing =. Since it’s a boolean result, the function would be either: (defn apply= [s] (or (empty? s) (apply = s))) or (defn apply= [s] (and (seq s) (apply = s))) (I argue that it should be the former, as already stated).

andy.fingerhut17:10:21

Even if no one would call (=) in that form in their code, it seems like slipset is exactly concerned with the case of calling (apply = xs) where xs is an empty sequence or collection, and doesn't want an exception in that case. He knows he can write his own custom function for this. If he was content with that solution, I don't think we would have had a discussion with so many comments.

quoll17:10:30

I was focused on the question asked at the top of this thread

slipset17:10:11

This is correct. I’m first and foremost concerned with the apply thing. What the semantics of (=) is, is a side effect of the first concern.

seancorfield17:10:07

There are several functions in Clojure that accept one or more arguments but not zero arguments and apply is painful for all of those (of course, now I can't actually remember what other functions I've seen people complaining about). I think we also have the situation that some n-arity functions have different semantics for their 0-arity form, returning a transducer? (again, can't think of an example right now)

slipset18:10:15

The transducer ones I don’t mind, but <,>,=,not= all come to mind as the ones I mind.

abdullahibra15:10:48

Hi everyone, is there something like tree-seq which could keep the structure of the tree?

slipset15:10:47

@dpsutton I'd say (= (=) (= nil)) (if that hadn't thrown an exception 😉

roklenarcic17:10:56

I’ve used depstar to make a JAR of my library. Verbose output explains that it’s been compiled by JDK 14: Build-Jdk: 14.0.1 . I am worried if people with JDK 8 will be able to use my library.

roklenarcic18:10:30

Ah I see that JAR contains only .clj files, but what if any part of it was AOTed? How to select the java version level for those class files?

alexmiller18:10:22

I would recommend using source and target of 8

alexmiller18:10:29

Clojure itself is AOT compiled with Java 8

alexmiller18:10:05

Most likely mixing the two is fine (but will require at least Java 14 to run an application, which may not be what you want).

seancorfield18:10:20

Library JARs should not be AOT compiled in general. Uberjars can be AOT'd but the expectation is that you would run them yourself and you control the JVM environment on which they run @roklenarcic

borkdude18:10:23

one example: I publish an (uber)jar for the VSCode clj-kondo extension which only requires people to have Java (8>=) on their system

borkdude18:10:44

I make sure I compile with 8 (by printing the java version before compilation, there are better ways)

borkdude18:10:21

I use jenv to quickly switch between java versions per project or shell

borkdude18:10:10

I guess I could not AOT this and rely on clojure main to invoke this right?

borkdude18:10:28

if clojure was part of the uberjar

borkdude18:10:38

java -jar clojure.main -m ...

noisesmith18:10:05

surely java -cp some.jar clojure.main -m ...

victorb18:10:22

Is there something like JS' "Shorthand property names" in clojure.core? I'd imagine there is some macro that creates {:a 1 :b 2 :c 3} from (let [a 1, b 2, c 3] (shorthand [a b c]))

victorb18:10:34

(defmacro as-map [& syms]
  (zipmap (map keyword syms) syms))
also does the trick

❤️ 3
noisesmith21:10:26

related

(ins)user=> (defmacro locals [] (into {} (map (juxt keyword identity)) (keys &env)))
#'user/locals
(ins)user=> ((fn [x] (let [y (+ x x)] (locals))) 21)
{:x 21, :y 42}

borkdude22:10:39

@UEJ5FMR6K I have this one in a project I work on:

(defmacro ->map
    "(->map a b) ;;=> {:a a :b b}"
    [& symbols]
    (assert (every? symbol? symbols))
    `(hash-map
      [email protected](interleave (map keyword symbols) symbols)))

emccue01:10:05

I have this macro

emccue01:10:22

(let [x 1 y 3]
  (m x y))

emccue01:10:32

that will evaluate to {:x 1 :y 3}

emccue01:10:39

and also this one

emccue01:10:57

(let [x 1 y 2]
  (m+ x y | :z (+ x y)))

emccue01:10:07

that will evaluate to {:x 1 :y 2 :z 3}

victorb09:10:36

These are all great, thanks folks!

holyjak22:10:07

hello! is there a good way to check whether a function (passed as an argument) has 0 or 1 arity? (I am trying to find a non-breaking way to evolve an API that previously took a 0-arg callback while I want to start sending it an argument, w/o breaking all existing clients)

noisesmith22:10:21

AFAIK the function object itself doesn't expose that information

borkdude22:10:22

What I mostly end up doing when designing an API is MIMO: map in, map out. This has enough flexibility for future extension

borkdude22:10:33

you could also just catch an ArityException and handle the old case in the catch

noisesmith22:10:38

thinking out loud - you could also use dynamic bindings instead of providing the values as args (the way clojure.test encourages for example)

noisesmith22:10:50

but using a hash map in the first place definitely wins

borkdude22:10:18

metadata opts on the fn also works

borkdude22:10:26

bit of a hack maybe, but it works

holyjak22:10:38

Thank you all! What do you mean by metadata opts, @U04V15CAJ ? The metadata added automatically by clojure for regular functions? That could be a good optimization to try but they will be missing for anonymous fns / lambdas so I would still need to catch the ArityExc. @noisesmith Yeah, I thought about dynamic bindings but it feels very non-functional and opaque, passing data like that, I prefer an explicit solution.

borkdude22:10:33

@U0522TWDA Never mind, the metadata solution won't work. I think the ArityException solution might be the least hacky if you want to keep supporting the 0-arity

noisesmith22:10:14

I'd say (try (f x) (catch ArityException _ (f))) is the simplest (and performs better than the reflection option)

👍 3
holyjak23:10:14

thank you!

wilkerlucio22:10:43

hello, is there a way to leverage the structural sharing for transfering data? my use case is that I have a map that goes though a complex series of operations until its done, to help with debug I'm doing snapshots of this map as the program builds it up. while running on the machine I get the memory savings from structural sharing for free, but I also need to transfer that data (all the snapshots) over the wire, and if I do that, than there is no structural sharing to help reduce the data size. so I wonder if there is a solution around for that

jeroenvandijk09:10:29

@wilkerlucio I wrote some compression code in Clojure that keeps a dictionary of items that it comes across. So if anything occurs twice you get compression, this happens recursively. It also allows the use of an external dictionary (/ schema) for common items mapped onto integers for extra compression. The resulting compressed datastructure is serialized via transit. All this comes at the cost of some cpu (I think, didn’t measure), but it is much smaller than any other option (gzip, plain transit etc). If you want more details please let me know. I’m also thinking about new options like data templates

noisesmith22:10:01

@wilkerlucio transit does some structural sharing, there might be something else that does it more aggressively

alexmiller22:10:32

I hate to suggest it but java serialization is probably the best example of that

alexmiller22:10:50

Objects in the stream are only transferred once

alexmiller22:10:41

I don’t recall if Clojure collections are customized though in such a way that would foil structural sharing, can’t look atm

noisesmith23:10:50

@wilkerlucio @alexmiller a simple proof of concept, the baseline usage for clojure data structures is a bit large

(defn to-bytes
  [& data]
  (with-open [b (ByteArrayOutputStream.)
              stream (ObjectOutputStream. b)]
    (doseq [d data]
      (.writeObject stream d))
    (.toByteArray b)))

(defn from-bytes
  [ba]
  (with-open [bs (ByteArrayInputStream. ba)
              stream (ObjectInputStream. bs)]
    (into []
          (take-while (complement #{::done}))
          (repeatedly
           #(try
             (.readObject stream)
             (catch IOException _ ::done))))))
(ins)user=> (to-bytes {:a 0 :b 1})
#object["[B" 0x4784013e "[[email protected]"]
(ins)user=> (def b *1)
#'user/b
(ins)user=> (count b)
536
(ins)user=> (from-bytes b)
[{:a 0, :b 1}]

phronmophobic23:10:24

is there any way to show that this method takes advantage of structural sharing of clojure's data structures?

noisesmith23:10:58

working on that now - making a relatively large structure and putting it in multiple spots in a parent coll

parrot 3
noisesmith23:10:30

definitely structural sharing:

(ins)user=> (def big-load (doall (range 1000)))
#'user/big-load
(cmd)user=> (count (to-bytes big-load))
67273
(ins)user=> (count (to-bytes (doall (repeat 100 big-load))))
69351

phronmophobic23:10:27

I've long been interested in the idea of being able to have a poor man's database just by doing (write-to-disk-on-change my-atm) and it would simply write the atom to a file and as long as you're just conjing onto some clojure data, it might theoretically be able to sync with the on-disk file incrementally. still not sure it's a good idea

noisesmith23:10:58

this is what databases are for right?

phronmophobic23:10:08

yea, but it may be ignorance of the options, but if I have some nested maps, is there a database that can sync incrementally the whole nested data structure?

phronmophobic23:10:29

for example, a map that holds all the UI data for a re-frame app

alexmiller23:10:22

range and repeat may be too specialized to really test this theory

noisesmith00:10:05

I see similar evidence of structural sharing when I switch it to vector

(ins)user=> (def big-load (into [] (range 1000)))
#'user/big-load
(cmd)user=> (count (to-bytes big-load))
14934
(ins)user=> (count (to-bytes (into [] (repeat 100 big-load))))
15555

alexmiller00:10:41

from glancing at the code, that is what I'd expect - it's just relying on normal Java serialization of object graphs, without any specialized read/write mods

wilkerlucio03:10:02

that's pretty cool 🙂

jmckitrick23:10:42

When it comes to the ‘heavies’ in the Clojure community (years in the field, active in core development, etc), what’s the approximate distribution of CIDER, Cursive, etc? Any idea?

noisesmith23:10:05

there's a yearly survey that asks about tooling / editors etc. but I don't think it's broken down to cross reference experience and tooling together

seancorfield23:10:09

I can't remember if the survey writeup also makes the raw data accessible, but that would be the way to figure it out I guess.

cfleming23:10:33

My impression is that it’s probably roughly 50/50 Cursive/Emacs. But on the Emacs side my impression is also that a lot of the more experienced people tend to use simpler Emacs setups, i.e. probably clojure-mode but not CIDER. I have no data to back that up though.

seancorfield23:10:24

I get the impression that the "heavies" use the tooling in roughly the same percentages as the overall community, with a slightly heavier lean toward Emacs. Yeah, what Colin said...

seancorfield23:10:41

Rich and Stu (and several other "long-timers") use Emacs in a fairly minimal configuration: inferior-mode, plain REPL, perhaps via a Socket REPL in some cases?

seancorfield23:10:31

If I ever went back to Emacs, I'd probably go down that path instead of CIDER. Ideally, I'd want a way to connect Emacs to a Socket REPL. I'm probably a bit of an outlier as I used Emacs on and off since way back in its 17.x version days, and came back to it once I got into Clojure a decade ago after trying a few other editors. But then I switched from Emacs to Atom back in 2016 I think? And I use Atom/Chlorine, a Socket REPL, and a deliberately minimal set of key bindings and commands now. I was using Cognitect's REBL but I've switched to Reveal now for the most part (because it's easier to customize and it's getting new features faster).

seancorfield23:10:35

We run Socket REPLs in a lot of our processes, so having an editor that can connect to a Socket REPL and eval code from source files that way is very important to my workflow -- in fact I just patched a bug in production that way about 30 minutes ago 🙂

noisesmith23:10:35

https://clojure.org/news/2020/02/20/state-of-clojure-2020 - emacs at 43%, then intellij, then vscode, then vim

noisesmith23:10:57

everything else is under 10%

jmckitrick00:10:06

@seancorfield Neat! I’ll have to check out ‘Reveal’ since I’m back on the Clojure scene.

jmckitrick00:10:49

@noisesmith thanks for that breakdown

mpenet08:10:01

most companies/startups I worked with have a split emacs/cursive with more users on emacs. I have seen many go from cursive -> emacs after a while (usually juniors) and can't recall one doing the opposite.

mpenet08:10:55

about inf-clojure I think that's quite uncommon outside of rh, stu and a few others. Personally I don't know a single dev who uses it exclusively

mpenet08:10:38

I don't see a reason not to use cider if you're on emacs really. I guess for core dev you might want something very minimalistic to avoid "false signals", but otherwise cider brings a ton of useful features

vlaaad08:10:55

I went from emacs to cursive 🙂

mpenet08:10:13

I personally know only 1 that used vscode, he's on cursive now

mpenet08:10:46

@U47G49KHQ yeah I guess ymmv, it's very difficult to have an overview of this stuff even with the survey imho

mpenet09:10:33

I doubt the majority of clojure devs I know from work fill the survey

mpenet09:10:41

it's good to have a few decent choices, we're not going to settle the editor wars anyway 🙂

cfleming09:10:59

I definitely know of a lot who have gone Emacs->Cursive, including several that everyone has heard of

cfleming09:10:12

But I tend to hear about those cases I guess 🙂

mpenet09:10:27

sure, I mentioned what I saw in my entourage, I don't doubt that

cfleming09:10:17

Sure, and no problem either way, like you say we have good options, and a lot depends on the culture you’re surrounded by too - it’s just easier to use what most in your group are using.

cfleming09:10:33

Or what you’re already used to.

mpenet09:10:41

yes, I think if you've been using emacs for 20 years you're less likely to move to something else

seancorfield15:10:59

I used Emacs for years but switched, and I know several Emacs users who prefer inferior mode, so it depends on who you know 😊

mpenet15:10:51

sure, as I said we mostly report anecdotal records, the conclusion imho is that tooling is good and we're fortunate to have quite a few good choices available. I wouldn't say one is better than the other just because some people around me use it (or not)

noisesmith16:10:33

I went from emacs/cider to emacs/monroe before switching to vim, currently I alternate between neovim/fireplace and neovim/vim-clojure-static

mpenet16:10:04

Neovim looks really nice now

mpenet16:10:26

I have heard good things about conjure

jmckitrick18:10:58

I’m about to make a big move career-wise into Clojure, and I’ve been using emacs/CIDER for a very long time. But I’d hate to miss out on really great stuff in Cursive just out of stubbornness.

seancorfield18:10:11

Yeah, I hear great things about Cursive but I dislike IntelliJ immensely 😐 The company even gave me free licenses of it to review a few different versions some years back but I just hated it 😞

seancorfield18:10:06

My advice is to use whatever editor you're most comfortable with, as long as it has a (reasonable) Clojure integration -- but I'm in the camp of minimal tooling so as long as you can: load a file into the repl, eval a form into the repl, pull up docstrings easily, maybe run tests via a hot key... that's about all I care about.

💯 3
seancorfield18:10:44

(I never type into the REPL so having any sort of "integrated REPL pane" is not useful to me)

jmckitrick18:10:31

@seancorfield before I totally commit to full-on CIDER, I’d be willing to try a minimalist setup. One on emacs and one on a different editor. I guess that would be emacs and clojure-inferior, then atom and a REPL. I’d probably lean toward emacs, especially if it’s super responsive with such a setup. But unless it’s very compelling, CIDER is probably going to be my choice.

seancorfield18:10:40

If you already like CIDER, I think you'd find inferior-mode to be disappointing. My main focus is so that I have the same experience "everywhere" so I can connect my editor to a Socket REPL in a running program -- locally or remotely -- and have the exact same editor experience as I would have with a local REPL (and I don't want CIDER/nREPL/middleware in my running programs).

jmckitrick18:10:07

That makes sense… connecting to a running instance should be as lightweight as possible.