Hacker News new | past | comments | ask | show | jobs | submit login

We don't address this in the post, but adding :while conditions is quite a fun challenge.

This is the best I could come up with (in Emacs Lisp): https://gist.github.com/4356261 . It's rather hacky :D

Darius Bacon (abecedarius) came up with a beautiful alternative formulation that handles :while conditions seamlessly. Here's my port of his idea into Emacs Lisp: https://gist.github.com/4380866

I'd be very happy to see other versions :)




"We don't address this in the post, but adding :while conditions is quite a fun challenge."

Eurgh, I hate the common Clojure habit (you see it in for, doseq, etc. loops, and in domonad, which is annoying in other ways) of abusing binding vectors to add in extra stuff that doesn't, in itself, have anything to do with binding names to values.

If you use a very slightly modified version of the macro defined at [1] (modified to use algo.monads), you can do list comprehensions like this:

     > (monads/with-monad
             monads/sequence-m
             (mdo x <- [1 2 3]
                  y <- [3 4 5]
                  let added = (+ x y)
                  (monads/m-result [x y added])))
     ([1 3 4] [1 4 5] [1 5 6] [2 3 5] [2 4 6] [2 5 7] [3 3 6] [3 4 7] [3 5 8])
The "let added = (+ x y)" is an unnecessary flourish: it would have worked just as well to wrap what followed in (let [added (+ x y)] ...). Likewise (as against domonad), you can just use regular old if; no need for :if.

1: https://bitbucket.org/kenko/macroparser/src/7a492ef941db38db...


abusing binding vectors to add in extra stuff that doesn't, in itself, have anything to do with binding names to values

I'm not familiar with the Clojure examples you mention, but it's interesting that Common Lisp provides directives like &rest for similar purposes.


Sure, and Clojure has '&' in function argument lists for rest arguments.

Here's a Clojure example:

user=> (for [x [0 1 2 3 4 5] :let [y (* x 3)] :when (even? y) z [1 2] [y z]) ([0 1] [0 2] [6 1] [6 2] [12 1] [12 2])

The binding vector for 'for' takes alternating name/value pairs, except it also allows these special keywords whose semantics affect the environment of the subsequent bindings, and whether the bindings are even evaluated, as well as the environment of the body expression (and whether it's evaluated). This strikes me as kind of ugly. The same can be done like this, using that mdo macro:

    > (monads/with-monad monads/sequence-m
                     (mdo x <- [0 1 2 3 4 5]
                          (let [y (* x 3)]
                             (when (even? y)
                              (mdo z <- [1 2]
                                   (monads/m-result y))))))
with identical results, or, as above, like this:

    > (monads/with-monad monads/sequence-m
                         (mdo x <- [0 1 2 3 4 5]
                                   let y = (* x 3)
                                   (when (even? y)
                                    (mdo z <- [1 2]
                                         (monads/m-result [y z])))))
Of course, if you think of a macro like "for" of consisting of a binding vector followed by an expression (or a sequence wrapped in an explicit do), there's no other place for the whens and lets that you want to use to control evaluation of (the environments of) the various bindings than the binding vector itself.

If I'm not mistaken, Common Lisp's loop macro (e.g.) isn't like this---that is, it establishes its own little mini language, instead of mimicking forms found elsewhere in the language (the way Clojure's for loop control constructs are all stuffed into a "binding vector" similar to what might be found in a defn). Now it's true that something like "let y = (* x 3)" is not very Lispy at all. But neither, I think, is having something like ":when (even? y)", a sequence of two elements in a flat vector, actually govern bindings and execution of things that follow it but which it doesn't enclose.


OK, I get you. The inline :WHEN in that FOR form makes me go "what?" as well. The syntax feels halfsie to me; it abandons regularity but doesn't want to go full mini-language a la LOOP, so it ends up in a syntactic no man's land. It doesn't feel very composable to me, either — does it scale to more complex expressions? I'm biased, though. The following seems far better to me; slightly lower-level, but clearer and more composable:

  (loop for x in '(0 1 2 3 4 5) for y = (* x 3) when (evenp y) append
    (loop for z in '(1 2) collect (list y z)))

  => ((0 1) (0 2) (6 1) (6 2) (12 1) (12 2))
The reason why LOOP has keywords for all its loopy things is that the set "stuff you often want to do while iterating" is too varied to be supported by an entirely regular syntax. Of course it can all be done with the utilities of the underlying Lisp (which is all that LOOP expands to), but often not as concisely and especially not as idiomatically. I like crossing out of universal Lisp into Loopland to do those little plumbing jobs. Because LOOP is so limited and specific, I know why such a construct is there and can grok very quickly what I'm looking at and what it evaluates to. Having such forms stand out from the backdrop of the far more powerful language the main program is written in is a major clarity win. (Good god, I sound like someone arguing against Lisp syntax altogether!)

For the same reasons (concision, idiom, and clarity), forgive me but I don't like the monads/with-monad versions you've given. "monads/with-monad monads/sequence-m" is much too verbose — I want an operation this simple to be lightweight. It doesn't feel idiomatic to me for looping and pairing things, though that may just be my ignorance. And it feels like some powerful and general infrastructure is being invoked to do something whose triviality I would rather see the code bring out. Looping is the canonical example, I think, of something that one does so often and has such repetitive-yet-assorted patterns that a little language is perfect for the job. But I'm biased, as I said.

By the way, I've been puzzling over that FOR form trying to make out how it ends up producing a Cartesian product out of '(0 1 2 3 4 5) and '(1 2). In CL, if you make the bindings parallel like that, as in (loop for x in foo for y in bar...), it means "take an x from foo and a y from bar and keep going as long as they're both producing new values". So to get a Cartesian product, I had to make an explicit nested loop in my translation above. Both idioms are useful, but I use the parallel one quite a bit more than the nested one. Does FOR handle both?

p.s. You're missing a square bracket after [1 2]. I was puzzling even more before I realized that :)


"p.s. You're missing a square bracket after [1 2]. I was puzzling even more before I realized that :)"

Whoops!

"Both idioms are useful, but I use the parallel one quite a bit more than the nested one. Does FOR handle both?"

Not directly---to get a parallel product, you would use something like:

    (map vector '(0 1 2 3 4 5) '(1 2))




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: