clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
seancorfield 2021-06-26T00:45:06.463400Z

What I do is use linuxbrew (HomeBrew for Linux) so I can use the regular clojure/tools/clojure tap and install any version I want, as well as easily upgrading or downgrading.

πŸ‘ 1
1
Stel Abrego 2021-06-26T02:23:17.466100Z

Random thought, I was just writing some comments in a map and I started missing Nix's language feature for nested map names:

{
  # A comment about this value
  foo.bar = true;

  # A comment about these two values
  boom.bam.woop = 4;
  <http://foo.biz|foo.biz> = false;
}
So you can separate out the nested maps. I don't know if that's practical in a lisp, or if there's some neat reader macro that could replicate that. But, yeah. Random thought. πŸ€“

Stel Abrego 2021-06-26T17:08:34.489100Z

@archibald.pontier_clo That's so neat! Thanks for taking the time to write that out

Stel Abrego 2021-06-26T17:13:12.490Z

@ben.sless That's a very good question.. Haha. Wow I guess if you're not planning on passing around the nested maps, using namespaced keywords is totally a solution. Reminds me of the datomic model, I think? I just started learning about Datomic on the most recent episode of the cognicast. Avoiding nested entities by using namespaced values when possible. Cool!

Ben Sless 2021-06-26T18:13:53.498500Z

@stelabrego semantically, I see no difference between the sugar-ed nested map syntax you shared and namespaced keywords. The "difficulty" with the sugared syntax is that you write the map flatly but access it in a nested manner. I also can't tell apart a value which just happens to have . in it vs a nesting separator. Also, you can add a reader tag to read map literals that way, but it seems like it adds more complexity than it solves, imo. Flat maps are simpler

πŸ‘ 1
Nazral 2021-06-26T05:50:49.466800Z

I wouldn't necessarily do it with a macro, but if you really want you could do something like

(defmacro parse-symbol
  [s v]
  (let [n `(name ~s)
        lvls `(map keyword (str/split ~n #"\."))]
    `(assoc-in {} ~lvls ~v)))

(parse-symbol 'foo.bar.baz 1)
;; =&gt; {:foo {:bar {:baz 1}}}

πŸ‘ 1
Ben Sless 2021-06-26T06:19:31.467300Z

Why nested maps over namespaced keywords?

seancorfield 2021-06-26T07:52:17.472600Z

This stuff belongs in #off-topic @ps @dromar56 @qmstuart since it is not about Clojure -- it is about silly rankings that silly websites come up with.

πŸ™Œ 2
seancorfield 2021-06-26T07:52:40.473Z

Feel free to re-post there but I'm deleting it from here. It's drivel.

Timofey Sitnikov 2021-06-26T13:02:03.479200Z

Hello Clojurians, looking for some advice. I love the (if-let ... , but it only discriminates nil and non-nil. In the case where there are layers of (if-let ... how can I pass back due to which (if-let... something did not happen? For example, when trying to reset password, a token can be expired or email does not exist. Should I use throw catch as part of the code to read off the reason for the error and show it back to the user? Or is there a better way of doing it?

p-himik 2021-06-26T13:05:25.479300Z

if-let will also trigger the else branch on false. If you need to only handle nil, use if-some instead. I usually use cond for this:

(let [error (cond
              (token/expired? token)
              "The token is expired, please try again."

              (not (email/valid? email))
              "The email is not valid.")]
  (if error
    (do-something-to error)
    (continue-normal-process)))  

πŸ‘ 2
1
dgb23 2021-06-26T13:12:49.479500Z

@p-himik I really like this. lt puts the errors clearly at the top and implies that the branching at the bottom doesn’t necessarily have to know which error occurred so a generic solution would handle it.

reefersleep 2021-06-26T15:42:49.482Z

Related: a small lib that I made for fun. Maybe you can use the pattern. https://github.com/Reefersleep/thread-until

2021-06-26T16:43:05.482400Z

Does this seem like a bug to anyone else? I wouldn't expect an NPE while printing (IIUC) in this scenario. A stack trace pointing at the line where the NPE occurred would seem more reasonable. But maybe I'm missing something? :)

dpsutton 2021-06-26T16:44:55.483Z

This is an artifact of laziness right?

2021-06-26T16:45:07.483600Z

Ooo maybe? Lemme see. :)

dpsutton 2021-06-26T16:45:15.484Z

It’s a lazy seq realized by the printer. And when printing it blew up

2021-06-26T16:46:46.484400Z

Doesn't look like it to my eyes initially at least

user=&gt;
(dorun (map (partial &lt; 1000) (conj (into [] (range 1000 1002)) nil)))
Execution error (NullPointerException) at user/eval152 (REPL:1).
null
(doall (map (partial &lt; 1000) (conj (into [] (range 1000 1002)) nil)))
Execution error (NullPointerException) at user/eval154 (REPL:1).
null

2021-06-26T16:47:16.484800Z

Actually LOL that looks exactly like it. xD

dpsutton 2021-06-26T16:47:22.485Z

Yeah :)

2021-06-26T16:47:45.485500Z

(Sorry I've been debugging what's happening here for the past half an hour. I've gone a bit cross-eyed it would seem.)

dpsutton 2021-06-26T16:48:06.485900Z

Rubber duck :)

2021-06-26T16:48:15.486100Z

It's been awhile since I was bitten by laziness. :feelsgood:

2021-06-26T16:48:54.486700Z

I went from CIDER β†’ REBL β†’ clj with a brief flirtation with monroe to get to the bottom of this. LOL

2021-06-26T16:51:05.487400Z

Aaaaah so I should forgive myself a little bit. In CIDER the doall makes no difference. I still just get an opaque NPE.

2021-06-26T16:52:14.487600Z

I forgot that I had tried that.

2021-06-26T17:02:11.488100Z

@dpsutton Thanks for your help! Moved the discussion over to #cider :) https://clojurians.slack.com/archives/C0617A8PQ/p1624726767180000

2021-06-26T17:05:13.488800Z

Ooooo boy it looks like rebel-readline hasn't been updated in awhile. :) Is this classic clojure where lack of activity != lack of stability?

dgb23 2021-06-26T17:26:56.491500Z

(&lt; nil 1)

2021-06-26T17:28:32.492100Z

Yeah, I'm pretty sure rebel-readline has been in a good state for a long while. I use it all the time.

πŸ”₯ 1
dgb23 2021-06-26T17:31:17.492300Z

clj-kondo for example catches the simple case but not with (map (partial &lt; 1) [1 2 nil])

2021-06-26T17:32:11.492500Z

Heh. What can static analysis do against such reckless first-class functionalism. Β―\(ツ)/Β―

πŸ˜„ 1
Stel Abrego 2021-06-26T17:44:00.495100Z

Hey Clojurians, I'm fairly new to Clojure deployment. Is their a performance advantage from running via uberjar or is a clj -X:start-app just fine? I'm not worried about the startup time.

alexmiller 2021-06-26T17:45:14.496100Z

I think the start mode there doesn't matter, but whether either is compiled will have an effect

πŸ‘ 1
alexmiller 2021-06-26T17:46:38.497Z

-X is really just a small shim to call a function

dgb23 2021-06-26T18:00:45.497900Z

@stelabrego if you are doing webdev you might want to poke around the luminus docs about deployment to get a bit of an idea of what your options are: https://luminusweb.com/docs/deployment.html

πŸ‘ 1
2021-06-26T18:17:27.001100Z

@stelabrego I've been running fairly large scale webservices for years using uberjars and AoT-ed mains (i.e. the rest of the source code is just added to the jar as source). I've been out of the community for a few years now so take what I say with a grain of salt but I feel like the common wisdom that's held up at least in my experience is that you want to avoid AoT unless absolutely necessary. There isn't even really a reason to AoT main in the case of a long running service because you can just java -classpath uberjar.jar -mclojure.main main-ns or whatever and while you may pay with an additional second or two you're still not meaningfully increasing the startup time of the app. That's worked well for me at least. Welcome to the party! :D

πŸ‘ 1
2021-06-26T18:18:42.001300Z

All clojure project readmes need a caveat at the top that somehow expresses that stability is a positive here vs. a sign of abandonment. :D

seancorfield 2021-06-26T18:21:54.001500Z

Heh, except that such caveats would likely stay in place even if a repo is abandoned πŸ™‚

seancorfield 2021-06-26T18:22:24.001700Z

But, yes, Rebel Readline seems to be rock-solid and I use it every day too.

1
2021-06-26T18:23:23.002Z

It really is so good. :)

1
seancorfield 2021-06-26T18:29:06.007300Z

@stelabrego At work, we use AOT'd uberjars for deployment but we only added AOT because startup time was getting long (up to a minute and a half) on a couple of processes and we wanted our rolling deploy/restart cycles to be faster than that. We've used uberjars for a long time because it's convenient to build those in CI and then deploy them out to production, rather than having to deal with source code on production servers: each uberjar can be completely self-contained so we can release each one independently without worrying about sync'ing changes across multiple processes if we deploy source (we used to deploy source and it was fine until our systems got big and complex). That said, we do also have the Clojure CLI installed on every server in case we need to get on and tinker with stuff (with just a minimal amount of source to support "build"-related stuff -- we can always use uberjars as :local/root deps if we need code from an app).

πŸ‘ 1
alexmiller 2021-06-26T18:32:07.008700Z

there are good reasons to AOT and use direct linking on the final application (perf, reduced code size so faster load, etc). there are good reasons not to AOT when publishing libraries (bakes in choice of external dep versions, clojure compiler version/api)

πŸ‘ 1
πŸ‘‚ 1
seancorfield 2021-06-26T18:58:10.010Z

Good point about direct linking, yes. We also do that when building AOT'd uberjars for deployment: :jvm-opts ["-Dclojure.compiler.direct-linking=true"]

Timofey Sitnikov 2021-06-26T20:59:37.012400Z

@p-himik, that works well.

πŸ‘ 1
Timofey Sitnikov 2021-06-26T21:01:12.013800Z

Have been struggling with this:

(let [a (super-complex-calculations-takes-10-seconds)
      b (+ a  1]
  (prn b))
Assuming multi-threaded process, can the b be calculated before the solution for a is completed?

alexmiller 2021-06-26T21:04:28.014600Z

not like that, but you could use future to background it

alexmiller 2021-06-26T21:05:01.015300Z

I mean b depends on a, so not sure how you can print b before a is done?

Timofey Sitnikov 2021-06-26T21:05:47.016Z

So there is a guarantee that multi-threaded process will wait for a before calculating for b ?

alexmiller 2021-06-26T21:06:16.017100Z

yes, almost all Clojure code is sequential in a single thread by default

2021-06-26T21:06:26.017500Z

Clojure doesn't do any automatic parallelization

2021-06-26T21:07:22.018800Z

Unless you explicitly use constructs that execute things on another thread, it is all on whatever thread you are on

borkdude 2021-06-26T21:10:14.019300Z

babashka tasks has something like this:

{:tasks {c (do (Thread/sleep 500)
               (println "c is done")
               11)
         b (do (Thread/sleep 1000)
               (println "b is done")
               42)
         a {:depends [c b]
            :task (prn (+ 10 c b))}}}
$ time bb a
c is done
b is done
63
bb a   0.02s  user 0.02s system 2% cpu 1.549 total

$ time bb run --parallel a
c is done
b is done
63
bb run --parallel a   0.02s  user 0.02s system 3% cpu 1.048 total

dgb23 2021-06-26T21:10:57.019600Z

that looks a bit like a wait group

dgb23 2021-06-26T21:11:25.019800Z

or a fan-in

dgb23 2021-06-26T21:12:49.020500Z

but the expression in question doesn’t look like you could parallelise it at all

dgb23 2021-06-26T21:19:11.023600Z

wait does this thing let you do arbitrary dependency graphs between the tasks? that’s quite declarative

borkdude 2021-06-26T21:19:43.023800Z

it does yeah

dgb23 2021-06-26T21:27:37.023900Z

I’m trying to read the code but I get confused as hell because some of it is in formatted strings!

dgb23 2021-06-26T21:28:34.024100Z

are you essentially spitting it out and then interpreting it with sci?

borkdude 2021-06-26T21:28:43.024600Z

you can run bb --debug a to see what it executes

borkdude 2021-06-26T21:28:52.024800Z

yes

dgb23 2021-06-26T21:28:55.025Z

ahhhh

dgb23 2021-06-26T21:28:59.025200Z

so meta

dgb23 2021-06-26T21:37:18.025400Z

quoting the form and then turning it into a string was not feasible here?

borkdude 2021-06-26T21:37:58.025600Z

?

dgb23 2021-06-26T21:38:43.025800Z

or using macros and then (str ..)

borkdude 2021-06-26T21:41:33.026Z

sure, it could have been implemented differently

borkdude 2021-06-26T21:41:57.026200Z

one benefit of doing it this way is that quotes in expressions still work, while '(+ 1 2 3) isn't actually allowed in EDN

dgb23 2021-06-26T21:41:59.026400Z

ah ok!

borkdude 2021-06-26T21:42:35.026600Z

but you shouldn't probably rely on this

dgb23 2021-06-26T21:42:59.026800Z

nono I’m just learning

borkdude 2021-06-26T21:44:07.027Z

that code isn't terribly clean and educational probably, sorry ;)

dgb23 2021-06-26T21:44:13.027200Z

haha

dgb23 2021-06-26T21:44:27.027400Z

i mean you opted for the straight forward way that got you the result you wanted

dgb23 2021-06-26T21:45:03.027600Z

thank you for clarifying!