Dark theme | Light theme

February 3, 2021

Clojure Goodness: Use .. For Invocation Java Method Chaining

Accessing Java from Clojure is easy. With the dot (.) special form we can invoke for example methods from a Java class or instance. If we want to invoke several methods together where the return value from one method is used to invoke the next method (method chaining) we can use the .. macro. The macro will expand into a nested expression with the . forms.

In the following example we see how to use the .. macro and how we can achieve the same result using nested . expressions and by using the thread first macro:

  (:require [clojure.test :refer [is]])
  (:import (java.util Optional)))

(def value "Clojure")

;; We use Optional map method that accepts a java.util.function.Function,
;; so here we implement the Function interface with an implementation
;; to return the given String value in upper case.
(def fn-upper
  (reify java.util.function.Function
    (apply [this arg] (. arg toUpperCase))))

(is (= "CLOJURE"
       ;; Invoke Java method chaining using the special .. macro.
       ;; Java: Optional.ofNullable(value).map(s -> s.toUpperCase()).orElse("Default")
       (.. Optional (ofNullable value) (map fn-upper) (orElse "Default"))
       ;; Macro expands to the following equivalent using . form.
       (. (. (. Optional ofNullable value) (map fn-upper)) (orElse "Default"))
       ;; Using thread first macro with equivalent method invocations.
       (-> (Optional/ofNullable value)
           (. (map fn-upper))
           (. (orElse "Default")))))

(is (= "Default"
       (.. Optional (ofNullable nil) (map fn-upper) (orElse "Default"))))

Written with Clojure 1.10.1.