Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure: how is defn different from fn?

If I explicitly define a function like this (defn f [x] (get x "a")), then both (-> {"a" 1} f) and (f {"a" 1}) work as expected.

However, if I'm using anonymous function, only (#(get % "a") {"a" 1}) works but (-> {"a" 1} #(get % "a")) throws exception: CompilerException java.lang.ClassCastException: clojure.lang.PersistentArrayMap cannot \ be cast to clojure.lang.ISeq, compiling:(NO_SOURCE_PATH:1:1)

like image 817
woodings Avatar asked Jun 06 '13 08:06

woodings


Video Answer


1 Answers

#(get % "a") is expanded by the reader:

 user=> '#(get % "a")
 (fn* [p1__852#] (get p1__852# "a"))

You can ignore the difference between fn and fn* in this case.

(-> ...) is a macro that just rethreads its arguments:

user=> (macroexpand-1 '(-> {"a" 1} f))
(f {"a" 1})

Note that it only wraps parentheses around f if there aren't ones already, so:

user=> (macroexpand-1 '(-> {"a" 1} (f)))
(f {"a" 1})

But that won't work as you might expect when applied to fn macros:

user=> (macroexpand-1 '(-> {"a" 1} (fn [x] (get x "a"))))
(fn {"a" 1} [x] (get x "a"))

Or on #(...) reader forms:

user=> (macroexpand-1 '(-> {"a" 1} #(get % "a")))
(fn* {"a" 1} [p1__867#] (get p1__867# "a"))

The general solution is to put your anonymous function inside a list, though if you can use a named function, I think it reads a lot clearer:

user=> (macroexpand-1 '(-> {"a" 1} (#(get % "a"))))
((fn* [p1__870#] (get p1__870# "a")) {"a" 1})
like image 77
Joost Diepenmaat Avatar answered Oct 06 '22 14:10

Joost Diepenmaat