Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

^char type hint not permitted for clojure defn parameter

Tags:

clojure

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

like image 392
noahlz Avatar asked Jun 26 '13 15:06

noahlz


1 Answers

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.

like image 84
A. Webb Avatar answered Sep 22 '22 12:09

A. Webb