Clojure overloaded method resolution for Longs

前端 未结 2 1373
迷失自我
迷失自我 2020-12-16 22:01

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=&g         


        
相关标签:
2条回答
  • 2020-12-16 22:19

    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)

    0 讨论(0)
  • 2020-12-16 22:26

    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))
    
    0 讨论(0)
提交回复
热议问题