Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

clojure

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?

like image 766
viebel Avatar asked Aug 04 '13 11:08

viebel


3 Answers

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.

like image 158
Leon Grapenthin Avatar answered Nov 11 '22 03:11

Leon Grapenthin


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
like image 23
user499049 Avatar answered Nov 11 '22 02:11

user499049


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)))
like image 1
viebel Avatar answered Nov 11 '22 03:11

viebel