This behavior makes no sense to me:
user=> (type 1)
java.lang.Long
user=> (type (cast Long 1))
java.lang.Long
user=> (type 1)
java.lang.Long
user=> (type (Long. 1))
java.lang.Long
user=> (type (cast Long 1))
java.lang.Long
user=> (BigDecimal. 1)
1M
user=> (BigDecimal. (Long. 1))
CompilerException java.lang.IllegalArgumentException: More than one matching method found: java.math.BigDecimal, compiling:(NO_SOURCE_PATH:22)
user=> (BigDecimal. (cast Long 1))
1M
Why does the (BigDecimal. (Long. 1))
case fail to find an unambiguous matching method signature while the other two expressions—which have exactly the same argument type—succeed?
What I find even more strange about this behavior is that it seems particular to the Long
type:
user=> (BigDecimal. (Long. 1))
CompilerException java.lang.IllegalArgumentException: More than one matching method found: java.math.BigDecimal, compiling:(NO_SOURCE_PATH:1)
user=> (BigDecimal. (Integer. 1))
1M
From this discussion on the Clojure discussion group it seems you have encountered a design decision made by Rich Hickey. Specifically, because BigDecimal does not have a constructor of the signature BigDecimal(Long long)
(Edit: and therefore leaves the compiler having to choose between the int
and long
constructors - see comments below for discussion on why using Integer
works), the compiler will not attempt to "guess" which constructor you will meant, and explicitly fails.
The bottom line is specific type requirements on the Java side require explicit boxing on order to have correct and non-brittle code. - Rich Hickey
Note that literals are parsed as primitives, not "boxed" types, per this documentation:
In contrast to previous versions of Clojure, numeric literals are parsed as primitive longs or doubles.
To understand why the other operations work, you have to dig into Clojure source, specifically Compiler.java
and its inner class NumberExpr
. That is the where your literal gets auto-boxed to a Long
and the compiler has no problem in turn calling Object.getClass()
(which both type
and class
do).
In the Compiler.getMatchingParams()
, the Clojure compiler attempts to resolve which constructor of BigDecimal
to use. However, you have explicitly specified that your parameter has the type Long
- there is no constructor for BigDecimal that takes that type.
Maybe this isn't "common sense," but Rich Hickey made the decision that you need to be precise about the type of your parameters and that they have to match the type of the Java class. The compiler is refusing to guess your intent.
Note the following:
user=> (new BigDecimal 1M)
Reflection warning, NO_SOURCE_PATH:33 - call to java.math.BigDecimal ctor can't be resolved.
IllegalArgumentException No matching ctor found for class java.math.BigDecimal clojure.lang.Reflector.invokeConstructor (Reflector.java:183)
Also note that this Java code is valid and resolves to the int
constructor for BigDecimal:
byte b = 1;
new BigDecimal(new Byte(b));
But this code also fails (even though it "should" use the int
constructor):
user=> (BigDecimal. (Byte. (byte 1)))
Reflection warning, NO_SOURCE_PATH:37 - call to java.math.BigDecimal ctor can't be resolved.
IllegalArgumentException No matching ctor found for class java.math.BigDecimal clojure.lang.Reflector.invokeConstructor (Reflector.java:183)
tl;dr: Clojure supports Java interop but that does not mean it has to follow the promotion rules of Java Language Specification.
What about cast
?
A comment below asks about (cast)
. In that case you are explicitly telling the Clojure compiler to delegate type resolution to the JVM. Note the following (nonsensical) code that compiles, yet fails at runtime:
user=> (set! *warn-on-reflection* true)
true
user=> (defn make-big-dec [obj] (BigDecimal. (cast Math obj)))
Reflection warning, NO_SOURCE_PATH:7 - call to java.math.BigDecimal ctor can't be resolved.
#'user/make-big-dec
user=> (make-big-dec 1)
ClassCastException java.lang.Class.cast (Class.java:2990)
Epilogue II
There has been quite a bit of discussion on this topic in the Clojure community. Please check out these detailed threads:
Enhanced Primitive Support (Rich Hickey)
Clojure 1.3 treatment of integers and longs (Nathan Marz)
BigDecimal does not have a constructor for Long,
BigDecimal(BigInteger val)
core> (BigDecimal. (BigInteger/ONE))
1M
BigDecimal(BigInteger unscaledVal, int scale)
core> (BigDecimal. BigInteger/ONE 1)
0.1M
BigDecimal(double val)
core> (BigDecimal. (double 1))
1M
core> (BigDecimal. (float 1))
1M
(BigDecimal. Double/MIN_VALUE)
BigDecimal(String val)
core> (BigDecimal. "1")
1M
It's unclear which of these (Long. 1)
matches. the clojure.core.bigdec
function works on this input by passing it's input to BigDec/valueOf
to create the BigDecimal
core> (bigdec (Long. 1))
1M
uses this call:
(BigDecimal/valueOf (long x))
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