clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
2021-04-11T05:03:59.327400Z

I'm currently testing out a way that I can remove the necessity to use gen-class in a library I'm writing. I have to have a class be referred to in a catch block for my code to perform the desired behavior, but to prevent breakage if the proxy-name implementation ever changes, I would prefer to not have the name of the proxy class directly embedded in the code. As a result, I'm considering writing code like the following:

(try whatever
     (catch #=(symbol (proxy-name Exception [some.interface.Foo])) e
       ...))

2021-04-11T05:05:20.328400Z

Is this use of read-time evaluation considered a problem, or is this one circumstance where it would be acceptable?

2021-04-11T05:05:57.328500Z

That sounds horrendous. Why do you need a custom exception type?

2021-04-11T05:07:08.329Z

You are controlling both the throw and the catch, so like don't do that

2021-04-11T05:07:08.329100Z

Custom control flow operations. It extends Error so that catch blocks in most java code won't interfere.

2021-04-11T05:07:25.329200Z

Sounds terrible

➕ 1
☝️ 2
2021-04-11T05:07:46.329600Z

Eh, it's what I have to deal with to implement dynamic extent non-local return.

2021-04-11T05:08:29.330Z

The entire purpose of the Error class is that it isn't caught by sane code outside of the runtime.

2021-04-11T05:08:35.330200Z

So it's more or less safe.

2021-04-11T05:08:52.330500Z

All I'm trying to do at this point is remove the need for gen-class

2021-04-11T05:09:53.330900Z

Also I guess it just doesn't quite work either, because the reader doesn't seem to like this code anyway

2021-04-11T05:14:13.331500Z

I guess unfortunately I need to just expand this to a macro

jumar 2021-04-11T05:47:06.333800Z

Your assumption might be violated more often than you think. Many people catch Throwable at edges to catch things like AssertionError

2021-04-11T05:53:40.334Z

While it may be that there is code that violates this, and perhaps more code than I would like, the places where it will matter (clojure code binding a context, passing a closure to java to run, and then signaling to the context from inside the closure) are infrequent enough that I'm comfortable just documenting them and moving on with my life.

vemv 2021-04-11T06:30:04.334400Z

Note that #=( isn't a public API (as e.g. grepping https://github.com/clojure/clojure-site would reveal)

quadron 2021-04-11T11:05:16.335300Z

say i have a set of independent actions, what is the simplest representation of asynchronously executing them?

2021-04-11T12:23:32.336Z

Just wrapping each one in future will execute each one on a different thread. Do you need to do anything with the results or care about order of execution?

✔️ 1
2021-04-11T13:19:54.336700Z

I would like to refactor by moving all clj files under xx.yy name space to xx.yy.zz. How to achieve that?

fubar 2021-04-11T14:04:06.337300Z

Are there any current Clojure newsletters? It looks like The REPL and and Clojure Weekly have been deprecated which is a bummer. https://www.therepl.net/newsletters/ https://us19.campaign-archive.com/home/?u=f5dea183eae58baf7428a4425&id=ef5512dc35

borkdude 2021-04-11T14:12:49.339100Z

Perhaps a new initiative could be organized as a collaborative repo where people can make PRs and the newsletter is built semi-automatically, this would probably make it more sustainable and less dependent on one person. I know other languages like Rust and Haskell have something like this.

2021-04-11T14:18:54.339400Z

@borkdude For clj-kondo.lint-as/def-catch-all, what does it check?

quadron 2021-04-11T14:20:02.340Z

no, results don't matter

borkdude 2021-04-11T14:20:09.340300Z

it disables all linting but just registers the fact that (deffrob foobar ....) introduces a var foobar.

2021-04-11T14:20:44.340500Z

thanks.

quadron 2021-04-11T14:20:53.340900Z

i also found the

consume-async
in manifold library

2021-04-11T14:21:10.341400Z

I’ve another question, how can I supprese warning on a variable in a clj file? Can I do that inline in that file?

quadron 2021-04-11T14:22:21.341500Z

was wondering if there are special constructs in core async for colls of actions

quadron 2021-04-11T14:22:33.341700Z

since colls are treated specially in clojure that is

2021-04-11T14:23:39.342200Z

For example, I have a core.clj where I import other variables for external use.

borkdude 2021-04-11T14:30:00.342500Z

Let's take it to #clj-kondo

2021-04-11T14:39:36.342700Z

consume-async in core.async would just roughly be

(defn consume-async [f ch]
  (go-loop []
    (when-let [x (<! ch)]
      (f x)
      (recur))))

✔️ 1
2021-04-11T14:40:34.343Z

you can take items from a collection and put them on a channel with core.async/onto-chan!

chepprey 2021-04-11T15:47:29.343600Z

Eric Normand's https://purelyfunctional.tv/ has a newsletter signup. I'm a subscriber. All of Eric's stuff is great.

2021-04-11T16:16:32.350300Z

I have a question about Kaocha tests which i suspect is really a question about classloaders. I was trying to config Kaocha in my multi-project repo. which means that some defaults must be altered. Specifically, the test-pathskey must point to ${subproject}/test rather than test. After making this change, Kaocha was not finding any tests. I had my test file tree arranged to mirror the source tree. Therefore, the first component I wanted to test was not in the root test directory, but rather in test/components/first-component.cljc That didn't work either, so I copied it to test just to see if I could get a test to run. Eventually I figured out that the filenames should be snake case rather than kebab case. That worked, but I still had the kebab case files in my tree and to my surprise it found all three. I then tried moving the snake case file to the components directory as per my original intent, and Kaocha stopped finding it. I was about to file a Kaocha bug requesting better documentation but I decided I wasn't sure what the rules were. 1. Does the snake case file have to be in the test root? (Is that a Kaocha bug or a rule of classloading?) 2. Once Kaocha finds a snake case file, why can it then find all the kebab case files, even in different directories?

vemv 2021-04-11T16:20:45.350400Z

clj-refactor is supposed to do that but it's not super fast beyond a certain project size 😔 for these cases I simply create a folder (`yy` this case ), rename files accordingly and then perform a search/replace for updating the ns forms did so last week, it was a pretty meaty case and yet it still took me < 5m

NoahTheDuke 2021-04-11T16:31:52.351400Z

Clojure files have to be snake case, right?

2021-04-11T16:37:53.353300Z

i just checked my source tree and i don't have any filenames with multiple segments, have i lost track of a fundamental rule? maybe :sheepish

2021-04-11T16:39:04.354400Z

that said, my questions still stand

2021-04-11T16:46:54.355100Z

I don't know about Koacha's rules for finding source files, but Clojure/Java require must have snake case, or it will not find the files.

2021-04-11T16:47:40.355900Z

If Koacha sometimes finds files with kebab case, then keeping such files around, rather than renaming or deleting them, is going to cause confusion for you on what Clojure/Java is loading, versus what Koacha is finding.

2021-04-11T16:50:01.358700Z

If the classpath you have set up in your Leiningen project.clj or Clojure CLI tools deps.edn file includes a directory like test, and you have a test namespace named components.first-component, then that should be in a file named test/components/first_component.clj, or Clojure/Java require will not find it.

2021-04-11T16:51:29.361300Z

(unless you confusingly have a file named components/first_component.clj in another of your classpath directories, e.g. src. Avoiding such name conflicts is why you will often see test namespaces have test as part of their name, so their namespace names do not conflict with production source code file names, e.g. components.first-component for a production code namespace, and components.first-component-test for a test namespace.

2021-04-11T16:51:58.361600Z

i hold up my hands in surrender re: snake case. I'll change the kebab case test files to snake case. I'm genuinely surprised that i have zero multi-segment filenames in this (pretty large) code base. Apparently my code is so well organized that it's not necessary? Obviously whatever-test namespaces were going to highlight this.

2021-04-11T16:53:01.362100Z

Are you saying your projects have no Clojure namespaces with . in their names?

2021-04-11T16:53:35.362700Z

That is legal, but unusual.

2021-04-11T16:55:24.364200Z

No, but the dot-based namespace hierarchy maps precisely to the slash-based filesystem hierarchy

2021-04-11T16:55:47.364500Z

dots in Clojure namespaces are multi-segment namespaces

2021-04-11T16:56:25.365400Z

That is what is meant by a multi-segment namespace, unless I have missed the meaning of that term completely somehow.

2021-04-11T16:58:26.367300Z

When you say "multi-segment filenames", do you mean "has a dash or underscores in their names" ?

2021-04-11T16:59:02.368Z

yes, but there are no multi-segment filenames. There is no foo-componentthus foo_component.clj, everything is arranged like components/foo.clj

2021-04-11T16:59:52.368900Z

yes to your last question

2021-04-11T17:00:18.369600Z

I will defer to others here who may know the terminology better than I do, but I am pretty sure that most Clojure developers use "multi-segment namespace" to mean something like foo.bar, with two segments, versus namespace foo-bar, which has one (no .). It has nothing to do with underscore/dash in the name.

👆 1
2021-04-11T17:02:34.371700Z

i was trying to draw a distinction between namespace and leaf filenames. I think I have my answers though. Thanks everyone for indulging my self-induced myopia

dpsutton 2021-04-11T18:28:01.373900Z

i'm working on a clojure.main/repl that will put all repl forms into a graph database. What would be a good api for using this? Currently it's just (:grepl/root +) would show all repl forms that used + somewhere in them or (:grepl/parent +) to show all one-level-up forms that used +. Trying to think how you kinda interact with the repl's provided features

dpsutton 2021-04-11T18:38:01.374900Z

grepl.repl=&gt; (let [a 1 b 2] (+ a b))
3
grepl.repl=&gt; (:grepl/parent b)
(+ a b)
nil
grepl.repl=&gt; (:grepl/root b)
(let [a 1 b 2] (+ a b))
nil

seancorfield 2021-04-11T18:42:27.375100Z

Since (:some/kw b) has meaning already, wouldn’t (grepl/parent b) be less confusing as an API?

seancorfield 2021-04-11T18:43:00.375300Z

Also, given (let [a 1 b 2] (+ a b)) I think I would expect [a 1 b 2] to also be a parent to b?

dpsutton 2021-04-11T18:43:53.375800Z

To me that’s a sibling in the tree

seancorfield 2021-04-11T18:44:36.376900Z

OK, so bindings are not considered “use”. Fair enough.

dpsutton 2021-04-11T18:45:04.377600Z

And about using a symbol, I didn’t want to do that in case someone had that as an alias. Using the keyword made sure it’s always kinda ok to hijack evaluation

seancorfield 2021-04-11T18:46:02.377800Z

What about:

grepl.repl=&gt; (def b 2)
#'grepl.repl/b
grepl.repl=&gt; (let [a 1 b b] (+ a b))
3
grepl.repl=&gt; (:grepl/parent b)
???

dpsutton 2021-04-11T18:47:09.378Z

grepl.repl=&gt; (def b 2)

#'grepl.repl/b
grepl.repl=&gt; (let [a 1 b b] (+ a b))

3
grepl.repl=&gt; (:grepl/parent b)
(+ a b)
(def b 2)
nil
grepl.repl=&gt; 

dpsutton 2021-04-11T18:47:31.378200Z

grepl.repl=&gt; (:grepl/root b)
(let [a 1 b b] (+ a b))
(let [a 1 b 2] (+ a b))
(def b 2)
nil
grepl.repl=&gt; 

seancorfield 2021-04-11T18:47:54.378400Z

So the “use” of the global b in the let binding isn’t registered here?

seancorfield 2021-04-11T18:48:38.378600Z

(I’m trying to establish a “mental model” of what you’re trying to do here — and somewhat failing)

dpsutton 2021-04-11T18:48:44.378800Z

correct. a "use" is where a node is exactly what you typed in. it's doing a tree-seq on the form, stuffing each value into a graph database

dpsutton 2021-04-11T18:49:12.379100Z

and when you look for "parent", it find's all the nodes that match it exactly and then "goes up" one level to give context, as otherwise it would just return exactly what you typed in

dpsutton 2021-04-11T18:49:51.379600Z

grepl.repl=&gt; (pprint (-&gt;tx-data '(let [a 1 b b] (+ a b))))
({:grepl/form "(let [a 1 b b] (+ a b))", :grepl/root -1, :db/id -1}
 {:grepl/form "let", :grepl/root -1, :db/id -2}
 {:grepl/form "[a 1 b b]", :grepl/root -1, :db/id -3}
 {:grepl/form "(+ a b)", :grepl/root -1, :db/id -4}
 {:grepl/form "+", :grepl/root -1, :db/id -5}
 {:grepl/form "a", :grepl/root -1, :db/id -6}
 {:grepl/form "b", :grepl/root -1, :db/id -7}
 [:db/add -1 :grepl/parent -1]
 [:db/add -1 :grepl/root -1]
 [:db/add -2 :grepl/parent -1]
 [:db/add -2 :grepl/root -1]
 [:db/add -3 :grepl/parent -1]
 [:db/add -3 :grepl/root -1]
 [:db/add -4 :grepl/parent -1]
 [:db/add -4 :grepl/root -1]
 [:db/add -5 :grepl/parent -4]
 [:db/add -5 :grepl/root -1]
 [:db/add -6 :grepl/parent -4]
 [:db/add -6 :grepl/root -1]
 [:db/add -7 :grepl/parent -4]
 [:db/add -7 :grepl/root -1])

seancorfield 2021-04-11T18:50:02.379900Z

Right, but I would expect (let [a 1 b b] ..) to register as a use of the global b.

dpsutton 2021-04-11T18:50:18.380300Z

oh i see. i wonder why it didn't descend into that

dpsutton 2021-04-11T18:50:44.380900Z

ah, classic (seq? [a 1 b 2]) is false

dpsutton 2021-04-11T18:51:42.381200Z

switching to seqable? as the branch test in the tree-seq fixes it

dpsutton 2021-04-11T18:51:45.381400Z

grepl.repl=&gt; (:grepl/parent b)
(+ a b)
[a 1 b b]
(def b 2)
nil
grepl.repl=&gt; 

yuhan 2021-04-11T18:56:45.383Z

How do I get a ns-qualified symbol in a macro? (symbol (str (ns-name *ns*)) (str sym)) works but doesn't seem right.

seancorfield 2021-04-11T19:04:34.384700Z

OK, that’s much more intuitive for me now!

2021-04-11T19:04:51.385Z

`~sym

2021-04-11T19:06:12.385200Z

well. it depends what exactly you’re trying to do

yuhan 2021-04-11T19:06:42.385400Z

I tried that but it doesn't qualify the symbol

dpsutton 2021-04-11T19:07:00.385700Z

thanks. and i further updated it so it enumerates maps better.

yuhan 2021-04-11T19:07:09.385900Z

Context: I'm trying to write a def macro which does expression-based caching of the definition body:

(defonce cache (atom {}))

(defmacro defcached
  [sym &amp; args]
  (swap! cache assoc (symbol (str (ns-name *ns*)) (str sym)) args)
  `(def ~sym ~@args))

2021-04-11T19:07:15.386100Z

it does, but it also resolves it

dpsutton 2021-04-11T19:07:16.386300Z

need to refine the api as well. i've got "substring" type matching as well

phronmophobic 2021-04-11T19:08:26.386500Z

`~sym
That doesn't produce a fully qualified symbol for me either

phronmophobic 2021-04-11T19:08:51.386700Z

I think you @qythium’s (symbol (str (ns-name *ns*)) (str sym)) should work

phronmophobic 2021-04-11T19:09:46.386900Z

I might also suggest using [*ns* sym] as the cache key rather than converting it to a fully qualified symbol

phronmophobic 2021-04-11T19:10:21.387100Z

or even (swap! cache assoc-in [*ns* sym] args)

yuhan 2021-04-11T19:10:36.387300Z

oh right, that makes a lot more sense!

dpsutton 2021-04-11T19:11:32.387500Z

https://github.com/dpsutton/grepl if you want to see it

yuhan 2021-04-11T19:11:57.387800Z

Are there any issues with using namespace objects as map keys? Or should I convert them to symbols to be safe

2021-04-11T19:12:11.388Z

I would actually suggest using a caching lib.

phronmophobic 2021-04-11T19:12:24.388200Z

yea, you may want to check out https://github.com/clojure/core.cache

phronmophobic 2021-04-11T19:14:52.388600Z

specifically, clojure.core.cache.wrapped from that lib

yuhan 2021-04-11T19:15:03.388800Z

I had a look at that but it seemed too complex a solution for my use-case - I just wanted to scratch an itch with REPL development

phronmophobic 2021-04-11T19:15:31.389Z

if you want a per function cache, you can use the built in memoize

yuhan 2021-04-11T19:16:08.389200Z

ie. having a

(def big-data (expensive-operation :some :args))
and wanting to only re-evaluate it when the arguments change

seancorfield 2021-04-11T19:16:23.389400Z

Kind of amazing how little code something like that is… 👀

dpsutton 2021-04-11T19:16:59.390300Z

Ha. The graph db does all the work. It’s really just putting the forms and the pointers in there

yuhan 2021-04-11T19:17:07.390700Z

I guess memoize would work in that case :thinking_face: Thinking of a case where it wouldn't

phronmophobic 2021-04-11T19:17:07.390900Z

for that, I typically have in my repl:

(def expensive-operation-memo (memoize expensive-operation))
so you can do:
(def big-data (expensive-operation-memo :some :args))

dpsutton 2021-04-11T19:17:24.391500Z

Some thought how to provide a help menu and how to return items. Might want values not just pprint

phronmophobic 2021-04-11T19:17:25.391800Z

so that I can still redo the calculation if I really want to

dpsutton 2021-04-11T19:17:52.392800Z

Also, I’ve found that the pr str of regex aren’t readable so that’s why there’s a try catch in there

dpsutton 2021-04-11T19:18:07.393300Z

Probably need to harden more as well in general

dpsutton 2021-04-11T19:21:30.394300Z

Also if I can figure out how to return values can make them datafiable so you can walk up the form as much as you like

yuhan 2021-04-11T19:23:07.394500Z

Ah, just remembered why I wanted to stay away from memoize - I don't want previous evaluations taking up memory in the memoization map

phronmophobic 2021-04-11T19:23:41.394700Z

ah

yuhan 2021-04-11T19:23:53.394900Z

Calling expensive-operation on a new set of arguments means I don't want to revisit the old args

2021-04-11T19:24:27.395100Z

Are you looking for def?

2021-04-11T19:25:46.395300Z

really though, clojure already has a cached, ns-symbol -> value lookup: the namespace. 🙂

yuhan 2021-04-11T19:30:40.395500Z

Hmm, I'll make it more concrete - let's say I have

(def im
  (img/load-image "resources/A1.png")) 
with a 500Mb image file that takes half a minute to load. Re-evaling the form / reloading the namespace would cause the image to be read every time

phronmophobic 2021-04-11T19:31:29.395700Z

caching as you were doing before seems reasonable. You may also be able to get away with defonce

phronmophobic 2021-04-11T19:31:44.395900Z

(defonce im (img/load-image "resources/A1.png"))

phronmophobic 2021-04-11T19:31:55.396100Z

which doesn't work if you want to reload it if the args change

yuhan 2021-04-11T19:32:10.396300Z

Yeah, I want to reload it when I change A1 to A2.png

phronmophobic 2021-04-11T19:32:25.396500Z

yea, your cache macro seems like a good fit :thumbsup:

yuhan 2021-04-11T19:33:06.396700Z

Great, thanks for the advice!

2021-04-11T19:35:10.396900Z

does it actually solve that problem?

2021-04-11T19:35:23.397100Z

(defcached foo
           (do (println "sleeping...")
               (Thread/sleep 1000)
               (println "done")
               :foo))

2021-04-11T19:35:31.397300Z

Loading dev/user/cert.clj... 
sleeping...
done
Loaded
Loading dev/user/cert.clj... 
sleeping...
done
Loaded

yuhan 2021-04-11T19:36:43.397500Z

That was only the first half of the impl, I'm testing the rest of it now

2021-04-11T19:38:45.397700Z

alternatively: defonce + alter-var-root for when you want to redefine

yuhan 2021-04-11T19:42:46.397900Z

Yup, seems to work 🙂

(defonce definition-cache (atom {}))

(defmacro defcached
  "Behaves just like clojure.core/def, but subsequently
  is only reevaluated if the body expression has changed
  (according to Clojure equality semantics)

  Purely compile-time syntactical check,
  does not trigger if eg. a dependency has changed."
  [sym &amp; args]
  (let [k [(ns-name *ns*) sym]
        old-definition (get @definition-cache k)]
    (if (= args old-definition)
      `(var ~sym)
      (do
        (swap! definition-cache assoc k args)
        `(def ~sym ~@args)))))

(defcached big-data
  "wow"
  (do (println "sleeping...")
      (Thread/sleep 1000)
      (println "done")
      (range)))

@definition-cache
;; =&gt; {[sandbox.defcached big-data]
;;     ("wow"
;;      (do (println "sleeping...") (Thread/sleep 1000) (println "done") (range)))}

2021-04-11T19:43:58.398100Z

yeah that makes more sense now. I missed the part where you were going to check the body form before re-evaluating.