Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to accumulate results in a vector in Clojure? (Pure functional code seems ugly and verbose)

Tags:

...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?

like image 982
Alex D Avatar asked Jul 02 '12 22:07

Alex D


3 Answers

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)
like image 166
amalloy Avatar answered Oct 19 '22 23:10

amalloy


(mapcat (fn [y] (filter condition y)) x)
like image 28
Hendekagon Avatar answered Oct 20 '22 00:10

Hendekagon


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"

like image 21
Ankur Avatar answered Oct 19 '22 23:10

Ankur