Yes, even better :)
> 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.
Well, given that quick tests showed them to be within 5% of each-other
on the examples you gave, maybe both end up the same after Hotspot
does its magic. In the absence of any other reasons, I'm with you in
choosing the cleanest solution (i.e., the one you just posted).
-Jason
I'm not sure about let-> though. I think it might be doing a bit too much. I think it could be confusing that the 'x' in the first expression is a different 'x' than the 'x' in the second expression.
--
Dave
Maybe _ is appropriate?
=> (let-> _ (+ 1 2) (* 2 _) (+ _ 1))
7
=> (let-> _ [1 2 3] (map inc _) (reduce + _) (+ _ 3))
12
Or maybe ? ?
/mike.
Am 10.02.2009 um 23:34 schrieb Michael Reid:
> Maybe _ is appropriate?
>
> => (let-> _ (+ 1 2) (* 2 _) (+ _ 1))
> 7
> => (let-> _ [1 2 3] (map inc _) (reduce + _) (+ _ 3))
> 12
>
> Or maybe ? ?
I would not use _. _ is often used as "don't care" variable
name. And this is certainly not what we want here.. ;)
Unfortunately % doesn't work, but ? does.
(-> (+ 1 2) (* 2 ?) (+ ? 1))
(If we make the arg explicit, we can also redefine
-> totally....)
Sincerely
Meikel
Don't forget the wide variety of unicode symbols you have at your
disposal:
user=> (let-> ★ 2 (+ ★ 3) (- 10 ★ ) (map #(* ★ %) [2 3 4]))
(10 15 20)
Perfectly valid. Hard to miss that. (It is a star glyph incase this
doesn't come across).
Of course, this is matter of style and I would not want to enforce a
specific character. Also, doing so could lead to unhygienic clashes of
a nasty sort.
-M
> In any case, I vote for approaching Konrad Hinsen about putting this
> in clojure.contrib.macros when a naming convention is agreed on.
When I started clojure.contrib.macros, I intended it as a repository
for everybody's small macros that don't have any other obvious place.
So I don't mind anyone on the clojure.contrib group adding whatever
they seem fit.
BTW, I completely agree about the utility of the pipe macro, though I
can't make up my mind about which syntax I would prefer. I remember
that Paul Graham discusses such a macro in On Lisp, using "it" as the
parameter, but I forgot what is macro is called.
I have a similar operation in my monad library, which is called m-
chain. It expects a list of monadic operations that are functions of
one argument, the result being again a function of one argument
representing the composite operation. Using this operation in the
identity monad would yield something quite close to the pipe macro
being discussed, but as a function. Example:
(def my-pipe
(with-monad id
(m-chain #(filter odd? %) #(map inc %)))
(my-pipe (range 10))
The advantage of such an approach is that there is no new syntax
rule: the parameter placeholder is the well-known %. Another
advantage is that it can be written as a function. Of course, the
drawback is that all those functions cause a run-time overhead. Plus
a macro makes for shorter syntax, which is part of its purpose.
As you can see, I am fully undecided :-)
Konrad.
There seems to be a general consensus that some sort of explicit form
of threading would be useful. Below are snippets of other messages,
outlining the remaining points. I should point out that we are not
limited to one macro/function to handle our needs. For my own part, I
think as written let-> can meet most if not all needs (and I'll make a
case for it below), but there is certainly the option of writing a
family of threading functions/macros for more specialized purposes.
> BTW, I completely agree about the utility of the pipe macro, though I
> can't make up my mind about which syntax I would prefer. I remember
> that Paul Graham discusses such a macro in On Lisp, using "it" as the
> parameter, but I forgot what is macro is called.
I believe Graham calls these "anaphoric" macros (http://www.dunsmor.com/lisp/onlisp/onlisp_18.html
). His examples tend to use a specified identifier ("it"), though I
think the concept can be considered more broadly to be when a macro
creates a binding that the user can use. (As compared to the macro
creating a binding with a gensym that the user never sees). In
Graham's case, the identifier is fixed ahead of time. In the case of
let->, the user specifies the identifier.
> Somewhat along the lines of what Miekel says, if the special symbol is
> not user-defined, the macro could be named after the special symbol,
> e.g. x->, or, (keeping with the the "threading the needle" metaphor in
> ->'s docs) ndl->, or with the pipe metaphor, P->. The latter has the
> advantage that capitals aren't idiomatic for variable names in
> Clojure.
I dislike this strategy for two related reasons: (1) It cannont be
nested. Here is a pseudo-example:
(?-> 2 ... some computations ... (f (?-> ... I don't have access to
the outer "?" anymore... )))
(2) Shadowing user bindings. If I bind ? to something in my code (e.g.
(let [? (pred? foo)] (....))) the ?-> form shadows my definition,
preventing access.
By asking the user for an explicitly named identifier, both of these
issues can be avoided. This is not to say a ?-> form shouldn't be
allowed in the family of thread functions, just that let-> as written
as a distinct purpose.
> The advantage of such an approach is that there is no new syntax
> rule: the parameter placeholder is the well-known %. Another
> advantage is that it can be written as a function. Of course, the
> drawback is that all those functions cause a run-time overhead. Plus
> a macro makes for shorter syntax, which is part of its purpose.
On to functions and the % symbol. To begin with, I'm not sure there is
any additional runtime overhead of the functional version. Just as
many functions are created and called in them macro version. There are
differences. The first is stylistic: The macro saves key strokes for
longer pipes in the form of fewer # and/or (fn [x] ....) definitions.
Not a big point. The second issue is that the functional version can't
nest function literals. In the example, we see #(map inc %). That is
all well and good if you are using predefined single arity functions.
I tend to use function literals in (map) all the time. But the
following is invalid: #(map #(+ 1 %) %). Using explicit variables,
e.g. x, would give us (map #(+ 1 %) x), which is valid. I think this
is a very important benefit of the macro form.
> Overall I prefer Meikel's
> suggestion of just having the one macro -> with ? appearing in lists.
> For singular functions you wouldn't need to specify the ? so in most
> use-cases it would be unchanged, and for multiple arity calls it would
> be very explicit and clear what the desired operation was.
I'm not entirely clear on this proposal. It does prompt me to consider
a more complicated macro that inspects its arguments and provides
several behaviors under one name. If the first arg is a symbol, it
works like let->. If the first argument is a list, it works like ->.
If the first argument is the keyword :last it behaves like pipe. This
would be backwards compatible with the current -> implementation, but
also allow for the additional functionality described in this thread.
I think a good implementation would be to have the three behaviors in
their own macros, and have the super macro dispatch to the appropriate
version (mullti-macros anyone?).
Thoughts?
Am 12.02.2009 um 16:15 schrieb Mark Fredrickson:
> I dislike this strategy for two related reasons: (1) It cannont be
> nested. Here is a pseudo-example:
>
> (?-> 2 ... some computations ... (f (?-> ... I don't have access to
> the outer "?" anymore... )))
>
> (2) Shadowing user bindings. If I bind ? to something in my code (e.g.
> (let [? (pred? foo)] (....))) the ?-> form shadows my definition,
> preventing access.
These are valid points. And I think you are right in that (in case there
is an easy solution) there shouldn't be arbitrary restrictions.
> Thoughts?
I wouldn't make things to complicated. I think that eg. -> can be well
handled with this same macro by simple specifying a local at the
right place.
However I would not name it let-> since there is no binding vector
following. I would suggest pipe-as. This also explains, that the given
local is bound to different things during the different stages of the
pipe.
What I definitively want is the non-seq => (non-seq local) translation.
That's really nice.
For the function vs. let discussion: I use anonymous functions a lot in
macros to package things up in a closure and then expand the macro to
some code calling a driver function passing said anonymous function at
runtime. However, in this case it is not really needed. Let is
sufficient. And since each function generates a new classfile, while
this does not happen for let, I would stick with the latter.
user=> (defmacro pipe-as
([_ expr] expr)
([local expr form & more]
`(let [~local ~expr]
(pipe-as ~local
~(if (seq? form) form (list form local))
~@more))))
#'user/pipe-as
user=> (pipe-as x (+ 1 2) (* 3 x) inc)
10
> (mullti-macros anyone?).
user=> (defmulti foo* (fn [x a b] x))
#'user/foo*
user=> (defmethod foo* 'add [_ a b] `(+ ~a ~b))
#<MultiFn clojure.lang.MultiFn@e6f711>
user=> (defmethod foo* 'sub [_ a b] `(- ~a ~b))
#<MultiFn clojure.lang.MultiFn@e6f711>
user=> (defmacro foo [x a b] (foo* x a b))
#'user/foo
user=> (macroexpand-1 '(foo sub 1 2))
(clojure.core/- 1 2)
user=> (macroexpand-1 '(foo add 1 2))
(clojure.core/+ 1 2)
;)
Sincerely
Meikel