I like that implementation. The recursive call makes it much cleaner. A slight improvement (?) yet:
(defmacro let-> "Provide a name that will be bound to the result of the first form. For each additional form, the variable will be used in the evaluation, and then rebound to the result of the form." ([varname start] start) ([varname start & forms] `(let [~varname ~start] (let-> ~varname ~...@forms)))) As to fn vs. let. I originally started with nested lets, but was getting bogged down in more boiler plate (the recursive call solves the problem). Then I remembered the equivalency of these two forms: Gorilla=> ((fn [x y] (+ x y)) 1 2) 3 Gorilla=> (let [x 1 y 2] (+ x y)) 3 But just because the second can be expanded into the first, doesn't mean that is what is clojure is doing behind the scenes (and a quick investigation shows it is not). I could argue it either way on the efficiency angle. On one hand it seems like a function call would be more expensive than pushing variables on the stack that clojure uses to keep track of bindings. On the other hand, I'm sure HotSpot can work wonders with tiny little inline-able functions in repeated code. My vote is for the cleaner implementation. -Mark On Feb 9, 2009, at 5:40 PM, Jason Wolfe wrote: > > Nice, I would definitely use this! > > One comment/question: would it be more efficient to expand to a bunch > of nested "let" statements, rather than nested function calls? I'm > not sure how Clojure handles "let" under the hood, or how Hotspot > inlining works here. Here's my version: > > (defmacro let-> > "Provide a name that will be bound to the result of the first form. > For each additional form, the variable will be > used in the invocation, and then rebound to the result of the > form." > [varname start & forms] > (if (empty? forms) > start > `(let [~varname ~start] > (let-> ~varname ~...@forms)))) > > In a few quick timing tests I see no real difference, so maybe it > doesn't matter. > > -Jason > > > > > On Feb 9, 2:31 pm, Mark Fredrickson <mark.m.fredrick...@gmail.com> > wrote: >> This inspired me to write a general purpose version: >> >> (defmacro let-> >> "Provide a name that will be bound to the result of the first >> form. >> For each additional form, the variable will be >> used in the invocation, and then rebound to the result of the >> form." >> [varname start & forms] >> (let [fn-args `[~varname] >> wrapped (map (fn [form] `(fn ~fn-args ~form)) forms)] >> (reduce >> (fn [acc func] `(~func ~acc)) >> start >> wrapped))) >> >> Some examples: >> >> Gorilla=> (let-> x (+ 1 2) (* 2 x) (+ x 1)) >> 7 >> Gorilla=> (let-> x [1 2 3] (map inc x) (reduce + x) (+ x 3)) >> 12 >> Gorilla=> (macroexpand-1 (quote (let-> x [1 2 3] (map inc x) >> (reduce + >> x) (+ x 3)))) >> ((clojure.core/fn [x] (+ x 3)) ((clojure.core/fn [x] (reduce + x)) >> ((clojure.core/fn [x] (map inc x)) [1 2 3]))) >> >> I tried a more general version that allowed any binding form and >> multiple variables in the first position, but it never seemed as >> clean >> as the single variable version, so I reverted to the simpler case. I >> also considered using function literals and % notation, but decided >> that since you couldn't nest them and map/reduce/etc with function >> literals are very common, using an explicitly bound var would be the >> best option. >> >> The doc string could probably be more clear. Suggestions and >> improvements welcome. >> >> -Mark >> >> On Feb 7, 2009, at 6:27 PM, MattH wrote: >> >> >> >> >> >>> Hi, >>> I want to suggest a "pipe" macro for dealing with collection >>> streams, >>> as a one-line variation on the built in "->" macro. >> >>> Instead of writing a nested stream like this: >>> ; "Take the first 3 elements of the odd numbers in the range 1 to >>> 20" >>> (take 3 (filter odd? (range 1 20))) >> >>> you can write this: >>> ; "Take the range 1 to 20, filter the odd numbers, then take the >>> first 3" >>> (pipe (range 1 20) (filter odd?) (take 3)) >> >>> I think both forms have their place. The piped form seems useful >>> when >>> you want to emphasise the collection, or when the chain gets very >>> long. It's also close to how I'd draw the chain on a whiteboard or >>> describe it in speech, and helps me to reason about the chain from >>> left to right in small "chunks". >> >>> The difference with "->" is this: >>> (-> x (f a b)) => (f x a b) >>> (pipe x (f a b)) => (f a b x) >> >>> The definition is the equivalent of the "->" macro (defined in >>> core.clj, line 984), swapping "~@(rest form) ~x" to "~x ~@(rest >>> form)": >> >>> (defmacro pipe >>> "Threads the expr through the forms. Inserts x as the >>> last item in the first form, making a list of it if it is not a >>> list already. If there are more forms, inserts the first form as >>> the >>> last item in second form, etc." >>> ([x form] (if (seq? form) >>> `(~(first form) ~@(rest form) ~x) >>> (list form x))) >>> ([x form & more] `(pipe (pipe ~x ~form) ~...@more))) >> >>> I've seen pipe written as a function with the use of reader >>> macros, or >>> as macros built from scratch. I'm putting this one forward because >>> the >>> tiny change in intention from "->" is reflected as a tiny delta in >>> the >>> code. >> >>> Cheers, >>> Matt >> >> - Mark Fredrickson >> mark.m.fredrick...@gmail.comhttp://www.markmfredrickson.com > > - Mark Fredrickson mark.m.fredrick...@gmail.com http://www.markmfredrickson.com --~--~---------~--~----~------------~-------~--~----~ 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 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 -~----------~----~----~----~------~----~------~--~---