April 2, 2020

Clojure Goodness: Keep Non-Nil Function Results From Collection

The keep function in Clojure invokes a function on each item in a collection and only returns non-nil results from the function invocation. The result of the keep function is a lazy sequence.

The following example uses the keep function, but also show what results would be when using map function on the same collection with the same function argument:

(ns mrhaki.seq.keep
  (:require [clojure.test :refer [is]]))

(def stuff ["Clojure" "Groovy" "Java"])

;; Function keep filters on non-nil results that are returned
;; from applying the function. 
(is (= '("Clojure has more than 6 characters")
       (keep #(if (> (count %) 6) (str % " has more than 6 characters")) stuff)))

;; Using the same function with map shows the
;; nil results that are filtered by keep.
(is (= (list "Clojure has more than 6 characters" nil nil)
       (map #(if (< 6 (count %)) (str % " has more than 6 characters")) stuff)))

(def user {:name "Hubert" :nickname "mrhaki" :age 46})

;; Returned result from the function is a boolean
;; so it is always included in the result after applying 
;; the keep function.
(is (= [true true false]
       (keep #(instance? String (% 1)) user)))

;; Here the function returns a string result or nill.
(is (= [":name has a String value" ":nickname has a String value"]
       (keep (fn [[k v]] (if (instance? String v) (str k " has a String value"))) user)))

Written with Clojure 1.10.1.