Is there a way to type hint expressions like this (prior to Clojure 1.12) without extracting a let binding or a function?
(.. server getThreadPool isDaemon)(-> server ^QueuedThreadPool .getThreadPool .isDaemon)
This applies the type hint to the symbol .getThreadPool which does nothing because the special handling for .foo symbols to transform them into . special form calls don't use that metadata
(-> server ^QueuedThreadPool (.getThreadPool) .isDaemon)
This applies the type hint to the call to getThreadPool() and type hints on calls hint their return values, which means that you are correctly applying the type hint so that it will get threaded through to the later calls.
The parens around the first call aren't necessary to make a call, but they are necessary to ensure that the type hint is applied to the correct part of the result expression.This is what hiredman was referring to, and what the macroexpansion noisesmith showed happens.
https://gist.github.com/darkleaf/d5f8869b47a1a2688b05e154cbf40c42#file-core-clj-L17
If server is hinted then the intermediate bits may not need hinting
.. is also something I never use
Good call! Looks like they do in this case, though. 😞
It predates -> and ->> if I recall, and the arrows are better it basically every way
(-> foo ^Bar (whatever) (.someBarMethod))
Yeah, I hear ya. I'm trying to make a change that changes as little as possible here, though.
Thanks! I tried going with the arrow, but I didn't realize you need the parens in around the first form I'm threading through (whatever) .
So (-> server ^QueuedThreadPool (.getThreadPool) .isDaemon) works, but (-> server ^QueuedThreadPool .getThreadPool .isDaemon) reflects.
correct me if I am wrong, but that's because the metadata can't attach to a method but can attach to a list with a method inside?
(ins)user=> (binding [*print-meta* true]
(prn
(macroexpand
'(-> server ^QueuedThreadPool .getThreadPool .isDaemon))))
(. (^QueuedThreadPool .getThreadPool server) isDaemon)
nil
(ins)user=> (binding [*print-meta* true]
(prn
(macroexpand
'(-> server ^QueuedThreadPool (.getThreadPool) .isDaemon))))
(. ^{:line 4, :column 30, :tag QueuedThreadPool} (.getThreadPool server) isDaemon)
nilwell, it isn't a method, it is a symbol
I mean that after reading, there's nothing for the metadata to stay on? I am probably misunderstanding
but it has to do with either how the macro or the compiler copy or list manipulation functions copy metadata around
I think the source metadata is a clue
thanks
and many how (.foo bar) gets rewritten as (. bar (foo)), but I forget if the compiler still bothers to desugar that
I tend to do:
(-> (PutObjectRequest/builder)
(PutObjectRequest$Builder/.bucket bucket)
(PutObjectRequest$Builder/.key key))
Which is arguably unnecessary in a case like this. But accumulate enough interop in a namespace, go do something else for a couple of weeks, and then come back.
You’ll really appreciate the explicit classes, makes it easy to go look up docs. Especially in chains where the class transforms multiple times, like:
(-> something (.foo) (.bar) (.zoo))
vs.
(-> something (Hello/.foo) (World/.bar) (Monkey/.zoo))Yes, I do that as well, but I specified "prior to Clojure 1.12" in my original question.
Yes you did, sorry.
No worries, it's good to point that out for the benefit of future generations, if nothing else. 🙂
OK, if I were on a Clojure version prior to 1.12, I would first upgrade to 1.12, and then do the above.
Prior to 1.12, I used a lot of wrapper functions and let s to get around this. Eg., I would gather up some part of the chain into a function, and type hint the function like:
(defn get-monkey-zoo
^Zoo [^Something x] …)This was for Ring, so upgrading to 1.12 was not an option, but yes, if it is an option, definitely would also do that first.