Lesser known Clojure: keep, keep-indexed and map-indexed functions


I could safely say that map, reduce and filter are the most commonly used functions in Clojure (and probably other functional languages). Of course Clojure provides other useful functions that operate on collections that are not so popular and commonly known. Let’s look at some of them.

keep

Let’s start with the keep function. Its idea is simple, it takes function f as a first parameter and a collection as second:

(keep f coll)

The result is a lazy sequence of results of applying f to every item in this collection - (f item). There is only one important condition, if result of (f item) gives nil then this item isn’t in returned sequence. On the other hand (in contrast to the filter function), if (f item) returns false it appears in result.

Let’s look at the example:

(keep :a '({:a 1} {:b 3} {:a 8} {:z 7}))
=> (1 8)

Here we use :a keyword as a function (it implements IFn interface) on collection of maps. The result is a sequence with 1 and 8, because (:a {:a 1}) evaluates to 1 and (:a {:a 8}) returns 8. The (:a {:b 3}) and (:a {:z 8}) evaluates to nil, so they aren’t in the final result.

For comparison if we use filter we get such result:

(filter :a '({:a 1} {:b 3} {:a 8} {:z 7}))
=> ({:a 1} {:a 8})

and map behaves in this way:

(map :a '({:a 1} {:b 3} {:a 8} {:z 7}))
=> (1 nil 8 nil)

keep-indexed

We can say that keep-indexed is extended version of keep function. The difference is that the function f that we pass as a first argument must have such signature:

(fn [index item])

where index will get succeeding non-negative integers starting with 0:

(keep-indexed
 (fn [index item]
   [index (:a item)])
 '({:a 1} {:b 3} {:a 8} {:z 7}))

=> ([0 1] [1 nil] [2 8] [3 nil])

and one more example (that have more sense):

(keep-indexed
 (fn [index item]
   (when (even? index)
     (:a item)))
 '({:a 1} {:a 5} {:b 3} {:a 8} {:a 12} {:z 7}))

=> (1 12)

Here we want to return only values of keyword :a in the map if the map is on the even position (like: 0, 2, 4, 6 and so on) in the collection and has this keyword.

map-indexed

The map-indexed function is equivalent of keep-indexed function, but of course it behaves in the same way as map function. In another words, it transform a sequence of items to another sequence, by applying function f to every item in the original collection. The function f takes index as a first parameter and item as second:

(map-indexed
 (fn [index item] [index item])
 '(:a :b :c :d :e :f))

=> ([0 :a] [1 :b] [2 :c] [3 :d] [4 :e] [5 :f])

This way we don’t need to use some kind of loop when we need to transform items in collection and we need to know the position of item in the sequence.

(side note: all those function has also version that produce transducer, but for simplicity I’ve skipped it)


You may also like

ClojureScript: JavaScript Interop

Lesser known Clojure: variants of threading macro

Template method pattern