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

Let's make it simple.) There is classic homework code in two different dialects of Lisp:

  (define (cross xs ys)
      (cond ((or (null? xs) (null? ys)) '())
            ((atom? xs) (cons (list xs (car ys)) (cross xs (cdr ys))))
            (else (append (cross (car xs) ys) (cross (cdr xs) ys)))))

  (defun cross (xs ys)
      (cond ((or (null xs) (null ys)) nil)
            ((atom xs) (cons (list xs (car ys)) (cross xs (cdr ys))))
            (t (append (cross (car xs) ys) (cross (cdr xs) ys)))))
Could you, please, provide the equivalent code in Clojure?



Well, you could do it this way:

    (defn cross2 [xs ys]
     (cond (or (and (sequential? xs) (empty? xs)) (empty? ys)) '()
           (not (sequential? xs)) (cons (list xs (first ys)) (cross2 xs (rest ys)))
           true (concat (cross2 (first xs) ys) (cross2 (rest xs) ys))))
which is pretty much exactly homologous, allowing for the detail that you can't ask if an atom is empty? in Clojure, cond (Arc-like) takes alternating conditions and consequents rather than condition-consequent pairs, and the spellings of the list operations no longer refer to IBM 709 machine instructions. Also, it works on any kind of sequences, not just lists, with of course a punishing performance overhead on sequences whose `rest` operation is slow.

But I would argue that this interface is poorly designed, since you can say (cross2 '(a b c) '(1 2 3)) or (cross2 'a '(1 2 3)) but not (cross2 '(a b c) '1), and worse, (cross2 '(a (b c) d) '(1 2 3)) implicitly flattens the (b c) into individual items, which is probably a latent bug rather than desired behavior. So I would argue for writing it in this form instead:

    (defn sc [x ys]  ; scalar cross
          (if (empty? ys) '() 
              (cons (list x (first ys)) (sc x (rest ys)))))

    (defn cross [xs ys]
          (if (or (empty? xs) (empty? ys)) '()
              (concat (sc (first xs) ys) (cross (rest xs) ys))))
which avoids those irregularities and makes the code easier to understand by removing misleading false symmetries.

Except really, if this isn't a homework problem, I think you should write it like this in any of these three Lisps:

    (defn cross [xs ys]
          (map (fn [x] (map (fn [y] (list x y)) ys)) xs))


With the last two lines you won.) My point was that to make correct translation one must use (recur ...) which will mess everything up.

One more subtle thing: your solution produces (((a 1) (a 2)) ((b 1) (b 2)) ((c 1) (c 2))) while the contract was to produce "list of all possible pairs".


Oh, that extra nesting was stupid of me! Thank you. It should have been

   (defn cross [xs ys]
          (mapcat (fn [x] (map (fn [y] (list x y)) ys)) xs))
and maybe in CL one would prefer

   (defun cross (xs ys) 
          (loop for x in xs
                appending (loop for y in ys
                                collect (list x y))))
which of course has no equivalent in Clojure, Scheme, or really any other language I can think of.

I'm not sure I agree on (recur...). You would need to use (recur...) if you were translating tail-recursive code that iterated over something other than a data structure and didn't produce new live objects on every iteration. But the code you gave wasn't tail-recursive, and what it iterated over was a data structure, and every iteration produced live objects that can't be garbage-collected. Even if you rewrote it to be tail-recursive, it wouldn't run out of stack for reasonably-sized output lists anyway; and for unreasonably-sized output lists, it would be likely to run out of heap for the output before it ran out of stack. I'm interested to hear if you manage to get it to stack-overflow. (It seems likely to be possible, but perhaps a bit of a challenge.)

Regardless, I don't think it's reasonable to claim that languages that don't have tail-call elimination — which I suspect you may be on the point of doing — aren't Lisps. Many popular Lisps have had TCE, but many more Lisps haven't, and the CL standard doesn't require it.


> ... which of course has no equivalent in Clojure, Scheme, or really any other language I can think of.

Python, for example: def cross(xs, ys): return [[x, y] for y in ys for x in xs]

LOOP in disguise :) (at least to my (very possibly faulty) understanding.)


A number of languages have listcomps that can do this, and which are actually more useful for this than CL's LOOP macro, but the thing I meant to point at was the APPENDING bit. I guess (loop for x in xs append (loop for y in (f x) collect y)) is awfully similar to [y for x in xs for y in f(x)], though.


Including Clojure for those who might not know:

    (defn cross [xs ys]
      (for [x xs, y ys]
        [x y]))




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

Search: