viebel
viebel

Reputation: 20670

In clojure, how to call all the methods of x then return the value of the last form execution?

I am looking for something like doto but instead of returning x, I would like to return the last call value.

For instance, instead of:

(.size (doto (java.util.ArrayList.) 
             (.add 2)
             (.add 3)))

I would like to write:

(mydoto (java.util.ArrayList.) 
             (.add 2)
             (.add 3)
             .size)

What is the idiomatic way of achieving this result?

Upvotes: 5

Views: 280

Answers (3)

viebel
viebel

Reputation: 20670

One could write mydoto using doto, as mentioned by @lgrapenthin:

(defmacro mydoto 
    "Like doto but returns the result of the last form instead 
     of the first argument"
    [& args]
    `(-> (doto ~@(butlast args))
         ~(last args)))

Upvotes: 1

user499049
user499049

Reputation: 564

Clojure macros will allow you to do what you want, in fact it's only a 1 symbol change to the existing doto macro (in emacs use Meta-. to see the definition in core.clj).

(defmacro mydoto 
   "Like doto but returns the result of the last form 
    instead to the first argument"
  [x & forms]
  (let [gx (gensym)]
  `(let [~gx ~x]
     ~@(map (fn [f]
              (if (seq? f)
                `(~(first f) ~gx ~@(next f))
                `(~f ~gx)))
            forms))))

This will allow you to write the following code:

(mydoto (java.util.ArrayList.) 
         (.add 2)
         (.add 3)
         .size)
;; => 2

Upvotes: 3

Leon Grapenthin
Leon Grapenthin

Reputation: 9266

doto returns it's first argument. -> and ->> return the result of evaluating the last form (after threading the first expression through, see (clojure.repl/doc ->). The macro doto will expand your code like this:

(let [r (java.util.ArrayList.)]
  (.add r 2)
  (.add r 3)
  r)

When you are using .size, it does not affect your array list and you are just interested in the return value of the method (unlike with .add which returns a boolean value and changes the state of the ArrayList which you are interested in).

You can't mix these methods like in mydoto, because what you require are different kinds of return and input values. If you wanted to multiply the final size by 3, this would be idiomatic:

(-> (doto (java.util.ArrayList.) 
          (.add 2)
          (.add 3))
    .size
    (* 3))

Or even

(-> (java.util.ArrayList.)
    (doto (.add 2) (.add 3))
    .size
    (* 3))

These macros should make the code easier to read so depending on the context I'd write them in a way that makes most sense.

Upvotes: 13

Related Questions