threading macro -> with anonymous functions

2020-02-17 09:04发布

问题:

I understand the `-> theading macro in Clojure applies all the provided functions provided to a given argument. However, it doesn't seem to work with anonymous functions. For example:

user> (-> 4 inc inc dec)
5

But:

user> (-> 4 #(+ % 1) #(- % 1) #(+ % 1))

Returns the error:

clojure.lang.Symbol cannot be cast to clojure.lang.IPersistentVector
[Thrown class java.lang.ClassCastException]

If someone knows a way around it would be helpful. Thanks!

回答1:

You can have anonymous functions in Clojure macros. You are having problems, because you are missing some parentheses. :) Your example is edited below.

(-> 4 (#(+ % 1)) (#(- % 1)) (#(+ % 1)))


回答2:

(this is based on the answer to the question i posted in comments).

the -> macro takes each argument, making it a list if necessary (applying "raw" functions to no args - converting myfunc to (myfunc)), and then inserts the first argument to -> as second argument in each of those lists.

so (-> foo myfunc) becomes (-> foo (myfunc)) becomes (myfunc foo), roughly.

this is all described in the docs for ->.

the problem with anonymous functions is that they are generated by a reader macro as described here (scroll down). that means that #(...) is converted (before normal macro expansion) into (fn [...] ...). which is fine, but, critically, is already a list.

so the macro believes that the anonymous function is already being applied, when in fact it is encountering a function definition (both are lists). and adding the "extra" parens - as described above in the other answer - applies the anonymous function to no args.

the reason for this un-intuitive behaviour is that the dwim (do-what-i-mean, not dwim-witted, although...) heuristic used by the -> macro, added to allow you to supply "bare" functions rather than requiring that you apply them to no args by enclosing them in a list, is just a heuristic - it simply tests for a list - and is confused by the function definition created by the reader macro.

[in my bad tempered opinion, -> is poorly implemented and should instead reject all "bare" functions, instead only accepting function applications; it would then appear more consistent. if not, then at least the docs could be clearer, explaining the motivating semantics behind placing things in lists.]



回答3:

Your specific case could have been solved simply using:

(-> 4 (+ 1) (- 1) (+ 1))

where the thread first macro -> takes care of inserting the result of the previous step as the first argument in the "current" function.

The confusion arises from the fact that -> is not a function but a macro and the arguments are treated very differently in this case as explained by other answers.