clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
Jakub Holý 2020-10-29T08:14:48.366400Z

Hello! I struggle finding a good way how to perform parametrized initialization only once in the face of concurrent calls. What I have now is:

(defn get-or-init-adoc [atom params]
  (compare-and-set! atom nil (delay (some-expensive-init-reading-files params)))
  @@adoc)
Is there a better way (then delay inside an atom)? What I essentially want is (atomic-if (initialized? x) x (init+return x, params)) where x cannot be changed between I check whether it is initialized and initializing it. Then, inside a frequently called function I do (defn render [params file] (.convert (get-or-init-adoc adoc params) file))

Eugene Koontz 2020-11-08T14:45:38.232800Z

I had the same problem (doing something slow and expensive that should only be done once per process) and came up with the following solution: https://github.com/ekoontz/menard/blob/master/src/menard/nederlands.cljc#L242

Eugene Koontz 2020-11-08T14:48:58.233200Z

and then every usage of the model calls (load-model) , although the actual loading is only done once : subsequent calls to load-model simply deferences the ref

2020-11-09T01:25:36.247500Z

@ekoontz delay might be a simpler way to get that result (depending on whether you want to re-execute)

Eugene Koontz 2020-11-09T03:15:05.259500Z

you’re right - thanks for that! updated: https://github.com/ekoontz/menard/commit/fbad11942fb7a46e51665abc054ac633c78fa6ee

Eugene Koontz 2020-11-09T19:41:02.271100Z

ahh, I do want to re-execute, so I can reload the models, so a delay won’t work, as you said. So my original code would be better.

2020-11-09T19:42:52.271300Z

also, there are dedicated libraries for stateful initialization, which take things like cross-ns dependencies and re-initialization into account - eg stuartsierra/component and integrant

👍 1
Eugene Koontz 2020-11-09T19:52:09.271600Z

i feel like I have to work through the primitives (what the core clojure libraries provide) to even appreciate the dedicated extra libraries

Ed 2020-10-29T09:18:02.366700Z

what's wrong with just having a (def x (delay (some-expensive-init params))) ?

Ed 2020-10-29T09:19:10.367200Z

where are the params coming from?

Ed 2020-10-29T09:23:18.367600Z

there's also promise which you can deliver once and will block all defref s until it's delivered ... but you need to be careful not to create deadlocks where the read calls block all the running threads before any thread can be started to deliver the promise

Ed 2020-10-29T09:26:06.367800Z

or maybe (defn get-or-init-adoc [atom params] (swap! atom #(when (nil? %) (some-expenisive-init params)) ?

Ed 2020-10-29T09:31:33.368Z

I guess you're trying to make sure that the init only gets run once, and have the function lazily initialise some value?

✅ 1
Ed 2020-10-29T09:31:48.368200Z

so maybe you could memoize the function?

borkdude 2020-10-29T09:32:17.368400Z

note that memoize is not suited for non-referentially transparent functions

borkdude 2020-10-29T09:32:35.368600Z

and it doesn't guarantee that the underlying function will be executed once

borkdude 2020-10-29T09:33:08.368800Z

you could use locking to lock some sentinel, which will probably be the least headachy

✅ 1
vncz 2020-10-29T13:54:23.370100Z

Do you know what's a free service to quickly deploy a Clojure Deps.edn application?

vncz 2020-10-29T13:54:39.370500Z

I would normally use Heroku but they do not support Clj tools out of the box and I need to do to many changes to my project

zilti 2020-10-29T13:55:09.370700Z

Make an uberjar and deploy that one

vncz 2020-10-29T13:56:15.371100Z

Yeah I guess that's the easiest way. Any chance you have a link about producing an uberjar with clj.tools @zilti

zilti 2020-10-29T13:57:11.371300Z

Yes: https://github.com/seancorfield/depstar

vncz 2020-10-29T14:47:46.371700Z

2020-10-29T14:47:15.148369+00:00 app[web.1]: Error: Invalid or corrupt jarfile app.jar

vncz 2020-10-29T14:47:57.372Z

Interestingly enough, the deployed jar on Heroku seem to be broken or something :thinking_face:

borkdude 2020-10-29T14:50:32.372200Z

@vincenz.chianese Does it work locally though?

vncz 2020-10-29T14:51:50.372700Z

Yeah, I think I made a mistake in the jar execution, let me try again 🙂

vncz 2020-10-29T14:52:08.373Z

I was specifying a wrong namespace

vncz 2020-10-29T14:57:20.373400Z

Ok that works now, but it seems like I'm having issues running datomic dev-local on heroku

borkdude 2020-10-29T15:00:10.373900Z

the name doesn't suggest you should run it in production maybe? just guessing

vncz 2020-10-29T15:00:55.374200Z

No, I think it's because I'm using a wrong directory name, let me triple check…

vncz 2020-10-29T15:04:08.374500Z

Ok I got it working. It takes ages to startup but eventually it works

vncz 2020-10-29T15:04:13.374700Z

😂

2020-10-29T16:32:00.374900Z

I don't really see the benefit of putting an atom outside the delay here, the delay already locks and will only execute once

Jakub Holý 2020-10-29T18:17:26.375800Z

The problem is I need to do the initialization inside a function that gets 'params' as it's argument. And I don't want to unnecessarily create a new delay upon each its call.

Jakub Holý 2020-10-29T18:18:16.376Z

swap! doesn't work because it can execute the function multiple times.

2020-10-29T19:18:51.378700Z

agents never retry, fwiw

2020-10-29T19:19:06.378900Z

so sending an fnil to an agent should work

👍 2
Jakub Holý 2020-10-29T20:00:48.379200Z

Thanks! I guess careful use of locking is the simplest solution here...

elton 2020-10-29T20:22:41.390900Z

Hi does anyone have an idea of how I would do this? I’m trying to conditionally define functions using a global lein profile depending on whether I have certain libraries loaded, ex. something like this where I’m developing several libraries at once, some of which are childrens of the others. Say if the dependencies looked something like

[my.lib5 [my.lib1 my.lib2 my.lib4]
 my.lib4 [my.lib1 my.lib2]
 my.lib3 [my.lib2]]
and basically the workflow would be something like while i’m in a repl for my.lib3, to have all the custom function definitions defined using variables defined in my.lib3 and my.lib2 and so on. So I have something like this being loaded via {:repl {:repl-options {:init (load-file "~/.lein/startup.cljc")
;; load libs if they exist
(let [repl-deps ['[my.lib1 :as l1]
                 '[my.lib2 :as l2]
                 '[my.lib3 :as l3]
                 '[my.lib4 :as l4]
                 '[my.lib5 :as l5]
                 '[clj-async-profiler :as prof]
                 '[criterium.core :as crit]]]
  (doseq [dep repl-deps]
    (try
      (require dep)
      (catch Exception e nil))))

;; Create custom functions depending on what's loaded
(let [namespaces (->> (all-ns) (map ns-name) (map name) set)
      loaded? (fn [& args] (every? namespaces args))]
  (when (loaded? "my.lib1")
    (defn custom-fn-1 (l1/foo) )
  (when (loaded? "my.lib2")
    (l2/init)
    (defn custom-fn-2 (l2/bar)))
  (when (loaded? "my.lib2" "my.lib4")
    (l4/init l2/options)
    (defn custom-fn-3 (l4/baz)))
But it doesn’t work because I get a compile error No such namespace: l4 when in a repl for my.lib1, my.lib2 and my.lib3 . So I’m wondering, is there anyway to solve this without too much mucking about? Or is the only way to solve this to have a compiler option *allow-unresolved-namespace* in the compiler which would be used in resolveIn in Compiler.java?

borkdude 2020-10-29T20:24:47.391900Z

@elton.law This is called the Gilardi scenario: https://technomancy.us/143

2020-10-30T14:02:07.420800Z

nice little bit of nostalgia courtesy of @technomancy and you two. :)

borkdude 2020-10-30T14:03:33.421100Z

I'm honoured to finally meet the person behind the name :)

2020-10-30T14:11:35.421900Z

🙂 thank you! I’m honored and happy to meet you! Thanks so much for your work and initiative in producing such useful and clever tools for us.

ghadi 2020-10-29T20:26:11.394Z

Gilardi issue is secondary symptom - the root issue is you are throwing away your exceptions @elton.law . Who knows what that is suppressing

ghadi 2020-10-29T20:26:49.394700Z

But a good hypothesis is that lib4 is not loading initially, being skipped silently, causing a subsequent compilation error later (failure to resolve symbol l4/init )

ghadi 2020-10-29T20:30:22.397300Z

but also good to understand the gilardi scenario

elton 2020-10-29T20:30:25.397500Z

Umm forgive me if I’m misunderstanding but I don’t think the Gilardi scenario is related, I’m working on a project that has about 20 different individual projects, and basically, sometimes I will be able to do a require on that my.lib4 but sometimes I won’t (because it’s not a dependency)

elton 2020-10-29T20:31:06.398100Z

and I want specific blocks using variables from my.lib4 to not be run

dpsutton 2020-10-29T20:32:15.399600Z

you can't compile a form containing the symbol l4/init even if its behind a when form if the l4 alias isn't present

elton 2020-10-29T20:32:24.399800Z

yeah

dpsutton 2020-10-29T20:32:56.400400Z

but you are attempting to

elton 2020-10-29T20:33:15.400800Z

I’d like to, that way I can have it all in one startup.clj file

elton 2020-10-29T20:33:30.401300Z

and then say if I have multiple projects that use criterium, I can have those all load

elton 2020-10-29T20:33:37.401600Z

only when its avaialble

elton 2020-10-29T20:33:42.401900Z

if that makes sense

dpsutton 2020-10-29T20:33:57.402200Z

right. and that seems to be exactly the Gilardi scenario

elton 2020-10-29T20:34:28.402900Z

hmm okay, maybe i’ll take another look at it, thanks everyone

dpsutton 2020-10-29T20:35:23.403200Z

i think you could easily get around this with requiring-resolve

👍 1
seancorfield 2020-10-29T20:41:49.404400Z

^ I was about to suggest that.

seancorfield 2020-10-29T20:42:16.405200Z

Bear in mind that it will throw an exception if the namespace can't be required.

elton 2020-10-29T20:42:48.405900Z

Right right, because it would be a runtime thing and okay, after a second reading it totally makes sense now thanks again everyone

seancorfield 2020-10-29T20:43:08.406400Z

(when-let [foo (resolve 'l1/foo)]
  (defn custom-fn-1 (foo)))
I think that would work

👍 1
Darin Douglass 2020-10-29T20:43:35.406500Z

Steve, my man!

seancorfield 2020-10-29T20:45:11.407400Z

That way, it will only resolve if l1 is loaded. Well, if it's loaded and l1/foo resolves 🙂

borkdude 2020-10-29T20:50:52.407500Z

I could have sworn he was on Slack here as well as @sgilardi

joshkh 2020-10-29T22:18:27.410600Z

with the understanding that it's not the best practice and for good reason, is there a way to fetch the latest commit of a branch via deps.edn, rather than a specific sha?

seancorfield 2020-10-29T22:19:30.411100Z

@joshkh I have an example in my dot-clojure repo, if you've seen that?

joshkh 2020-10-29T22:19:59.411500Z

nope! searching now

seancorfield 2020-10-29T22:20:49.412200Z

(relies on the add-lib3 branch of t.d.a.)

joshkh 2020-10-29T22:26:14.412300Z

exactly what i was looking for. just to test my understanding, extra-deps brings in tda:add-lib3, which then handles the :add-libs key of deps.edn? nvm i misread the indentation

Darin Douglass 2020-10-29T22:39:51.412700Z

@scgilardi

Darin Douglass 2020-10-29T22:40:35.412900Z

i actually asked him (since i was pairing with him at the time) if "Gilardi scenario" was a coincidence. totally wasn't 😛

borkdude 2020-10-29T22:45:02.413100Z

why would it be a co-incidence?

Darin Douglass 2020-10-29T22:46:28.414Z

I figured given the community it’d be pretty unlikely it WASNT him. But still wanted to ask