This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-10-07
Channels
- # announcements (10)
- # architecture (25)
- # babashka (5)
- # beginners (95)
- # calva (1)
- # cider (3)
- # clerk (16)
- # clj-on-windows (41)
- # clojure (64)
- # clojure-europe (7)
- # clojurescript (9)
- # deps-new (2)
- # graalvm (25)
- # honeysql (3)
- # hyperfiddle (19)
- # malli (1)
- # meander (5)
- # music (1)
- # nbb (1)
- # off-topic (54)
- # rdf (10)
- # releases (2)
- # shadow-cljs (12)
- # tools-deps (41)
Tiny Clojure puzzler for you: Why does this work at all? And why is it doing what it's doing?
user=> (let [x 1] ((symbol "foo-bar-bat") 1 x))
1
Because, same as keywords, symbols have a [map default]
arity that gets a value from a map by symbol key and returns default if key is not found.
However, when I say "map", it can in fact be absolutely anything because RT.get
is very permissive, and if you pass something non-associative to it, it will act like it just couldn't find the supplied key in it.
@U06PNK4HG This was quite surprising to me this morning. :) Do you know where that arity is coming from?
Oh and to wrap up the mystery here's what I was actually trying to do. :)
user=> (defn x [] 1)
#'user/x
user=> ((ns-resolve *ns* (symbol "x")))
1
I'm pretty sure that at Clojure's inception, it was considered that symbols and keywords would be used equally as keys, so they got the same treatment. Nowadays it is apparent that almost nobody use symbols as general-purpose keys in maps, let alone calls them on maps. But the behavior is already there, can't undo that.
EDIT: never be too sure:)
They have different intended uses, but both are useful keys in a map, and thus both have the ability to look themselves up that way. Symbols are particularly useful map keys when doing symbolic stuff.
I'm using tons of symbolic-keyed maps in SCI (mostly to represent Clojure namespaces)
> I'm using tons of symbolic-keyed maps in SCI (mostly to represent Clojure namespaces)
That's why I said "general-purpose keys" which is perhaps my not very clear way of conveying keys that mean "programmer names" rather "entities". Put another way, I don't mean ns-name->namespace
maps, but maps like {'name "John", 'surname "Smith"}
, i never see the latter.
@U064X3EF3 Can't argue with the usefulness of symbols as keys, but I wonder how often the (a-symbol a-map)
construct is used. I personally cannot remember if I ever saw such invocation.
not sure if it is related, but Symbol.create does a Symbol.intern which does no interning at all and just creates a new Symbol. This Symbol intern is there from 2007.
I mean related to the original intention for symbols
I think one of the reasons for not interning is that symbols can mean different things in different contexts, unlike keywords and they can carry metadata, so they need to be in fact different objects some of the time. I wonder if interning symbols without any metadata, which might be very common, is an option and if there would be any benefits to that
yeah looks like meta is the reason https://ask.clojure.org/index.php/8540/why-are-symbols-not-cached-and-interned-the-keywords-clojure I just find interesting that intern is still there for Symbols
probably a dumb question since just came to my mind and I haven't think much about it, but why does every clojure object contains a reference to meta as part of the object itself instead of meta being tracked in a different map from reference to meta, which I guess would also allow adding meta to every object reference (like String, etc) while also reduce mem footprint since most objects doesn't contain meta
@U0739PUFQ It would not be a good solution at all. That map will either keep every object with metadata from being GCed, so you would have to clean it up every time the object is no longer needed, and that is incredibly messy and slow; or the map would have to contain weak/soft references and that is not reliably and also has its overhead.
> meta being tracked in a different map from reference to meta I guess lookup of meta being fast is another aspect, field vs map lookup. The meta being a field of an object makes more sense to me
oh yeah, collecting the meta oc
told you that was going to be a dumb question XD
No worries, it is good to ask those "wait, why not do X" questions to better understand why things are done in a certain way.
I'm using clj-http which uses the Apache HTTP client library behind the scenes. I'm on JDK 21 and have access to virtual threads. If I have a function that it making a single HTTP call by calling client/get
, do I gain anything by wrapping that in a virtual thread or should I only expect to see an improvement if I'm making multiple calls in parallel?
To get a benefit, you would have to wrap the code that waits for the result of client/get
in a virtual thread. Say
(.start (Thread/ofVirtual)
#(let [result @(client/get ...)]
(process-result result)))
Or, like you said, if you need to run two requests in parallel, then running then both in separate virtual thread is good. But, again, it is important that the surrounding code (that will block until both requests return) also run in a virtual thread.
> But, again, it is important that the surrounding code (that will block until both requests return) also run in a virtual thread. And if something is blocking and waiting on the result of that thread, does it also need to be a virtual thread? Is it "virtual threads all the way down"?
For the sake of keeping things simple – yes, you can't go wrong by replacing all platform threads with virtual threads. Loom creators argue that it's a misunderstanding of Loom to do that, but I have yet to see significant downsides to doing that (barring the issues with blocking in native code or I/O under locks). Disclaimer: I haven't used Loom in production applications yet, just experimenting for now, so take with a grain of salt.
Oh and to wrap up the mystery here's what I was actually trying to do. :)
user=> (defn x [] 1)
#'user/x
user=> ((ns-resolve *ns* (symbol "x")))
1
Random Q about alter-var-root
-- what sort of things do you use it for in real-world code? Answers in 🧵 please (I'm updating part of http://clojure-doc.org and it currently offers a fairly incomprehensible reason for using alter-var-root
).
I've never used it (I'm a noobie), but I think I've seen it used in dev code for keeping track of, say, a server instance or component system state. The rationale seemed to be that an atom was overkill and maybe sent the wrong message about the intended usage of that thing.
Like here: https://github.com/stuartsierra/component.repl/blob/master/src/com/stuartsierra/component/repl.clj
I use it most often to change the output printer used by clojure.spec.alpha/*explain-out*
. We also use it in some babashka scripts to stick in noop replacements for AWS calls for dry runs.
In our codebase we have a 'context' map dynamic var *ctx*
and with a bunch of system-wide config, and in released code we use (binding ...)
but in the REPL i use alter-var-root out of convinience to make the REPL environment a lot easier to use so you can invoke any function that depends on it. All the places where i've used it that I'm not embarrassed about the code, is generally constrained to source code that only runs in REPL/dev builds.
I have a couple variants of a utility that intercepts Datomic transactions and sends them into a core.async channel
@U050ECB92 care to share, that sounds very interesting?
It grabs txdata before the transaction occurs, and allows me to get a sense of the shapes of transactions in alien services at nubank
yah, that does seem like a really nice junction point to add metrics & monitoring, what var are you calling alter var root on?
Interesting, i just ended up wrapping d/transact , and all the code calls the wrapper, but maybe you don't have that luxury
I use it to dynamically enable debug tooling that is off by default
We used alter-var-root to store a system. Then we had posix signal listeners that would safely stop it on sigint, or restart on sughup, etc
I use it for patching some things in babashka, e.g. I patch clojure.pprint to make it not use find-var
which increases image size.
https://github.com/clojure/clojure/blob/6975553804b0f8da9e196e6fb97838ea4e153564/src/clj/clojure/pprint/pprint_base.clj#L148
Also see https://clojure.atlassian.net/browse/CLJ-2582
I also have this piece of code which detects runtime require
s by patching require
using alter-var-root to make it a macro, so I can see where it's being used at runtime already when the code has just been loaded, not even when it runs
https://github.com/babashka/babashka/blob/1dff46699aa4c53156ca26ab0287c2c49389c289/src/aaaa_this_has_to_be_first/because_patches.clj#L7-L43
I use it in one place to solve a cyclic dependency over many namespaces that was too much of a headache to solve properly.
EDIT: Nvm, I was actually using set!
Adding "advice" / aspect-orientated programming style wrappers to functions, like perf logging and the like
Thanks, folks! Keep them coming. It does seem that it's mostly for dev/debug, which is kind of what I expected.
(defonce state nil)
(defn- start-system []
(alter-var-root #'state
(fn [current-state]
(ig/init (ig/prep config)))))
(defn stop-system []
(when state
(alter-var-root #'state ig/halt!)))
It could be also atom
, but then I have to write @
everywhere. It is not real use case for atom, when state is changing and it makes code a little nicer without @ everywhere to use integrant. Small thing.I just used alter-var-root
as part of test fixtures for my logging library, so that I can rebind *err*
and have it direct to my own PrintWriter class.
This is useful because it ensures the root binding of *err*
is the same across all threads that deref the var, and my tests involve asynchronous logging.
oh and the not obvious fact can be I don’t use it anywhere else. At least I think so. For developing and tests I use different Integrant input and with-redefs.
I use it for things that change very rarely, specially initialization de-initialization stuff, and use atoms when things are supposed to be changing during the app execution. In part because it conveys some extra information about how to use the var and in part because you don't pollute everything with derefs for things that only change once
In reference to my answer above, I now checked the source code and see that I actually use alter-var-root!
. I had thought not, because I'm changing it to a constant value. It seems that (set! #'sql/log*! #'query-logger/log!)
does not work, whereas`(alter-var-root #'sql/log*! (constantly #'query-logger/log!))` does. Why is that?
The answer is in the docstrings:
user=> (doc set!)
-------------------------
set!
(set! var-symbol expr)
(set! (. instance-expr instanceFieldName-symbol) expr)
(set! (. Classname-symbol staticFieldName-symbol) expr)
Special Form
Used to set thread-local-bound vars, Java object instance
fields, and Java class static fields.
Please see
nil
user=> (doc alter-var-root)
-------------------------
clojure.core/alter-var-root
([v f & args])
Atomically alters the root binding of var v by applying f to its
current value plus any args
nil
user=>
The key here is "thread-local-bound". Which def
and defn
generally are not.user=> (def ^:dynamic foo nil)
#'user/foo
user=> (set! foo 42)
Execution error (IllegalStateException) at user/eval193 (REPL:1).
Can't change/establish root binding of: foo with set
user=> (binding [foo ""] (set! foo 42))
42
Awesome, thanks @U04V70XH6!