I find loop recur kind of hard to follow sometimes....

I was browsing through this pdf the other day:
http://www.cs.umbc.edu/331/resources/papers/Evolution-of-Lisp.pdf

And I found the tail recursive definition of common lisp/scheme style
do.
Thoroughly intrigued, I implemented it:

(defmacro cl-do* [var-init-step* [test result] & body]
  (let [pvis (partition 3 var-init-step*)
        vars (map first pvis)
        inits (map second pvis)
        steps (map #(nth %1 2) pvis)]
    `(loop [~@(interleave vars inits)]
       (cond ~test ~result
              :else (do ~...@body
                       (recur ~...@steps))))))

Examples would be:

(let [myseq "the quick brown fox"]
  (cl-do* [s myseq (apply str (interpose " " (rest (re-seq #"\w+"
s))))]
      [(= (first (re-seq #"\w+" s)) "fox") "Yeah!"]))
;;not exactly the same as the desired implementation, but similar.

Or..

(let [lst [0 1 2 3 4 5 6 7]]
  (cl-do* [i 0 (dec i),
           fr (first lst) (first rst),
           rst (rest lst) (rest rst),
           acc [] (conj acc (* i fr))]
      [(empty? rst) acc]
 (println (* i fr))))

In some cases where you are incrementing a lot of counters, loop/recur
can get kind of heinous in terms of readability (Not that do* is
exactly a pretty princess either).

I wonder If I would be strung up by the thumbs for using this
monstrosity?

:-)

-Jon

On Sep 18, 2:12 am, Adrian Cuthbertson <adrian.cuthbert...@gmail.com>
wrote:
> There's also re-split in str-utils in clojure.contrib;
>
> (use 'clojure.contrib.str-utils)
> (re-split #"\s" "The quick brown fox")
> => ("The" "quick" "brown" "fox")
>
> You can then use all the good clojure collection functions;
>
> (def words (re-split #"\s" "The quick brown fox"))
> (some #{"brown"} words)
> => "brown"
> (some #{"foo"} words)
> => nil
> (nth words 2)
> => "brown"
> (str-join "," words)
> => "The,quick,brown,fox"
> ..., etc.
>
> (There's also some good stuff in str-utils2 worth looking at).
>
> The "some" function above needs some explanation;
> It takes a predicate, applies it to each element of the collection and
> returns the first logical true (not nil) value if found otherwise nil.
> The #{"brown"} is a set containing "brown", but sets, like maps are a
> function of their elements, e.g;
> (#{"a" "b"} "b")
> => "b"
> and hence acts as a predicate for testing each element in words in the
> some function.
>
> There are also endless things you can do with map and reduce;
>
> (map (fn [w] (.toUpperCase w)) words)
> => ("THE" "QUICK" "BROWN" "FOX")
> same as;
> (map #(.toUpperCase %) words)
> => ("THE" "QUICK" "BROWN" "FOX")
>
> Say you wanted to encode a "record" of stems from a map of word/stem's;
>
> (let [stems {"the" :th "quick" :qk "brown" :br "fox" :fx}]
>   (reduce (fn [rec x] (conj rec (stems (.toLowerCase x)))) [] words))
> => [:th :qk :br :fx]
>
> So then given that background and that you're looking for a clojure
> approach rather than a specific solution to your "brown" split
> example, lets elaborate on a possible approach;
>
> You could "abstract" the requirement to something like;
> "Given a collection and a predicate to find an element in that
> collection, return two collections - up to the split element and the
> remaining elements (including the split element)."
>
> (defn split-coll
> "Doc as above."
> [coll pred]
> (reduce (fn [[l r] x]
>    (if-not (empty? r) [l (conj r x)]
>       (if (pred x) [l [x]] [(conj l x) r]))) [[][]] coll))
>
> (split-coll '(:a :b :c) #(= % :b))
> => [:a] [:b :c]]
> (split-coll '(:a :b :c) #(= % :x))
> => [[:a :b :c] []]
> (split-coll '(:a :b :c) #(= % :a))
> => [[] [:a :b :c]]
>
> and of course;
> (split-coll words #(= % "brown"))
> => [["The" "quick"] ["brown" "fox"]]
>
> finally, you could create the specific sentence function;
>
> (defn split-sentence [sentence word] (split-coll (re-split #"\s"
> sentence) #{word}))
> (split-sentence "The quick brown fox" "brown")
> => [["The" "quick"] ["brown" "fox"]]
>
> and you can also use de-structuring to get exactly what you were
> originally looking for;
> (let [[l r] (split-sentence "The quick brown fox" "brown")] r)
> => ["brown" "fox"]
>
> To explain the above reduce function ; we start with two empty vectors
> in a vector [[][]] and then looping through the collection on each
> iteration that vector is passed to the fn as the first arg. We
> de-structure that into l and r for the left and right vectors. If r is
> not empty we've already found the element, so conj x into the r
> vector. Otherwise if x satisfies the pred, conj it to the r vector,
> else we haven't got there yet, so conj it to the l vector.
>
> For completeness, I should show a loop/recur alternative as this is a
> totally idiomatic clojure technique, but should be used for functional
> goals, not just trying to emulate imperative ways.
>
> (defn split-coll
> [coll pred]
> (loop [c coll [l r] [[][]]]
>   (if (nil? (seq c)) [l r]
>   (recur (next c) (let [x (first c)]
>            (if (nil? c) [l r] (if-not (empty? r) [l (conj r x)] (if
> (pred x) [l [x]] [(conj l x) r]))))))))
>
> (split-coll words #(= % "brown"))
> => [["The" "quick"] ["brown" "fox"]]
>
> It is also important to master loop/recur as you can use this more
> easily than reduce for complex looping constructs, nested loops,
> performant techniques, etc.
>
> I hope that gives you a feel of the kinds of things you can do with
> functional idioms and clojure and also a general approach to
> functional programming. Takes a while to pickup all these tips but
> you'll never look back.
>
> Rgds, Adrian.
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to