Welcome! Please see the About page for a little more info on how this works.

+2 votes
in Compiler by

In the reference documentation on type hints (https://clojure.org/reference/java_interop#typehints), it states that function return types can be hinted by placing the type hint before the argument vector:
(defn hinted-single ^String [])

However, placing the hint before the var name itself appears to have the same effect:

(defn ^String hinted-var [])

eg. no reflection warnings are produced by the following:
(set! *warn-on-reflection* true) (fn [] (.length (hinted-var)))

Is this the correct and expected behavior? For instance it's used by most functions in clojure.string: https://github.com/clojure/clojure/blob/653b8465845a78ef7543e0a250078eea2d56b659/src/clj/clojure/string.clj#L48

2 Answers

+3 votes

Note that one of the examples on the documentation page you linked shows a multi-arity function definition with different return type hints on each arity. That is only possible if you put the return type hints on the argument vector.

(defn hinted
  (^String [])
  (^Integer [a])
  (^java.util.List [a & args]))

That page also mentions that type hints can be placed on "var names (when defined)", which is what you are seeing used several times in the clojure.string namespace.

One difference between type hints in these locations is that the compiler evaluates the metadata expressions when placed on a var name, as mentioned on this page of documentation https://clojure.org/reference/special_forms, but does not when placed on the arg vector. It probably does not evaluate it in other places, too, but I haven't done an exhaustive study of that.

The Eastwood lint tool documentation has a fair amount of details of what I believe are correct statements about useful and useless type hints in Clojure code in its documentation for the :wrong-tag linter here: https://github.com/jonase/eastwood#wrong-tag

also in the documentation of the :unused-meta-on-macro linter here: https://github.com/jonase/eastwood#unused-meta-on-macro

Thanks for the reply!

So does that mean it is officially correct behavior for a function return type to be inferred from its the tag on its var name?
While the Eastwood docs agree, it's not the same as an official language spec (which is why I'm asking on this forum)

The sentence you refer to in the clojure.org docs does not make it clear if hinting a _function_ var would hint its return type or the function object itself:

> They can be placed on function parameters, let-bound names, var names (when defined), and expressions

After all `defn` macroexpands to a `def` form, and placing a type hint on a `def` var refers to the type of value stored at the var.
So it seems strange and confusing for type hints on function vars to have two simultaneous interpretations, and for there to be multiple syntactical slots for hinting return types.
I cannot make official statements about Clojure.  There are about 3 people in the world who can.  I can tell you how Clojure behaves now, and has behaved for most or all of its existence, which is that type hints on a function var name do hint its return type, as you have found from experimentation.
The preferred (and less error-prone) location for these kinds of return type hints are on the arg signature, not the var.
Thanks for the clarification!
I also just realised there was a related question dating back to 2009 about the same issue:
0 votes
edited by

When type hinting the return type of a function declared with defn, Clojure supports two valid locations:

On the var:

(defn ^String hello []

Or on the arg-vector:

(defn hello ^String []

That said, the former is never preferred over the latter. That is, you should really be type hinting return values in the arg vector for the following reasons:

  1. When you type hint on the var, the hint gets evaluated. So you can't type int primitives, because ^long will resolve to the long function, not the type. So for primitive type hints this won't actually work.

  2. If you have a multi-arity function where different arities return different types, only the latter syntax will allow you to properly hint each one.

  3. In the presence of both hints, the arg vector one will be used.

  4. A hint on the var is ambiguous to the programmer reading the code, are you hinting the function return value or the value in the var (which will be a function in this case). Clojure will use it as a hint to the return value, but it can be confusing since for def the hint is about the value in the var, where as for defn the hint will be about the return value of the function in the var.

For these reasons I'd recommend type hinting the return value by adding a hint to the arg vector always, even though both will work.

You can still type hint primitives on the var, just not with that syntax:

(def ^{:tag 'long} b 1)
So, why have both ways?
Sometimes it's necessary to have the type hint come from an evaluated expression. And I'm pretty sure the return type hint on the var came first, and the type hint on the arglist was added later, and Clojure pretty much never removes anything so that no one's code gets broken by updates.