Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ignore unnecessary arguments?

Tags:

clojure

Is there simple way ho to ignore unnecessary arguments?

For example: (#(+ %1 %2) 1 2) returns 3

I would like to force this code

(#(+ %1 %2) 1 2 3)

to returns 3 too. But it returns

java.lang.IllegalArgumentException: Wrong number of args (3) passed to.

How to change #(+ %1 %2)?

I hope there is some more elegant way than #(+ (nth %& 0) (nth %& 1)).

like image 545
boucekv Avatar asked Aug 02 '12 11:08

boucekv


3 Answers

Here's what I would go with:

=> ((fn [a b & _] (+ a b)) 1 2 3)
=> 3

This creates an anonymous function that takes any number of arguments, but just adds the first two. The _ symbol isn't doing anything special, it's just idiomatic for "I don't care about this thing, but I need it bound somehow". If you think you are going to be doing this a lot, then you would probably want to define it:

(defn add-first-two
  [a b & _]
  (+ a b))

If you are really into the #() syntax, you will have to include %& somewhere in the definition for it to "realize" that it should take any number of arguments. It doesn't mean you would have to index it as you show, it would just have to be present somewhere. Here's an example:

=> (#(do %& (+ %1 %2)) 1 2 3)
=> 3

On the other hand, this solution involves some sort of processing of %&, where you don't need any, and may slow things down slightly (I'm not sure about this, maybe someone else can give me feedback on this). A rather fun solution would be to just stick %& inside a (comment ...) block, which will evaluate to nil.

=> (#(do (comment %&) (+ %1 %2)) 1 2 3)
=> 3

An even more exotic solution would be to use #_..., which is a lot like (comment...) except that the reader actually removes it from evaluation, as though you had simply typed nothing there. This way, maybe we could stick it inside the body of (+ ...) to shorten the code.

=> (#(+ %1 %2 #_%&) 1 2 3)
=> 3

What do you know, it worked. I'd actually never tried this one before, and I am pretty surprised to see it work. Apparently, there is some processing before the #_... section is removed. Strange.

If you don't like any of these weird comment solutions, and the do isn't doing it for you, you could always put it inside an (if nil ...) block:

=> (#(if nil %& (+ %1 %2)) 1 2 3)
=> 3

Well, after all that, I still like the first solution best (using (fn ...)), but some of these others are certainly shorter.

like image 52
Omri Bernstein Avatar answered Nov 12 '22 16:11

Omri Bernstein


This works for any number of arguments, and is readable:

(fn [& args] (apply + args))

If you only want to use the first 2 arguments (as per Alex' comment):

(fn [& args] (apply + (take 2 args)))

You can use it directly:

((fn [& args] (apply + (take 2 args))) 1 2)
; => 3

Or bind the function to a symbol like this:

(let [f (fn [& args] (apply + (take 2 args)))]
  (f 1 2))
; => 3

Or create a var:

(def f (fn [& args] (apply + (take 2 args))))

Or a function:

(defn f [& args] (apply + (take 2 args)))

The best definition depends on your use case.

like image 39
Gert Avatar answered Nov 12 '22 17:11

Gert


how about this?

(#(apply + (take 2 %&)) 1 2 3 4 5)
;; => 3
like image 20
blueiur Avatar answered Nov 12 '22 16:11

blueiur