Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure apply vs map

Tags:

clojure

I have a sequence (foundApps) returned from a function and I want to map a function to all it's elements. For some reason, apply and count work for the sequnece but map doesn't:

(apply println foundApps)
(map println rest foundApps)
(map (fn [app] (println app)) foundApps)
(println (str "Found " (count foundApps) " apps to delete"))))

Prints:

{:description another descr, :title apptwo, :owner jim, :appstoreid 1235, :kind App, :key #<Key App(2)>} {:description another descr, :title apptwo, :owner jim, :appstoreid 1235, :kind App, :key #<Key App(4)>}
Found 2 apps to delete for id 1235

So apply seems to happily work for the sequence, but map doesn't. Where am I being stupid?

like image 778
James Massey Avatar asked Feb 22 '10 14:02

James Massey


3 Answers

I have a simple explanation which this post is lacking. Let's imagine an abstract function F and a vector. So,

(apply F [1 2 3 4 5])

translates to

(F 1 2 3 4 5)

which means that F has to be at best case variadic.

While

(map F [1 2 3 4 5])

translates to

[(F 1) (F 2) (F 3) (F 4) (F 5)]

which means that F has to be single-variable, or at least behave this way.

There are some nuances about types, since map actually returns a lazy sequence instead of vector. But for the sake of simplicity, I hope it's pardonable.

like image 105
rishat Avatar answered Nov 12 '22 16:11

rishat


Most likely you're being hit by map's laziness. (map produces a lazy sequence which is only realised when some code actually uses its elements. And even then the realisation happens in chunks, so that you have to walk the whole sequence to make sure it all got realised.) Try wrapping the map expression in a dorun:

(dorun (map println foundApps))

Also, since you're doing it just for the side effects, it might be cleaner to use doseq instead:

(doseq [fa foundApps]
  (println fa))

Note that (map println foundApps) should work just fine at the REPL; I'm assuming you've extracted it from somewhere in your code where it's not being forced. There's no such difference with doseq which is strict (i.e. not lazy) and will walk its argument sequences for you under any circumstances. Also note that doseq returns nil as its value; it's only good for side-effects. Finally I've skipped the rest from your code; you might have meant (rest foundApps) (unless it's just a typo).

Also note that (apply println foundApps) will print all the foundApps on one line, whereas (dorun (map println foundApps)) will print each member of foundApps on its own line.

like image 34
Michał Marczyk Avatar answered Nov 12 '22 15:11

Michał Marczyk


A little explanation might help. In general you use apply to splat a sequence of elements into a set of arguments to a function. So applying a function to some arguments just means passing them in as arguments to the function, in a single function call.

The map function will do what you want, create a new seq by plugging each element of the input into a function and then storing the output. It does it lazily though, so the values will only be computed when you actually iterate over the list. To force this you can use the (doall my-seq) function, but most of the time you won't need to do that.

If you need to perform an operation immediately because it has side effects, like printing or saving to a database or something, then you typically use doseq.

So to append "foo" to all of your apps (assuming they are strings):

(map (fn [app] (str app "foo")) found-apps)

or using the shorhand for an anonymous function:

(map #(str % "foo") found-apps)

Doing the same but printing immediately can be done with either of these:

(doall (map #(println %) found-apps))

(doseq [app found-apps] (println app))

like image 10
rosejn Avatar answered Nov 12 '22 16:11

rosejn