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 -~----------~----~----~----~------~----~------~--~---