/ CLOJURE, SEQUENCE, STREAM

Feedback on Learning Clojure: comparing with Java streams

Coming from a Java background, I’m currently trying to learn the Clojure programming language, with the help of online resources and mentorship. Some weeks ago, I tried to wire things together by trying to find equivalent methods to those available in Java streams.

While I managed to get things working, writing working code and writing idiomatic code are two very different things. I was fortunate to have a good degree of feedback from different sources: Hacker News, Reddit and on this very blog. I’d like to thank everyone who contributed in a positive way, and gave me more insight into Clojure. This post is a sum up of the most common feedback I received.

This is the 7th post in the Learning Clojure focus series.Other posts include:

  1. Decoding Clojure code, getting your feet wet
  2. Learning Clojure: coping with dynamic typing
  3. Learning Clojure: the arrow and doto macros
  4. Learning Clojure: dynamic dispatch
  5. Learning Clojure: dependent types and contract-based programming
  6. Learning Clojure: comparing with Java streams
  7. Feedback on Learning Clojure: comparing with Java streams (this post)
  8. Learning Clojure: transducers

Idiomatic keyword map

Let’s start with a simple, but it seems very widespread, usage regarding dictionary access.

In order to retrieve a value by its key inside a dictionary, I originally used a named function. Then, I went on to use an anonymous function:

(map #(:name %) justice-league)

Actually, the anonymous function is boiler-platey in Clojure. It can be replaced with plain keyword access:

(map :name justice-league)

This returns the same result, but with a much terser syntax.

Filtering out nil values

To filter out nil values, I inverted with (not) the available (nil?) check:

(filter #(not (nil? %)) [nil
                         {:vehicles [::Bat-Mobile, ::Bat-Plane]}
                         {:vehicles [::Invisible-Plane]}
                         nil
                         nil
                         nil])

That yields:

({:vehicles [:sandbox.function/Bat-Mobile :sandbox.function/Bat-Plane]}
 {:vehicles [:sandbox.function/Invisible-Plane]})

But Clojure provides an out-of-the-box function that does exactly the same, named (some?). It’s defined as the following:

Returns true if x is not nil, false otherwise.

Replacing the initial functions combination with (some?) is pretty straightforward:

(filter some? [nil
               {:vehicles [::Bat-Mobile, ::Bat-Plane]}
               {:vehicles [::Invisible-Plane]}
               nil
               nil
               nil])

In conclusion, it’s always better to directly call a function - if there’s one available, instead of inverting another one.

Keeping things together

The next improvement arises when only dictionary values from a single key need to be kept. In the last post, I used (filter) and (map) sequentially:

(->> [nil
      {:vehicles [::Bat-Mobile, ::Bat-Plane]}
      {:vehicles [::Invisible-Plane]}
      nil
      nil
      nil]
  (filter some?)
  (map :vehicles))

The output is the following:

([:sandbox.function/Bat-Mobile :sandbox.function/Bat-Plane]
 [:sandbox.function/Invisible-Plane])

Clojure provides an out-of-the-box function called (keep) that behaves similarly:

Returns a lazy sequence of the non-nil results of (f item).

— keep
https://clojuredocs.org/clojure.core/keep

In essence, (keep) applies the mapping function f to only non-nil values. Hence it’s easy to update the above code with it:

(keep :vehicles [nil
                 {:vehicles [::Bat-Mobile, ::Bat-Plane]}
                 {:vehicles [::Invisible-Plane]}
                 nil
                 nil
                 nil])

Mapcat for the win!

Finally, we need to flatten the resulting collection. This is the code I used previously:

(->> [nil
      {:vehicles [::Bat-Mobile, ::Bat-Plane]}
      {:vehicles [::Invisible-Plane]}
      nil
      nil
      nil]
  (keep :vehicles)
  (flatten))

This returns:

(:sandbox.function/Bat-Mobile
 :sandbox.function/Bat-Plane
 :sandbox.function/Invisible-Plane)

Interestingly enough, the following is the equivalent of the above snippet:

(->> [nil
      {:vehicles [::Bat-Mobile, ::Bat-Plane]}
      {:vehicles [::Invisible-Plane]}
      nil
      nil
      nil]
  (keep :vehicles)
  (apply concat))

Returns a lazy seq representing the concatenation of the elements in the supplied colls.

— concat
https://clojuredocs.org/clojure.core/concat

Alternatively, Clojure offers a (mapcat) function, a combination of a mapping function and applying (concat):

Returns the result of applying concat to the result of applying map to f and colls. Thus function f should return a collection.

— mapcat
https://clojuredocs.org/clojure.core/mapcat

Hence, the above code can make use of it, as in the following:

(mapcat :vehicles [nil
                   {:vehicles [::Bat-Mobile, ::Bat-Plane]}
                   {:vehicles [::Invisible-Plane]}
                   nil
                   nil
                   nil])
nil values are automatically filtered out because of applying (concat)

Final result

All in all, the "original code":

(->> justice-league
  (map #(:vehicles %))
  (filter #(not (nil? %)))
  (flatten))

can be replaced with this simple one-liner:

(mapcat :vehicles justice-league)

Conclusion

In my previous post, I wrote that Clojure syntax is pretty limited. Some commenters were not in agreement with this choice of words. Actually, I can only confirm: the basic building blocks are few.

On the opposite side, there is a whole lot of available out-of-the-box macros and functions. Knowing them can make the difference between a verbose hard-to-read code, and an idiomatic one.

Again, many thanks to everyone who gave me feedback. I hope to continue learning Clojure, don’t hesitate to steer me in the right direction if the need be!

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
Feedback on Learning Clojure: comparing with Java streams
Share this