Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Odd difference between named and anonymous functions when using the threading macro

There's something I must be missing about the threading macro in Clojure.

I have a map with values that are maps as well, and I'd like to a lookup in the result of another lookup. Let the map be a simple {:a {:b 2}} -- first I want to look up the key :a, that's going to yield {:b 2}, then look up b, the result is 2. The key for the second lookup needs to be a result of a function.

((fn [x] (get x :b)) ({:a {:b 2} } :a ))
=> 2

Ok, let's make it more readable with the threading macro.

(-> {:a {:b 2} } :a (fn [x] (get x :b)))

I.e. apply :a as a function on the map then apply another function. Well, this doesn't work: CompilerException java.lang.IllegalArgumentException: Parameter declaration :a should be a vector

Oddly enough, if the anonymous function is extracted to a named one, then it works fine:

(defn f [x] (get x :b))
(-> {:a {:b 2} } :a f)
=> 2

Or even:

(def f (fn [x] (get x :b)) ) 
(-> {:a {:b 2} } :a f)
=> 2

Why is there a difference between how named and anonymous functions work?

like image 522
Mate Varga Avatar asked Oct 05 '14 17:10

Mate Varga


2 Answers

The threading macro sees, and alters, each subform in the series before that form is evaluated, by recursively inserting the prior form as the first argument to each subform.

you start with:

(-> {:a {:b 2} } :a (fn [x] (get x :b)))

this becomes:

(-> (:a {:a {:b 2}}) (fn [x] (get x :b)))

this becomes:

(fn (:a {:b {:b 2}}) [x] (get x :b)))

Which is clearly not what you wanted at all.

But let's see what happens if you add extra parens around the anonymous function:

(-> {:a {:b 2}} :a ((fn [x] (get x :b))))

(-> (:a {:a {:b 2}}) ((fn [x] (get x :b))))

(-> ((fn [x] (get x :b)) (:a {:a {:b 2}})))

((fn [x] (get x :b)) (:a {:a {:b 2}}))

At the last recursive macroexpansion of the -> form we are now left with valid code that does what you want.

like image 169
noisesmith Avatar answered Oct 11 '22 18:10

noisesmith


To complement noisesmith's response, in this particular case you don't need the threading macro. The idiomatic way to get a value from a nested map is get-in. e.g:

(get-in {:a {:b 2}} [:a :b])

=>

2
like image 36
Diego Basch Avatar answered Oct 11 '22 18:10

Diego Basch