Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

the -> macro and partial function

Tags:

clojure

Why can't I use a partial function or an anonymous function in a chained call with ->?

(->
    "123"
    seq
    sort
    reverse
    (partial apply str))
=> #<core$partial$fn__4230 clojure.core$partial$fn__4230@335f63af>

I would have thought the partial function would be created and immediately applied to the previous function's result, but it's actually returned itself.

A simple chaining of functions with one parameter of course works without problems:

(->
    "123"
    seq
    sort
    reverse
    clojure.string/join)
like image 570
kossmoboleat Avatar asked May 07 '26 20:05

kossmoboleat


2 Answers

As -> and ->> are just macros, you can test what -> expands to with macroexpand-1 (note the quote ')

user=> (macroexpand-1 '(->
       "123"
       seq
       sort
       reverse
       (partial apply str)))

(partial (reverse (sort (seq "123"))) apply str)

So that's why you get a function instead of a string - result is partial of collection (which is a function) returned from reverse and apply str as arguments.

If for some reason you need to apply a funcion in ->, you should wrap it in parens:

user=> (macroexpand-1 '(->
    "123"
    ((partial sort))  ; we add parens around so -> have plase to insert
   ))
((partial sort) "123")

Or you can use ->> macro, as it is doing same work as partial here:

((partial str "hello ") "world")

"hello world"

and

(->> (str "hello ") "world")    ; expands to (str "hello " "world")

"hello world"
like image 141
lobanovadik Avatar answered May 10 '26 22:05

lobanovadik


The -> macro is defined as:

(doc ->)
-------------------------
clojure.core/->
([x & forms])
Macro
  Threads the expr through the forms. Inserts x as the
  second item in the first form, making a list of it if it is not a
  list already. If there are more forms, inserts the first form as the
  second item in second form, etc.

Therefore (apply str) yields an error, because the list from the former function is entered as the second argument:

(->
    "123"
    seq
    sort
    reverse
    (apply str))
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn  clojure.core/apply (core.clj:624)

Instead one should use the macro ->>, which applies to the last item:

clojure.core/->>
([x & forms])
Macro
  Threads the expr through the forms. Inserts x as the
  last item in the first form, making a list of it if it is not a
  list already. If there are more forms, inserts the first form as the
  last item in second form, etc.

Then the solution with apply looks like this:

(->>
  "123"
  seq
  sort
  reverse
  (apply str))
=> "321"

UPDATE: expanded version of the -> and ->>:

(macroexpand-1 '(->>
                 "123"
                 seq
                 sort
                 reverse
                 (apply str)))
=> (apply str (reverse (sort (seq "123"))))

As you can see from the expanded versions, there's only a difference in how the form (apply str) is interpreted. With -> the second item is inserted while ->> inserts the last item.

(macroexpand-1 '(->
                 "123"
                 seq
                 sort
                 reverse
                 (apply str)))
=> (apply (reverse (sort (seq "123"))) str)
like image 43
kossmoboleat Avatar answered May 10 '26 23:05

kossmoboleat