^char type hint not permitted for clojure defn parameter
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 (ie 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.
This is based on the comments from @A. Webb and @kotarak, as far as I can understand them.
There are two aspects to this: first, why the ^char
is available in some contexts (eg in for
)? This, is necessary not only for optimizations, but for correct Java interop as your example shows. Also, it looks (to me) relatively cheap to implement, as each variable is independent, so it can be processed by itself.
This is not the case with function definition, where for each combination of supported type you have to define a new interface: eg
static public interface L{long invokePrim();}
static public interface D{double invokePrim();}
static public interface OL{long invokePrim(Object arg0);}
// ...
static public interface OLD{double invokePrim(Object arg0, long arg1);}
// all the way to
static public interface DDDDD{double invokePrim(double arg0, double arg1, double arg2, double arg3);}
Each new supported type would add a lot of new interfaces (the exponential growth). That's why only the most encompassing primitive types are supported: long
and double
.
上一篇: 将数组转换为.ini文件