...Maybe imperative programming with mutable data is just drilled too deep into my brain, but I find the code for building up vectors of data in Clojure to be verbose, unwieldy, and convoluted. There must be a better way!
In Ruby I might write code like:
results = []
a_collection.each do |x|
x.nested_collection.each do |y|
next if some_condition_holds
results << y
end
end
In Clojure, I don't know of a better way to do that than to use a recursive function, perhaps like the following (horrendous) code:
; NEWBIE ALERT! NEWBIE ALERT!
(loop [results []
remaining a_collection]
(if (empty? remaining)
results
(recur
(loop [results results
nested (nested_collection (first remaining))]
(if (empty? nested)
results
(if (some_condition_holds)
(recur results (rest nested))
(recur (conj results (first nested)) (rest nested)))))
(rest remaining))))
Without mutable data and iterative loops, you need to use recursion to build up a collection. Each such recursive function needs an (empty?)
guard clause, etc. etc. The whole thing is so repetitive it makes me want to scream.
In simple cases, map
would be enough, but I'm thinking of cases where there are multiple levels of nesting, and at each level, there may be conditions which require skipping an iteration.
In Common Lisp I might use the loop
macro, or mapcan
. Doesn't Clojure have anything like mapcan
?
In descending order of how nice I think the options look:
(for [x coll,
y (nested-collection x)
:when (not (some-condition-holds y))]
y)
Alternatively, if you'd rather build it out of functions like map
and mapcat
instead of using the for
syntax:
(mapcat (fn [x]
(remove some-condition-holds
(nested-collection x)))
coll)
If you are really keen on it, you can also build it with partial function applications and composition:
(mapcat (comp (partial remove some-condition-holds)
nested-collection)
coll)
This third style doesn't read very well in Clojure, although equivalent code in some other languages is very nice. In Haskell, for example:
coll >>= (filter (not . someConditionHolds) . nestedCollection)
(mapcat (fn [y] (filter condition y)) x)
Others have already provided answers regarding how to solve the specified problem using FP concepts like using high order functions. If you analyse your thought process which lead to your existing code and compare that with FP solutions that other people have provided, you will find that whenever you think of - "having a variable to store the processed result" - it will lead to imperative OR step-by-step kind of solution and hence your Clojure code is mostly imperative as you thought about storing the result is a "vector variable". This kind of thinking won't allow you to apply FP concepts which are based on "evaluation of expression" and "solving problem by composition"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With