That’s because:
Type hints are metadata tags placed on symbols or expressions that are consumed by the compiler. They can be placed on function parameters, let-bound names, var names (when defined), and expressions
You can not type hint values such as nil
and true
. That’s why you need to wrap the value in something that can be hinted so the compiler can use the hint.
That’s correct. The boolean
function returns a primitive boolean, not a Boolean. The literals true
and false
return a Boolean.
Alright, I’ll explain, but it gets hairy, even I get confused explaining it .
Functions in Clojure are always created with type Object for their parameters. Type hinting does not change that (except for long, double and arrays). Thus the type
function takes Object as an argument, and no overload exist which takes a primitive.
When Clojure invokes the type
function, it will see that only an Object overload exists, and it will thus perform auto-boxing of the primitive before calling it.
Even if there was an overload for type
which took a primitive, the implementation of it calls .getClass
on the argument. Clojure also performs auto-boxing when method instances are used on primitives. In such scenario, the imaginary type
which accepts a primitive would be called, but prior to calling .getClass
on the primitive argument, Clojure would also perform auto-boxing.
Now, something even more confusing is that primitives do not have types at runtime. The memory only contains the value, no additional meta-data. So you can’t introspect a primitive at runtime, for example, to ask it what type it is. The only way is to do it at compile time, and in Clojure I only know that recur will type check for primitives at compile time:
(loop [a (boolean true) i 0]
(when (zero? i)
(let [a true]
(recur a (inc 0)))))
;=> a is not matching primitive, had: java.lang.Boolean, needed: boolean
;; Because true is Boolean, but if we corce to boolean it works:
(loop [a (boolean true) i 0]
(when (zero? i)
(let [a (boolean true)]
(recur a (inc 0)))))
;=> nil
Now in your case, you have a method which can take Object, Object
or boolean, boolean
. Type hinting or passing a primitive to any of them will allow Clojure to figure out which one to call. In the case of a conflict, like you pass a boolean, Boolean
, normally Java would fail to compile that since it is ambiguous, but Clojure prioritizes the primitive, and will unbox the Boolean automatically for us. So you only need to coerce or type hint one of them.
Another thing is, type hinting is not the same as coercing. A type hint of ^boolean as so:
(let [^boolean a true
^boolean b true]
(java.awt.font.FontRenderContext. nil a b))
Results in:
final Object a = Boolean.TRUE;
final Object b = Boolean.TRUE;
return new FontRenderContext(null, (boolean)a, (boolean)b);
So the binding a and b are of type Object, but will be casted to boolean if there is an overload of that type. While:
(let [a (boolean true)
b (boolean true)]
(java.awt.font.FontRenderContext. nil a b))
Creates a binding of type boolean as such:
final boolean a = RT.booleanCast(Boolean.TRUE);
final boolean b = RT.booleanCast(Boolean.TRUE);
return new FontRenderContext(null, a, b);
This is only true in certain local context though. Like I said before, Clojure does not allow you to create functions with parameters of type boolean, but let
and loop
will allow it.
Alright, I’m done for today
If you want to learn more about this stuff, I recommend reading this: Clojure - Java Interop especially the later stuff on coercions and optimizations. And make heavy use of clj-java-decompiler.core/decompile
from GitHub - clojure-goes-fast/clj-java-decompiler: REPL-integrated Clojure-to-Java decompiler in your REPL session. I have it loaded by default in all my REPLs, never know when I’ll need it.