Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure overloaded method resolution for Longs

Tags:

types

clojure

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?


Update:

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
like image 591
DaoWen Avatar asked Sep 25 '12 16:09

DaoWen


2 Answers

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)

like image 59
noahlz Avatar answered Oct 21 '22 15:10

noahlz


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))
like image 36
Arthur Ulfeldt Avatar answered Oct 21 '22 16:10

Arthur Ulfeldt