Hi,

Am 28.05.2009 um 20:11 schrieb Sean Devlin:

Without discussing a specific application, I think what you're looking
for can be achieved by normal macros and functions in Clojure.  I'll
try implement the collect method in Clojure, and hopefully that will
explain things.

Let's start by creating a collect function.

(defn collect
 [pred coll]
 (loop [remaining coll
        output []]
   (if (empty? remaining)
     output
     (recur (rest remaining)
       (conj output (pred (first remaining)))))))

This can be written more concise with reduce:

(defn collect
  [f coll]
  (reduce #(conj %1 (f %2)) (empty coll) coll))

Whenever you do loop/recur and return one argument
when the input is used up, you probably can turn it into
a nice reduce.

And a quick test shows

(collect (fn[x](* 2 x)) [1 2 3])
=>[2 4 6]

Notice that the clojure collect takes a function, pred as an
argument.  The s-expression (pred (first remaining)) automatically
applies the right function do to the *pure genius* that is eval.  It's
pretty slick.

Here's my best attempt at writing an all-purpose collect-m macro.
It's ugly, and I don't like it.

(defmacro collect-m
 [coll & body]
 `(let [~'pred (fn [~'elemen...@body)]
    (loop [~'remaining ~coll
        ~'output []]
   (if (empty? ~'remaining)
     ~'output
     (recur (rest ~'remaining)
       (conj ~'output (~'pred (first ~'remaining))))))))

(collect-m [1 2 3] (* 2 element))
=>[2 4 6]

Please don't write a macro like that! This captures
the names you chose for your locals. Imagine a
call like this (collect-m (do-something-with-another pred) ...),
where the pred comes from outside the macro. This
call will fail, because you captured pred in your macro
expansion. Use auto-gensym via the # suffix.

`(let [pred# ...])

This will generate and new symbol like pred__AUTO_412
or something similar, which is unlikely to be used by the
surrounding code, which calls the macro.

The parameter problem can be solved by providing an
additional parameter, which is then used in the function
argument vector.

Notice that the term element is fixed as a parameter name.  I assume
that there is ONLY on input in the function.  The macro is ugly to
read.  Maybe someone else can do better.

Revisiting the function version, notice:

(collect (fn[x](* 2 x)) [1 2 3])

Feels very similar to

[1, 2, 3].collect(){|x| 2*x}

You have much more control of the function.

The main point I'm getting at is that I don't think the blockfn macro
approach is the way to go.  Either write a function that take
functions, or use a traditional macro.

I find this defblockfn actually quite interesting. A lot of macros
boil down to wrap some body in a thunk and pass it to a driver
function, which does the work, which can be defered to runtime.
This approach has several advantages:

- smaller code size since the macro expands to a simply function call
- changing the logic in the driver function does not require recompilation
  of the macro users.
- the driver function is easier to write than the macro

So maybe 95% of my macros are paired with a function.

YMMV of course...

Sincerely
Meikel

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to