javaclojureclojure-java-interop

Dealing with Java Optional<T> in clojure


I am writing Clojure code interacting with external Java library. One of the method returns Java Optional object. Let's assume that it returns Optional<String> and I need to change the string value in the Optional object, for example, to lowercase. I'd use map method if I write the code in Java:

Optional<String> ret = someObj.someMethod();
ret.map(String::toLowerCase)
   .map(...)
   .orElse("x");

So, I need to call someObj.someMethod() in Clojure and have to do the similar work. What I found is this: Passing a Clojure function as java.util.Function

So I wrote code like this:

(defn ^java.util.function.Function jfn [f]
  (reify java.util.function.Function
    (apply [this arg] (f arg))))

(let [ret (.someMethod someObj)]
  (.. ret
      (map (jfn (fn [x] (s/lower-case x))))
      (map (jfn (...)))
      (orElse "x")))

In order to pass clojure function to the place that expects Java lambda, I used jfn defined like the above. And actually it works fine.

But I am not sure this is the best way to do this as I have to wrap clojure function that calls Java method inside with Java's Function.

Are there any better/simpler way to do this? It will be better if we can call Java String's toLowerCase method directly.


Solution

  • I'm not sure if it fits your use case, but you could "unbox" the Optional early on, and use some-> macro to do similar short-circuit-on-null function composition.

    Even if you needed an Optional with the value later, it might be easier to unbox the value early and re-box the value later, rather than Function interop.

    (defn optional->nilable [this]
      (when (.isPresent this)
        (.get this)))
    
    (def maybe (Optional/of " something "))
    
    (some-> (optional->nilable maybe)
            (clojure.string/trim)
            (not-empty)
            (clojure.string/upper-case))
    ; => "SOMETHING"
    

    Then if you needed to convert back to Optional:

    (Optional/ofNullable *1)