Dax Fohl
Dax Fohl

Reputation: 10781

define `fori` macro / function in clojure

I want to be able to loop through a collection but maintain a counter with that loop. I managed to create e.g.

(for [[email i] (map vector emails (range))]
  ...)

to do this, but ideally I'd prefer a fori macro (unless a function is possible but my gut says it's gotta be a macro) that lets me do equivalently

(fori [email i emails]
  ...)

or

(fori [[email i] emails]
  ...)

I've no experience with macros, and looking at the source for the for macro it looks pretty intimidating. Any help?

Super-cool would be a macro that follows the for syntax and allows e.g.

(fori [email i emails
       line j (lines email)]
  ...)

Upvotes: 0

Views: 118

Answers (2)

amalloy
amalloy

Reputation: 92057

You can write a macro that expands to for, but I'm not really convinced there's much point. There are numerous built-ins for exactly this purpose, and the fori syntax doesn't really read very well. At the very least it should look like

(fori [[email i] emails, 
       [line j] (lines email)]
  ...)

But if you really wanted to write it exactly as you said, it's certainly not impossible:

(defmacro fori [bindings body]
  (letfn [(clauses [bindings]
            (lazy-seq
              (cond (empty? bindings) ()
                    (keyword? (first bindings)) (concat (take 2 bindings)
                                                        (clauses (drop 2 bindings)))
                    :else (let [[name index value] (take 3 bindings)]
                            (list* [index name] `(map-indexed vector ~value)
                                   (clauses (drop 3 bindings)))))))]
    `(for [~@(clauses bindings)]
       ~body)))

user> (macroexpand-1 '(fori [email i xs, 
                             :when (even? i)
                             line j (lines email)]
                        [i line]))
(for [[i email] (map-indexed vector xs)
      :when (even? i)
      [j line] (map-indexed vector (lines email))]

  [i line])

Upvotes: 2

Diego Basch
Diego Basch

Reputation: 13079

(defn fori [coll]
  (map-indexed (fn [idx itm] [idx itm]) coll))

Is probably what you want. e.g.

(fori [:a :b :c :d])

produces

([0 :a] [1 :b] [2 :c] [3 :d])

BTW, for is not a loop but creates a list comprehension. If you're looking to do something with side effects inside a loop you should look into doseq.

Upvotes: 0

Related Questions