This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-06-26
Channels
- # announcements (6)
- # aws (5)
- # beginners (42)
- # cider (24)
- # clojure (81)
- # clojure-canada (1)
- # clojure-europe (122)
- # clojured (5)
- # cursive (6)
- # data-science (5)
- # datahike (9)
- # emacs (6)
- # events (2)
- # fulcro (12)
- # helix (4)
- # honeysql (1)
- # jobs-discuss (1)
- # lsp (28)
- # mid-cities-meetup (1)
- # off-topic (31)
- # polylith (13)
- # shadow-cljs (22)
- # spacemacs (8)
- # tools-deps (33)
- # vim (10)
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. 🤓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}}}
@UGH9KH1DF That's so neat! Thanks for taking the time to write that out
@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!
@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
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.
Feel free to re-post there but I'm deleting it from here. It's drivel.
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?
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)))
@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.
Related: a small lib that I made for fun. Maybe you can use the pattern. https://github.com/Reefersleep/thread-until
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? :)
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
(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.)
I went from CIDER → REBL → clj with a brief flirtation with monroe to get to the bottom of this. LOL
Aaaaah so I should forgive myself a little bit. In CIDER the doall makes no difference. I still just get an opaque NPE.
@dpsutton Thanks for your help! Moved the discussion over to #cider :) https://clojurians.slack.com/archives/C0617A8PQ/p1624726767180000
Ooooo boy it looks like rebel-readline hasn't been updated in awhile. :) Is this classic clojure where lack of activity != lack of stability?
Yeah, I'm pretty sure rebel-readline has been in a good state for a long while. I use it all the time.
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
Heh, except that such caveats would likely stay in place even if a repo is abandoned 🙂
clj-kondo for example catches the simple case but not with (map (partial < 1) [1 2 nil])
Heh. What can static analysis do against such reckless first-class functionalism. ¯\(ツ)/¯
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.
I think the start mode there doesn't matter, but whether either is compiled will have an effect
-X is really just a small shim to call a function
@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
@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
@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).
Up to a minute and half? On what hardware?
@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.
When that overhead is removed -- by AOT'ing code going into the uberjar -- then the services restart in seconds, not minutes.
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)
Good point about direct linking, yes. We also do that when building AOT'd uberjars for deployment: :jvm-opts ["-Dclojure.compiler.direct-linking=true"]
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?not like that, but you could use future
to background it
I mean b depends on a, so not sure how you can print b before a is done?
So there is a guarantee that multi-threaded process will wait for a
before calculating for b
?
yes, almost all Clojure code is sequential in a single thread by default
Unless you explicitly use constructs that execute things on another thread, it is all on whatever thread you are on
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
wait does this thing let you do arbitrary dependency graphs between the tasks? that’s quite declarative
I’m trying to read the code but I get confused as hell because some of it is in formatted strings!