Search code examples
clojure

`for` as a function instead of a macro


I'd like to have a forall function that acts like for but takes a list of sequences as inputs.

(forall (fn [arr]
            (prn arr))
        (range 10)
        (range 10)
        (range 10))
=> 
; [0 0 0]
; [0 0 1]
; [0 0 2]
;  ....
; [9 9 9]

Is there a good way to write this?


Solution

  • I suppose you want to map over the cartesian product of the input sequences, right? If so, you can create a lazy sequence of the cartesian product using for example the clojure.math.combinatorics/cartesian-product function from the clojure.math.combinatorics library and then map over that.

    (defn forall [f & seqs]
      (map f (apply clojure.math.combinatorics/cartesian-product seqs)))
    

    This is a function that works more or less like the for macro when there are multiple sequences. Here is an example of calling it, and the result that you would obtain:

    (forall (fn [arr]
              [:element arr])
            (range 3)
            (range 3)
            (range 3))
    ;; => ([:element (0 0 0)] [:element (0 0 1)] [:element (0 0 2)] [:element (0 1 0)] [:element (0 1 1)] [:element (0 1 2)] [:element (0 2 0)] [:element (0 2 1)] [:element (0 2 2)] [:element (1 0 0)] [:element (1 0 1)] [:element (1 0 2)] [:element (1 1 0)] [:element (1 1 1)] [:element (1 1 2)] [:element (1 2 0)] [:element (1 2 1)] [:element (1 2 2)] [:element (2 0 0)] [:element (2 0 1)] [:element (2 0 2)] [:element (2 1 0)] [:element (2 1 1)] [:element (2 1 2)] [:element (2 2 0)] [:element (2 2 1)] [:element (2 2 2)])
    

    Note that in your example code, you call the prn function which is side-effectful, whereas the for-macro produces a lazy sequence. Combining laziness with side-effects is often discouraged because the side-effect may not happen when you would expect it to happen unless you know how lazy sequences work.

    If you don't want to pull in the clojure.math.combinatorics library, you can easily implement your own cartesian product function, e.g.

    (defn cart2 [a b]
      (for [x a
            y b]
        (conj x y)))
    
    (defn my-cartesian-product [& arrs]
      (reduce cart2 [[]] arrs))