clojure

flowthing 2025-09-23T19:19:54.872499Z

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)

2025-09-24T15:47:36.386159Z

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

2025-09-24T15:48:35.032839Z

This is what hiredman was referring to, and what the macroexpansion noisesmith showed happens.

2025-09-23T19:25:22.174559Z

If server is hinted then the intermediate bits may not need hinting

2025-09-23T19:27:13.781309Z

.. is also something I never use

flowthing 2025-09-23T19:27:29.329089Z

Good call! Looks like they do in this case, though. 😞

2025-09-23T19:28:01.093499Z

It predates -> and ->> if I recall, and the arrows are better it basically every way

2025-09-23T19:29:09.478739Z

(-> foo ^Bar (whatever) (.someBarMethod))

flowthing 2025-09-23T19:33:06.495709Z

Yeah, I hear ya. I'm trying to make a change that changes as little as possible here, though.

flowthing 2025-09-23T19:33:38.899949Z

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

flowthing 2025-09-23T19:34:45.188799Z

So (-> server ^QueuedThreadPool (.getThreadPool) .isDaemon) works, but (-> server ^QueuedThreadPool .getThreadPool .isDaemon) reflects.

2025-09-23T20:06:05.463929Z

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?

2025-09-23T20:13:18.285889Z

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

2025-09-23T20:15:22.985329Z

well, it isn't a method, it is a symbol

2025-09-23T20:15:52.876529Z

I mean that after reading, there's nothing for the metadata to stay on? I am probably misunderstanding

2025-09-23T20:16:08.491279Z

but it has to do with either how the macro or the compiler copy or list manipulation functions copy metadata around

2025-09-23T20:16:38.369579Z

I think the source metadata is a clue

2025-09-23T20:16:39.335599Z

thanks

2025-09-23T20:17:54.542769Z

and many how (.foo bar) gets rewritten as (. bar (foo)), but I forget if the compiler still bothers to desugar that

henrik 2025-09-26T13:15:16.858559Z

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

flowthing 2025-09-26T13:15:58.290079Z

Yes, I do that as well, but I specified "prior to Clojure 1.12" in my original question.

henrik 2025-09-26T13:16:24.246539Z

Yes you did, sorry.

flowthing 2025-09-26T13:17:09.306149Z

No worries, it's good to point that out for the benefit of future generations, if nothing else. 🙂

henrik 2025-09-26T13:19:21.880359Z

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

flowthing 2025-09-26T13:19:52.654449Z

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.