clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
seancorfield 2020-11-25T00:01:03.146600Z

We have a few "rogue" JAR files at work that aren't available in a repo (and we've had to build ourself), and we just stick them in a lib folder accessible to our code and point :local/root at them.

seancorfield 2020-11-25T00:03:10.148Z

(I just checked and we're down to just one such JAR these days but Cognitect's REBL was also handled this way for quite a while: just download it somewhere and point a :local/root dependency at it)

jmckitrick 2020-11-25T00:08:19.148400Z

@noisesmith Ah yes, CI/CD must be considered

jmckitrick 2020-11-25T00:08:44.149200Z

@seancorfield This project is based on lein for the forseeable future.

2020-11-25T00:08:46.149400Z

right, @seancorfield’s solution is to check the jar into the repo it seems

seancorfield 2020-11-25T00:10:16.150300Z

(yeah, or build it on the fly from source and keep that source under git)

seancorfield 2020-11-25T00:10:43.151400Z

For dev-only stuff, you don't need to check it in, if it's just in your local deps.edn file tho'...

jmckitrick 2020-11-25T00:11:04.151800Z

Yes, it will certainly be Java under source control, but I want non-Clojure devs to be able to build it easily, and Clojure devs to consume it easily.

2020-11-25T00:11:36.152700Z

I think for something like that, it makes sense to treat it as its own artifact in a maven repo

seancorfield 2020-11-25T00:11:37.152800Z

For the particular "rogue" JAR I mentioned above, it's never going to change so it was a one-off add+commit. Ugly but...

2020-11-25T00:11:46.153200Z

I'd treat a shared clojure library the same way

seancorfield 2020-11-25T00:12:17.153800Z

Aye, I agree that I would expect a Java lib that needs to be built from source to have the artifacts published to a repo for the company.

seancorfield 2020-11-25T00:12:27.154100Z

(since it's going to keep changing)

seancorfield 2020-11-25T00:14:08.155900Z

We ran Apache Archiva for a similar reason for a couple of years (but it's pretty flaky and didn't seem well-maintained). If we needed to do that again, we'd probably use S3 buckets since that's a fairly widely support repository provider now for Clojure.

jmckitrick 2020-11-25T00:22:47.156100Z

Maybe an S3 bucket would work

2020-11-25T00:30:29.156400Z

this one is a little clunky, but it works https://github.com/s3-wagon-private/s3-wagon-private

2020-11-25T00:30:53.157Z

there are alternatives I haven't tried

zendevil 2020-11-25T01:48:51.157500Z

🙋 Roll call! Who else is here?

👍 1
jmckitrick 2020-11-25T02:08:13.157700Z

Thanks!

devn 2020-11-25T03:55:32.158400Z

@ps hello

2020-11-25T04:31:03.158600Z

hey

seancorfield 2020-11-25T04:37:57.159300Z

(reminder that there are thousands of people here so keep things focused on Clojure)

Setzer22 2020-11-25T09:54:44.160500Z

why is (or (not nil) (nil x)) an error? I have an optional parameter in my function, a predicate that may either be a lambda or nil, so I call it like this: (or (not f) (f x)) . But it gives me an error when f is nil. Doesn't or short-circuit the evaluation?

lukas.rychtecky 2020-11-25T10:05:11.161100Z

Because nil is not a function, you probably want nil?

2020-11-25T10:07:36.161300Z

or does short-circuit the evaluation

user> (def f nil)
;; => #'user/f
user> (or (not f) (f 1))
;; => true

lukas.rychtecky 2020-11-25T10:08:28.161600Z

Don’t you want something like (when f (f x) ? Or you can (when (fn? f) (f x))

2020-11-25T10:11:06.161800Z

but the forms like this (or (not nil) (nil 1)) will fail because first of all this form needs to be compiled and Compile exception will be thrown because of the form (nil 1) In case of macro it is easy to catch this case

2020-11-25T10:12:47.162Z

2020-11-25T10:54:02.166400Z

Just spotted LazilyPersistentVector in the clojure implementation, and I’m guessing this is part of the logic that keeps collections smaller than 32 elements as essentially linear arrays, but promotes them when they’re larger than that?

zilti 2020-11-25T10:58:55.170500Z

I have a question to reducers. I have a line that uses r/map . So far I had r/foldcat in my code, but while that ran everything in parallel as expected, I didn't get the result I wanted out of it. I see in the reducers reference it says "To produce an output collection, use https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/into". When I wrap the call to r/map in an into will it still be run in parallel?

reborg 2020-11-25T11:06:07.170700Z

Nope, just r/fold or r/foldcat (which is an r/fold in disguise) enable parallelism for foldable collections (beyond a certain size)

reborg 2020-11-25T11:17:33.170900Z

Wait, if you mean on top of r/foldcat then yes

(require '[clojure.core.reducers :as r])
(def input (r/map inc (into [] (range 1000))))
(into [] (r/foldcat input))
> [1 2 3...

2020-11-25T11:24:07.175Z

out of curiosity, how do Clojure’s short-lived functions behave with the JVM JIT? assuming I have a reduce in a hot function that uses a short-lived reduction function, I can only assume the JVM “forgets” the previous allocations (and invocations) of these short lived functions, right ?

lukas.rychtecky 2020-11-25T11:31:54.175100Z

Yeah that’s possible because (nil 1) makes no sense.

zilti 2020-11-25T11:39:23.175300Z

No, not on top of r/foldcat, because since the input to it is a vector of maps from r/map, it doesn't process them correctly (and seems to just merge the maps)

zilti 2020-11-25T11:40:09.175500Z

What I did now is write a reducef and wrap it in a (r/fold reducef (r/map ...)).

(defn- reducef
  ([] [])
  ([a b]
   (if (and (vector? a) (vector? b))
     (into a b)
     (conj a b))))

reborg 2020-11-25T12:00:26.175800Z

Worth remembering that r/foldcat is not an equivalent mapcat or concat of sort. The “cat” part in r/foldcat is mainly an implementation detail. But it seems to work as advertised in your case?

(require '[clojure.core.reducers :as r])
(def large-map (into {} (map vector (range 1000) (range 1000))))
(def vector-maps (into [] (repeat 1000 large-map)))
(def input (r/map #(assoc % :new :key) vector-maps))
(:new (first (r/foldcat input)))
;; :key
(count (into [] (r/foldcat input)))
;; 1000

dominicm 2020-11-25T13:47:32.176Z

What is "short lived"?

dominicm 2020-11-25T13:48:09.176200Z

Unless you're using eval, all functions are compiled to a class and have their locals available.

2020-11-25T14:19:58.176600Z

Anonymous functions are compiled once, not each time the function around them is called.

👍 1
2020-11-25T14:20:58.176800Z

And in general local functions defined within another function are compiled once, whether they are anonymous or have a name, so the "anonymous" part of my previous statement is irrelevant.

wombawomba 2020-11-25T14:33:52.185800Z

Let's say I have a macro that I want to conditionally define a few functions depending on its input — how can I break out the 'function-building' part into functions? For instance, let's say I have something like:

(defmacro foo [x]
  `(if (even? ~x)
     (defn ~(symbol (str "foo-" x) [] (inc x)))
     (defn ~(symbol (str "bar-" x)) [] (dec x))))
and I want to turn this into something like
(defn make-foo [x] `(defn ~(symbol (str "foo-" x) [] (inc x))))
(defn make-bar [x] `(defn ~(symbol (str "bar-" x) [] (inc x))))
(defmacro foo [x] `(if (even? ~x) (make-foo ~x) (make-bar ~x)))
...how can I get the result of make-foo and make-bar to be evaluated?

Timur Latypoff 2020-11-25T14:35:18.187600Z

Are there any editors (or IDE plugins) that somehow highlight the S-expressions which return values from a function? Or maybe there's an easy heuristic that everybody uses? As a newbie-lisper, I find it really difficult to mentally parse a large nested structure of let/loop/if/do/implicit-do to find all the places where expressions' values are not discarded and they form function's return values.

wombawomba 2020-11-25T14:35:27.187900Z

actually maybe this is a bad example

wombawomba 2020-11-25T14:36:27.188900Z

What I'm actually doing is:

(defmacro foo [x]
  `(let [x# (some-fn x)]
     (if (even? x#)
       (make-foo x#)
       (make-bar x#))))

wombawomba 2020-11-25T14:37:12.189700Z

and what ends up happening here is that my defns end up getting returned to the code calling the macro without being evaluated

scknkkrer 2020-11-25T14:37:46.190Z

def  prefix explains that the function define a `var` in the namespace. And, `def*`  calls must be top level calls. Any other usage of them is not right.

wombawomba 2020-11-25T14:37:53.190300Z

I'd do ~(make-foo x#) to solve this but that doesn't work because x# isn't available unquoted

scknkkrer 2020-11-25T14:38:16.190500Z

I recommend you to consider about your data-flow again.

2020-11-25T14:38:18.190700Z

The JVM JIT should see local Clojure functions as just another class with methods to execute. No different from top level functions.

wombawomba 2020-11-25T14:38:45.190900Z

@scknkkrer if that's the case, you should probably stop using defn 😉

scknkkrer 2020-11-25T14:40:08.191100Z

Sorry to bother you, it’s a common knowledge, I just wanted to inform you.

2020-11-25T14:40:36.191300Z

I do not know of any editor/IDE help related to this, but I haven't looked for such a thing, either.

wombawomba 2020-11-25T14:40:42.191500Z

No worries

2020-11-25T14:41:27.191700Z

I would suggest that another technique that would help with this, and perhaps have other benefits as well, is to make some effort at breaking sub-expressions within a large block of code into separate functions.

wombawomba 2020-11-25T14:41:44.191900Z

I'm aware that non-top-level defs are typically discouraged, but there are places where it's appropriate to make use of them (like in the definition of defn)

wombawomba 2020-11-25T14:42:02.192100Z

just wanted to inform you back 🙂

2020-11-25T14:42:06.192300Z

In general, smaller expressions / functions make it easier to understand a lot of things about them, including what you asked about, and can also make it easier to develop and test the smaller functions, sometimes separately.

👍 1
dominicm 2020-11-25T14:51:18.192600Z

The most deeply nested code at any point is usually the final step I guess. But this isn't something I generally find myself searching for in my programs either.

👍 1
Timur Latypoff 2020-11-25T14:57:52.192800Z

@andy.fingerhut I agree. I was just making some quick-and-dirty java-interop megafunction, and wondered if there's already such a "tail detection" implemented somewhere :)

2020-11-25T15:03:48.193200Z

for example, something like this:

(defn my-transform [prefix input]
   (reduce (fn [xs x]
              (conj xs (str prefix x)))
           {} input))

(my-transform "prefix" ["a" "b" "c"])
i am wondering what the behavior of the local (fn [xs x]) is for the JIT

2020-11-25T15:04:22.193400Z

especially since the behavior of this function depends upon an external input

2020-11-25T15:04:41.193600Z

but as i understand now, this is actually compiled once?

dominicm 2020-11-25T15:11:59.193800Z

Compiled once, yep.

2020-11-25T15:16:09.194Z

It is compiled once, and if you look at the JVM byte code, and/or the Java source code decompiled from that, you will see that the class created for the (fn [xs x] ...) function has a constructor that takes prefix as a parameter. So the local function is an instance of a class that is constructed once on each call to my-transform, but the class remains from one call of my-transform to the next. The only thing that is allocated fresh on each call to my-transform is an instance of that class.

2020-11-25T15:22:54.194200Z

Of the IDEs I know of, I don't have deep knowledge of them, but #cider has probably been around the longest, and #cursive has a full time commercial developer behind it, so those are the two IDEs that I'd guess have the most features, and therefore the most likely to have what you are asking for, if any of them do.

2020-11-25T15:25:58.194400Z

and if I recall correctly, the only thing in those instances are things like a local variable for each value in the inner function's environment, like prefix in your example, and the constructor does not do anything except assign values to those fields and return, so they should be as light weight as any constructor can be.

2020-11-25T15:26:34.194600Z

this is a great answer, thanks a lot! this was exactly what i was wondering, so the good news is that all this integrates really well into the JIT of the JVM 🙂

2020-11-25T15:31:40.194800Z

I’ve written some pretty gnarly clojure, and I’ve never had the problem of not being able to easily see the return form.

2020-11-25T15:32:09.195Z

Curious if you have some sort of example. do blocks should be rare enough that they stand out.

2020-11-25T15:32:28.195200Z

Oh, you know what helps: indentation markers.

2020-11-25T15:32:39.195400Z

together with properly formatted code

2020-11-25T15:34:53.195600Z

2020-11-25T15:35:49.196Z

(This is Cursive.) You can see the indentation markers on the left. Also, rainbow parens help to easily spot, e.g. where the finally block should go.

Jonas Claesson 2020-11-25T16:11:14.197900Z

I am writing a generative test case in kaocha, using fdef and a local function, but it seems like I get a null pointer exception when calling spec/exercise-fn. The function exercise-fn is calling is declared with letfn. Could that be the reason for my problems?

Jonas Claesson 2020-11-25T16:11:18.198100Z

`

Timur Latypoff 2020-11-25T16:11:23.198200Z

@potetm yeah, using these already. They help, but really don't give me feeling that I have everything covered. For example, upon careful examination, I see three tail positions in my code: 1. (recur) 2. :error 3. (swap!) But the function spans up a couple of screens more 🙂

Jonas Claesson 2020-11-25T16:11:32.198700Z

(test/deftest ^:integration-generative-concepts-paging generative-test-concepts-paging (test/testing "test main/concepts paging generatively" (let [_ (db/create-version-0) concepts (assert-concepts (rand-int 4)) ;; time consuming operation _ (versions/create-new-version 1)] (letfn [(test-concept-paging [query pagination] ;; Return true if the test passed. (let [params (generate-page-params (count concepts) query pagination)] true))] (let [res (spec/exercise-fn `test-concept-paging 10)] (doall (map #(test/is (= true true)) res)))))))

Jonas Claesson 2020-11-25T16:11:53.198900Z

user=> (use 'kaocha.repl) user=> (run 'jobtech-taxonomy-api.test.generative-test/generative-test-concepts-paging)

Jonas Claesson 2020-11-25T16:12:12.199100Z

Uncaught exception, not in assertion. Exception: java.lang.NullPointerException: null at clojure.core$apply.invokeStatic (core.clj:665) ... clojure.spec.alpha$exercise_fn$iter__2569__2573$fn__2574.invoke (alpha.clj:1881) ... jobtech_taxonomy_api.test.generative_test$fn__68371.invokeStatic (generative_test.clj:145) jobtech_taxonomy_api.test.generative_test/fn (generative_test.clj:135) jobtech_taxonomy_api.test.test_utils$fixture.invokeStatic (test_utils.clj:48)

Jonas Claesson 2020-11-25T16:12:47.199800Z

generative_test.clj:145 is the line with spec/exercise-fn

Jonas Claesson 2020-11-25T16:14:39.201700Z

I have written generative tests before, but never using a local function. The reason I have the local function is because I want to share some data across the test. This data takes a long time to generate, and I do not want to regenerate it for each time the tested function is called.

Jonas Claesson 2020-11-25T16:21:28.202200Z

Here's a link to the code if anyone feels inclined to have a look. https://gitlab.com/team-batfish/backend/jobtech-taxonomy-api/-/merge_requests/143

Timur Latypoff 2020-11-25T17:13:42.202700Z

Aaand I was wrong, these are not tail positions, because they turned out to be inside a doseq 😞

2020-11-25T17:52:29.203100Z

Since I have seen a similar question only a couple of weeks ago, and did most of this investigation at that time, I decided to put the results into a short article that you might find interesting: https://github.com/jafingerhut/jafingerhut.github.com/blob/master/notes/clojure-inner-functions.md

Ed 2020-11-25T17:54:44.203500Z

you're calling functions from a macro that just return lists of symbols. for make-foo and make-bar to be macro expanded into the defn for evaluation, they need to be macros not functions ... does that make sense?

👍 1
Ed 2020-11-25T17:57:40.203700Z

but, if you're always passing in literal values like that, you could take the syntax quote out and just return the code from foo?

👍 1
2020-11-25T17:57:56.203900Z

So, some things

2020-11-25T17:58:19.204100Z

This sort of side-effect heavy algorithm is never going to feel good in clojure.

2020-11-25T17:58:55.204300Z

There is an upside to that: It strongly encourages you to isolate side effects (which most developers are poor at).

2020-11-25T17:59:16.204500Z

The downside is: Any algorithm that really needs to be side-effect heavy is just going to feel bad.

2020-11-25T18:00:20.204700Z

IME those cases are relatively rare. It’s usually the case that I need to separate logic from side effects.

2020-11-25T18:00:29.204900Z

But those cases do exist. And they’re just not fun.

👍 1
respatialized 2020-11-25T18:02:31.206300Z

not quite sure if this is the right channel to ask this question, but I was wondering if anyone has ever done side-by-side pair programming with two REPL clients connected to the same REPL server?

Ed 2020-11-25T18:07:34.206400Z

yes ... I found it very confusing when someone else changed a function underneath me ... it was better to have one typing at a time

Ed 2020-11-25T18:09:09.206600Z

if you do things like load-file or cider-load-buffer then you can clobber changes that have been loaded in from someone else's machine without thinking about it ... it's a very easy thing to do

borkdude 2020-11-25T18:18:05.206800Z

@afoltzm I've heard some people do this via LiveShare in VSCode

✔️ 1
borkdude 2020-11-25T18:18:30.207Z

Ask in #calva for details.

respatialized 2020-11-25T18:20:53.207300Z

@l0st3d yeah I figured it could get really weird really quick if you're not careful, but it also seemed like an interesting technique in the distributed-by-default setting we're all in now, which is why I wanted to ask about people's experiences

Bob B 2020-11-25T18:31:04.207500Z

I think if you're doing pair programming in the sense that you're communicating about what changes each other are making and you're working toward a common goal, I could see it working, but if multiple people are redefining things in a REPL without communicating it, then it feels like essentially the whole "shared mutable state" problem, where the threads are people instead of program threads

✅ 1
seancorfield 2020-11-25T18:42:20.211700Z

For exercise-fn to run, your function needs at least :arg specs -- and you aren't writing a spec for test-concept-paging

seancorfield 2020-11-25T18:42:58.211900Z

user=> (letfn [(foo [n])] (s/exercise-fn `foo))
Execution error at user/eval24110 (REPL:1).
No :args spec found, can't generate
@jonas.cl

cap10morgan 2020-11-25T18:43:26.212100Z

How would one set any of the clojure.core/*config-flag* type vars from Java?

seancorfield 2020-11-25T18:43:35.212200Z

Where is your fdef?

cap10morgan 2020-11-25T18:44:38.213500Z

I'm writing a plugin in a Java environment that hands me a pretty restricted classloader and I think I need to set *use-context-classloader* to false before I invoke any clojure code.

st3fan 2020-11-25T18:47:25.214Z

var: #'clojure.core/assert-args is not public. - hmm this seemed like such a useful macro to use in my own code

st3fan 2020-11-25T18:49:42.214300Z

I just copied it over to my own 'core' lib

Jonas Claesson 2020-11-25T18:59:10.214500Z

The fdef is

Jonas Claesson 2020-11-25T18:59:13.214700Z

(spec/fdef test-concept-paging :args (spec/cat :query ::concept-paging-query :pagination ::paging) :ret (spec/and boolean? #(= true %)))

Jonas Claesson 2020-11-25T19:00:06.214900Z

But to evaluate everything you also need a few more functions from, https://gitlab.com/team-batfish/backend/jobtech-taxonomy-api/-/merge_requests/143/diffs

seancorfield 2020-11-25T19:00:21.215200Z

That would be a global (top-level) function tho', right?

Jonas Claesson 2020-11-25T19:00:46.215400Z

Yes

Jonas Claesson 2020-11-25T19:01:21.215600Z

But the problem might be that the implementation is local?

seancorfield 2020-11-25T19:01:36.215800Z

And in letfn, that's a different function -- it's a local name that shadows any global.

Jonas Claesson 2020-11-25T19:02:47.216Z

Ok, now I see. The top-level fdef doesn't find the local one.

Jonas Claesson 2020-11-25T19:02:54.216200Z

test-concept-paging is only defined locally.

Jonas Claesson 2020-11-25T19:03:17.216400Z

So then I need to define the fdef at the same local level as test-concept-paging?

seancorfield 2020-11-25T19:08:08.216600Z

I'm pretty sure fdef only works on top-level functions.

Jonas Claesson 2020-11-25T19:08:20.216800Z

Thanks for the help. I'll see tomorrow, how to do that. But is this the right way to test a function that has some parameters constant and the same, and others that you want to vary?

Jonas Claesson 2020-11-25T19:08:37.217Z

Unfortunately it seems that way. I did a quick test

seancorfield 2020-11-25T19:10:29.217200Z

clojure.spec is not a type system 🙂

seancorfield 2020-11-25T19:14:48.217400Z

(local functions are generally just considered an implementation detail)

seancorfield 2020-11-25T19:15:18.217600Z

In the code you shared, I would expect generate-page-params to have the fdef and be the function under test.

2020-11-25T19:33:14.218Z

Yes, I’ve always found shared keyboard control creates mutex issues. One person “driving” (typing) at a time, taking turns, seems to work better for me.

Jonas Claesson 2020-11-25T19:49:56.218200Z

Ok, thanks! I'll try to rearrange the functionality tomorrow.

dominicm 2020-11-25T19:58:51.218400Z

You might see an example in bukkure

2020-11-25T20:07:23.219200Z

Does anybody know how to get javadoc to work in cider? I've used it for a decade, and never seen it do anything useful.

seancorfield 2020-11-25T20:07:49.219700Z

@russell.mull Maybe ask in #cider?

2020-11-25T20:07:58.219900Z

oh right on, didn't know that was a thing. Thanks.

2020-11-25T20:17:56.220600Z

clojure.java.javadoc is aliased to javadoc in the repl by default, and uses the web browser

2020-11-25T20:29:27.221300Z

https://www.reddit.com/r/Clojure/comments/k105ud/inner_functions_in_clojurejava/ shared on Reddit if you don’t mind :)

2020-11-25T20:29:32.221700Z

it’s great

2020-11-25T21:06:15.222700Z

(it can even try to find javadoc using google’s I’m feeling lucky URL, which I found amusing when I stumbled over that)

alexmiller 2020-11-25T21:18:00.223100Z

(although that is broken right now as google changed the url)

cap10morgan 2020-11-25T22:22:41.223500Z

thanks! I don't see any instances of use-context-classloader in there from a GitHub search.