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:
- Decoding Clojure code, getting your feet wet
- Learning Clojure: coping with dynamic typing
- Learning Clojure: the arrow and doto macros
- Learning Clojure: dynamic dispatch
- Learning Clojure: dependent types and contract-based programming
- Learning Clojure: comparing with Java streams
- Feedback on Learning Clojure: comparing with Java streams (this post)
- 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).
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.
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.
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])| nilvalues 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!