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.
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. π€@archibald.pontier_clo That's so neat! Thanks for taking the time to write that out
@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!
@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
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}}}
https://gist.github.com/Tyruiop/dc372ebc5ea4cbf6da50ef5000a79ed7
Why nested maps over namespaced keywords?
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)))
@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.
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? :)
This is an artifact of laziness right?
Ooo maybe? Lemme see. :)
Itβs a lazy seq realized by the printer. And when printing it blew up
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
Actually LOL that looks exactly like it. xD
Yeah :)
(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.)
Rubber duck :)
It's been awhile since I was bitten by laziness. :feelsgood:
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.
I forgot that I had tried that.
@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?
(< nil 1)
Yeah, I'm pretty sure rebel-readline has been in a good state for a long while. I use it all the time.
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
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 π
But, yes, Rebel Readline seems to be rock-solid and I use it every day too.
It really is so good. :)
@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).
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"]
@p-himik, that works well.
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
Clojure doesn't do any automatic parallelization
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
that looks a bit like a wait group
or a fan-in
but the expression in question doesnβt look like you could parallelise it at all
wait does this thing let you do arbitrary dependency graphs between the tasks? thatβs quite declarative
it does yeah
Iβm trying to read the code but I get confused as hell because some of it is in formatted strings!
are you essentially spitting it out and then interpreting it with sci?
https://github.com/babashka/babashka/blob/master/src/babashka/impl/tasks.clj
you can run bb --debug a
to see what it executes
yes
ahhhh
so meta
quoting the form and then turning it into a string was not feasible here?
?
or using macros and then (str ..)
sure, it could have been implemented differently
one benefit of doing it this way is that quotes in expressions still work, while '(+ 1 2 3)
isn't actually allowed in EDN
ah ok!
but you shouldn't probably rely on this
nono Iβm just learning
that code isn't terribly clean and educational probably, sorry ;)
haha
i mean you opted for the straight forward way that got you the result you wanted
thank you for clarifying!