clojure-dev

cfleming 2024-02-10T23:26:28.336619Z

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?

Alex Miller (Clojure team) 2024-02-15T18:34:20.742309Z

@cfleming the feedback on the uniform method selection not using type flow information was helpful, thanks. we're incorporating that into next iteration.

❤️ 2
cfleming 2024-02-15T18:48:49.165509Z

Great news, thanks Alex.

seancorfield 2024-02-10T23:33:10.349429Z

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

cfleming 2024-02-10T23:34:40.535479Z

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.

seancorfield 2024-02-10T23:35:02.591379Z

Do you have an example of that change?

seancorfield 2024-02-10T23:35:50.075179Z

I picked String/valueOf because it is static and has multiple overloads...

cfleming 2024-02-10T23:36:07.782159Z

Yes, above - (Long/toString) (static method) works without a hint, even though there’s an overload for toString taking a radix.

seancorfield 2024-02-10T23:36:36.401489Z

So... that's the same between 1.11 and 1.12, yes?

cfleming 2024-02-10T23:36:42.354029Z

Right.

cfleming 2024-02-10T23:37:44.829049Z

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.

seancorfield 2024-02-10T23:41:02.585789Z

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

cfleming 2024-02-10T23:43:06.706379Z

Yes, I’m assuming that static invocations still use arity and argument types to disambiguate.

seancorfield 2024-02-10T23:45:51.904739Z

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.

seancorfield 2024-02-10T23:46:37.775979Z

Oh, Long/toString is both static and instance...

cfleming 2024-02-10T23:47:00.769539Z

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

seancorfield 2024-02-10T23:54:39.048299Z

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)

seancorfield 2024-02-10T23:55:05.762659Z

point_up::skin-tone-2 so there's the specific "exception" for (legacy) static method invocation for backward compatibility.

Alex Miller (Clojure team) 2024-02-10T23:57:17.012329Z

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.

cfleming 2024-02-10T23:58:09.248309Z

Ok, thanks all.

cfleming 2024-02-10T23:59:36.167149Z

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

cfleming 2024-02-11T00:00:24.163099Z

i.e. (^[] String/toLowerCase "Foo") is essentially equivalent to (.toLowerCase "Foo")

Alex Miller (Clojure team) 2024-02-11T00:00:37.926839Z

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)

seancorfield 2024-02-11T00:01:22.759999Z

So (.fooMethod obj "x") could be reflective but (SomeClass/fooMethod obj "x") is never reflective (as long as it compiles).

Alex Miller (Clojure team) 2024-02-11T00:01:33.348019Z

In all invocation position code, the compiler ultimately uses the same expressions and emits the same bytecode as before

Alex Miller (Clojure team) 2024-02-11T00:02:09.476189Z

Depends if fooMerhod is static or instance

Alex Miller (Clojure team) 2024-02-11T00:02:31.894419Z

If static, might reflect (but you can add param-tags to get that)

seancorfield 2024-02-11T00:02:33.455429Z

(I updated it to make it clearer that I'm only talking about instance methods)

Alex Miller (Clojure team) 2024-02-11T00:02:55.663669Z

Then yes, same with /new

👍🏻 1
seancorfield 2024-02-11T00:03:53.023959Z

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

🔥 1
😍 1
seancorfield 2024-02-11T00:04:52.768339Z

The 1.12 structure is longer but more explicit. And we could (:import ( URI)) to turn it into (comp URI/getHost ^[_] URI/new) which is quite nice.

cfleming 2024-02-11T00:07:27.815899Z

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.

Alex Miller (Clojure team) 2024-02-11T00:13:13.150229Z

Yes, we expect that will be common

cfleming 2024-02-11T00:14:16.324099Z

Sorry, what will, huge type hints?

Alex Miller (Clojure team) 2024-02-11T00:14:27.988359Z

Oh, I misread that

Alex Miller (Clojure team) 2024-02-11T00:15:01.375429Z

Note that param-tags can use wildcard _ for params that are unambiguous

Alex Miller (Clojure team) 2024-02-11T00:15:26.028219Z

And you can use importe short class names

Alex Miller (Clojure team) 2024-02-11T00:16:25.050609Z

If you are more specific, the payoff is no reflection

cfleming 2024-02-11T00:16:43.484159Z

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.

cfleming 2024-02-11T00:17:28.844709Z

Admittedly, I’m probably on the fringes of interop use cases, but still…

Alex Miller (Clojure team) 2024-02-11T00:20:43.062309Z

Inference with warn on reflection may be better for you then

Alex Miller (Clojure team) 2024-02-11T00:21:11.410289Z

But I’m interested in if it works for you

cfleming 2024-02-11T00:21:16.853629Z

Yes, I already guarantee no reflection because I AOT everything and fail the build on reflection warnings.

cfleming 2024-02-11T00:21:41.519959Z

But I’ll try it out once it get a little more stable.

seancorfield 2024-02-11T00:23:40.668949Z

(we also fail our build if new reflection warnings appear -- and also if we omit (set! *warn-on-reflection* true) in any files)