I’m unsure how the new method values stuff maintains backwards compatibility with the older static method invocations. e.g. (Long/toString 0) still works, but (String/toLowerCase "Foo") does not without the type hint: (^[] String/toLowerCase "Foo"). Is there still a specific path for static method references in invocation position?
@cfleming the feedback on the uniform method selection not using type flow information was helpful, thanks. we're incorporating that into next iteration.
Great news, thanks Alex.
That's an instance method, not a static method.
(~/clojure)-(!2001)-> clj
Clojure 1.12.0-alpha7
user=> (String/valueOf 1.23) ; static method
"1.23"
user=> (.toLowerCase "Hello World!") ; instance method
"hello world!"
user=> (String/toLowerCase "Hello World!")
Syntax error (IllegalArgumentException) compiling at (REPL:1:1).
Multiple matches for method toLowerCase in class java.lang.String, use param-tags to specify
user=> (^[] String/toLowerCase "Hello World!")
"hello world!"
user=>
Sat Feb 10 15:31:18
(~/clojure)-(!2001)-> clj -A:1.11
Clojure 1.11.1
user=> (String/valueOf 1.23) ; static method (same)
"1.23"
user=> (.toLowerCase "Hello World!") ; instance method (same)
"hello world!"
user=> (String/toLowerCase "Hello World!") ; not supported in 1.11
Syntax error (IllegalArgumentException) compiling . at (REPL:1:1).
No matching method toLowerCase found taking 1 args for class java.lang.String
user=> (^[] String/toLowerCase "Hello World!") ; not supported in 1.11
Syntax error reading source at (REPL:4:5).
Metadata must be Symbol,Keyword,String or Map
Syntax error compiling at (REPL:0:0).
Unable to find static field: toLowerCase in class java.lang.String
"Hello World!"
Syntax error reading source at (REPL:4:40).
Unmatched delimiter: )
user=>Yes, I know. However, the overload resolution is clearly different in invocation position for static methods, and I just wanted to clarify that that’s the intended behaviour.
Do you have an example of that change?
I picked String/valueOf because it is static and has multiple overloads...
Yes, above - (Long/toString) (static method) works without a hint, even though there’s an overload for toString taking a radix.
So... that's the same between 1.11 and 1.12, yes?
Right.
My question is: I’m assuming that there’s an explicit path which causes the overload resolution to be different for static and instance methods in invocation position, and I’m just wanting confirmation that that is indeed the case, or to understand what I’m seeing if it’s not.
Ah, that's not what I thought you were asking, sorry. So you're asking if it's deliberate that static method invocation hasn't changed and still does overload resolution (and, presumably, reflection at compile-time?) whereas a similar-looking instance method invocation does not do overload resolution (but, presumably, still has to do reflection at compile-time to determine that it is a) an instance method b) has multiple overloads)...
Yes, I’m assuming that static invocations still use arity and argument types to disambiguate.
When you use a static method in a value position, you have to hint it:
user=> (map Long/toString (range 5))
Syntax error (IllegalArgumentException) compiling fn* at (REPL:1:1).
Multiple matches for method toString in class java.lang.Long, use param-tags to specify
user=> (map ^[] Long/toString (range 5))
("0" "1" "2" "3" "4")
So it does seem that invocation position is special and still does arity/type resolution for backward compatibility.Oh, Long/toString is both static and instance...
Right, which makes sense. I think it would also make sense to do the same for instance methods in invocation position: https://ask.clojure.org/index.php/13684/use-arity-method-selection-disambiguation-where-possible
I'm looking at the Clojure compiler source:
// In invocation position
// direct invocation of resolved constructor, static, or instance method
// OR legacy static method invocation with inference
// this is the ONLY valid case where method is unresolved by end of constructor
// In value position, will emit as a method thunk (error if not resolved)point_up::skin-tone-2 so there's the specific "exception" for (legacy) static method invocation for backward compatibility.
This is by design. The point of qualified methods (+ param-types if needed) is that you are selecting one method and no inference is done. The one place where this overlaps with prior behavior is static methods (which were already qualified) in invocation position. In that specific case, we do support inference for backwards compatibility.
Ok, thanks all.
Somewhat related to this, am I correct in understanding the Uniform Class/member JIRA that when using an instance method in invocation position, it will be compiled to the same code as using the old .method form (i.e. not indirect through a thunk)?
i.e. (^[] String/toLowerCase "Foo") is essentially equivalent to (.toLowerCase "Foo")
Other than this one special case (static and no param-tags), inference is never done, and if your code compiles it does not use reflection (which is actually a really important thing you can rely on)
So (.fooMethod obj "x") could be reflective but (SomeClass/fooMethod obj "x") is never reflective (as long as it compiles).
In all invocation position code, the compiler ultimately uses the same expressions and emits the same bytecode as before
Depends if fooMerhod is static or instance
If static, might reflect (but you can add param-tags to get that)
(I updated it to make it clearer that I'm only talking about instance methods)
Then yes, same with /new
From some code I was playing with at work:
(->> (jdbc/query mysql-db ["select defaultUrl from site"])
(map :defaulturl)
;; originally (map #(.getHost (java.net.URI. %)))
;; this is just curiosity about the new 1.12 interop features:
(map (comp java.net.URI/getHost ^[_] java.net.URI/new))
(filter some?))
The 1.12 structure is longer but more explicit. And we could (:import ( to turn it into (comp URI/getHost ^[_] URI/new) which is quite nice.
As someone who uses a lot of interop and cares about avoiding reflection, I’d be much more likely to use the instance methods in invocation position if it still used inference and failed if the result wasn’t unambiguous. Those param-tags hints are going to be huge, at least in my case.
Yes, we expect that will be common
Sorry, what will, huge type hints?
Oh, I misread that
Note that param-tags can use wildcard _ for params that are unambiguous
And you can use importe short class names
If you are more specific, the payoff is no reflection
I’m not sure how much that will help, I’ll have to try it out. I’m often working with methods that have 5+ overloads per arity, and the classnames are long.
Admittedly, I’m probably on the fringes of interop use cases, but still…
Inference with warn on reflection may be better for you then
But I’m interested in if it works for you
Yes, I already guarantee no reflection because I AOT everything and fail the build on reflection warnings.
But I’ll try it out once it get a little more stable.
(we also fail our build if new reflection warnings appear -- and also if we omit (set! *warn-on-reflection* true) in any files)