Fork me on GitHub
#clojure
<
2021-06-26
>
Stel Abrego02:06:17

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;
   = 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. 🤓

Nazral05:06:49

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)
;; => {:foo {:bar {:baz 1}}}

👏 3
Ben Sless06:06:31

Why nested maps over namespaced keywords?

Stel Abrego17:06:34

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

Stel Abrego17:06:12

@UK0810AQ2 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 Sless18:06:53

@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

👍 3
seancorfield07:06:17

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.

🙌 6
seancorfield07:06:40

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

Timofey Sitnikov13:06:03

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-himik13:06:25

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)))  

👍 6
3
dgb2313:06:49

@U2FRKM4TW 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.

reefersleep15:06:49

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

Timofey Sitnikov20:06:37

@U2FRKM4TW, that works well.

👍 3
timvisher16:06:05

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? :)

dpsutton16:06:55

This is an artifact of laziness right?

timvisher16:06:07

Ooo maybe? Lemme see. :)

dpsutton16:06:15

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

timvisher16:06:46

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

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

timvisher16:06:16

Actually LOL that looks exactly like it. xD

timvisher16:06:45

(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.)

dpsutton16:06:06

Rubber duck :)

timvisher16:06:15

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

timvisher16:06:54

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

timvisher16:06:05

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

timvisher16:06:14

I forgot that I had tried that.

timvisher17:06:11

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

timvisher17:06:13

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

Joshua Suskalo17:06:32

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

🔥 3
timvisher18:06:42

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

seancorfield18:06:54

Heh, except that such caveats would likely stay in place even if a repo is abandoned 🙂

seancorfield18:06:24

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

thanks3 3
timvisher18:06:23

It really is so good. :)

3
dgb2317:06:56

(< nil 1)

dgb2317:06:17

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

timvisher17:06:11

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

😄 3
Stel Abrego17:06:00

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.

Alex Miller (Clojure team)17:06:14

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

👍 3
Alex Miller (Clojure team)17:06:38

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

dgb2318:06:45

@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

👍 3
timvisher18:06:27

@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

👍 3
seancorfield18:06:06

@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).

👍 3
Jakub Šťastný02:06:42

Up to a minute and half? On what hardware?

seancorfield02:06:20

@U024VBA4FD5 90 seconds to start up a Clojure app from source is nothing... for a large app... because all the .clj files have to be compiled into memory.

seancorfield02:06:02

When that overhead is removed -- by AOT'ing code going into the uberjar -- then the services restart in seconds, not minutes.

Alex Miller (Clojure team)18:06:07

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)

👂 3
👍 3
seancorfield18:06:10

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 Sitnikov21:06:12

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?

Alex Miller (Clojure team)21:06:28

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

Alex Miller (Clojure team)21:06:01

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

Timofey Sitnikov21:06:47

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

Alex Miller (Clojure team)21:06:16

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

hiredman21:06:26

Clojure doesn't do any automatic parallelization

hiredman21:06:22

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

borkdude21:06:14

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

dgb2321:06:57

that looks a bit like a wait group

dgb2321:06:25

or a fan-in

dgb2321:06:49

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

dgb2321:06:11

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

borkdude21:06:43

it does yeah

dgb2321:06:37

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

dgb2321:06:34

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

borkdude21:06:43

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

dgb2321:06:18

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

dgb2321:06:43

or using macros and then (str ..)

borkdude21:06:33

sure, it could have been implemented differently

borkdude21:06:57

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

borkdude21:06:35

but you shouldn't probably rely on this

dgb2321:06:59

nono I’m just learning

borkdude21:06:07

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

dgb2321:06:27

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

dgb2321:06:03

thank you for clarifying!