Welcome! Please see the About page for a little more info on how this works.

+2 votes
in Clojure by

with java method references the following works (this code is taken from java.time.LocalDate):

public static LocalDate parse(CharSequence text, DateTimeFormatter formatter) {
    return formatter.parse(text, LocalDate::from);

the second arg to the parse method is a TemporalQuery - a functional interface that takes a single TemporalAccessor as arg. In this example, instead of passing a TemporalQuery instance, here a method reference is being passed, where the method signature matches that of TemporalQuery - ie takes a single TemporalAccessor as arg.

Trying to do similar in clojure:

(^[CharSequence TemporalQuery] DateTimeFormatter/parse formatter "20200202" java.time.LocalDate/from)

results in

Execution error (ClassCastException) at user/eval2266 (form-init14137309762912529150.clj:1).

class user$eval2266$invokeLocalDate_from2268 cannot be cast to class java.time.temporal.TemporalQuery (user$eval2266$invokeLocalDate_from2268 is in unnamed module of loader clojure.lang.DynamicClassLoader @37e99783; java.time.temporal.TemporalQuery is in module java.base of loader 'bootstrap')

Is this a bug or intentional do you think? If intentional, some docs on how clojure method values compare to java method references would be useful.

2 Answers

+3 votes

This will work with the new functional interface coercion coming in next alphas.

At a base level, Clojure IFns will be implicitly converted to an interface of the appropriate type (or you can request explicit coercion in let bindings). This is done with invokedynamic, essentially the same mechanism used by Java lambda compilation (except we’re going to use a fixed set of adapter targets rather than dynamically gen’ing synthetic lambda methods).

The new method values reify as IFns so can automatically feed into this, but as you can imagine, it is not optimal to wrap a Java method into an Ifn, then adapt it back into a method reference. Fortunately, we can tell this is happening in the compiler and adapt the original method directly.

So, it’s coming soon! This is part of the 1.12 story.

awesome! thanks Alex
0 votes

Method values in Clojure are, well, values, by themselves. It's syntax sugar for creating a lambda function that uses the right overload of a Java method.

Method references in Java are a syntax sugar to create the right thing in a particular context where it's used. You can't just get a value of LocalDate::from - you have to put it in a context where Java compiler can figure out what you want exactly.

Your example is more about Java, not Clojure. It's Java compiler that figures out all the signatures and that they're compatible and makes it possible to use ...::from that way.

Since you're just calling the parse function, it doesn't make much sense to use

(^[CharSequence TemporalQuery] DateTimeFormatter/parse formatter x y)

when instead you can use

(.parse ^DateTimeFormatter formatter ^CharSequence x ^TemporalQuery y)

However, it won't help in your case because LocalDate/from is not an instance of TemporalQuery. In Clojure, LocalDate/from is a regular Clojure function. Even if you use ^[TemporalAccessor] LocalDate/from, it's still a regular Clojure function, just pointing to the .../from method (not important here since there's only one such method in LocalDate, but is important when there are overloads). That function's signature is still [Object]. (And even if the signature was [TemporalQuery], I doubt that would help since Java compiler won't be called here, but I could be wrong.)

With all that said, I'm pretty sure that with the current state of affairs the only way to achieve what you want is via the good old reify:

(let [formatter java.time.format.DateTimeFormatter/BASIC_ISO_DATE]
  (.parse formatter
          (reify java.time.temporal.TemporalQuery
            (queryFrom [_ ^java.time.temporal.TemporalAccessor temporal]
              (java.time.LocalDate/from temporal)))))
Seem my other comment - this is actually coming soon.