Observe the following repl session:
user=> (set! *warn-on-reflection* true)
true
user=> (defn blah [s] (for [c s] (if (Character/isDigit c) true false)))
Reflection warning, NO_SOURCE_PATH:1:31 - call to isDigit can't be resolved.
Reflection warning, NO_SOURCE_PATH:1:31 - call to isDigit can't be resolved.
#'user/blah
user=> (blah "abc123abc")
(false false false true true true false false false)
user=> (defn blah [s] (for [^char c s] (if (Character/isDigit c) true false)))
#'user/blah
user=> (blah "abc123abc")
(false false false true true true false false false)
So we used a type hint of ^char
to get rid of reflection - great. Now try the same thing in a function parameter:
user=> (defn blah-c [c] (if (Character/isDigit c) true false))
Reflection warning, NO_SOURCE_PATH:1:22 - call to isDigit can't be resolved.
#'user/blah-c
user=> (defn blah-c [^char c] (if (Character/isDigit c) true false))
CompilerException java.lang.IllegalArgumentException: Only long and double primitives are supported, compiling:(NO_SOURCE_PATH:1:1)
user=> (defn blah-c [^Character c] (if (Character/isDigit c) true false))
#'user/blah-c
user=> (blah-c \1)
true
user=> (blah-c \a)
false
I understand that Clojure only supports long or double type hints for numeric primitives, and that a Java char
is a numeric data type - no need to explain that. But the above seems inconsistent - type hinting ^char
is allowed in the first function inside the for
, but not in the function signature of blah-c
, where I had to specify Character
. What the reason for this (i.e. from the compiler implementation perspective)?
In the type-hinted for
expression you are tagging c
as a char
as a hint to the compiler. When the compiler emits the (static) method for isDigit
it then knows you want the version accepting a char
(as opposed to possibly the int
version). The byte code is emitted into a function object implementing the O
(single Object
argument) version of the IFn
interface (all arguments are boxed by default).
In the other case, blah-c
, the byte code would need to be emitted to a function object implementing a non-existent C
(for example, for char
) version of the IFn
interface. Could there be interfaces for each primitive? Sure, but there is not. For each possible combination? Not feasible, due to combinatorial explosion.
You could say, well, why not just emit blah-c
to an O
interface? This would defeat the point of the type hint on the function argument, which is to avoid boxing/unboxing, as the character primitive would then have to be boxed to make the call. The point of type hints on function arguments is not merely to avoid reflection. If you want to avoid reflection here, then you would not tag the function argument but instead coerce it into a char
in a let
block before making the isDigit
call.
Note in clojure.lang.IFn, the enumerated interfaces are (currently) limited to any number of Objects (boxed type) and up to four combinations of double
and long
types. The double
and long
versions are provided as an optimization to avoid boxing/unboxing when writing performance critical code on primitives and should be enough for most purposes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With