noahlz
noahlz

Reputation: 10311

Can I use the clojure 'for' macro to reverse a string?

This is a follow up to my question "Recursively reverse a sequence in Clojure".

Is it possible to reverse a sequence using the Clojure "for" macro? I'm trying to better understand the limitations and use-cases of this macro.

Here is the code I'm starting from:

((defn reverse-with-for [s] 
    (for [c s] c))

Possible?

If so, I assume the solution may require wrapping the for macro in some expression that defines a mutable var, or that the body-expr of the for macro will somehow pass a sequence to the next iteration (similar to map).

Upvotes: 4

Views: 762

Answers (3)

Goran Jovic
Goran Jovic

Reputation: 9508

Clojure for macro is being used with arbitrary Clojure sequences.

These sequences may or may not expose random access like vectors do. So, in general case, you do not have access to the last element of a Clojure sequence without traversing all the way to it, which would make making a pass through it in reverse order not possible.

I'm assumming you had something like this in mind (Java-like pseudocode):

for(int i = n-1; i--; i<=0){
   doSomething(array[i]);
}

In this example we know array size n in advance and we can access elements by its index. With Clojure sequences we don't know that. In Java it makes sense to do that with arrays and ArrayLists. Clojure sequences are however much more like linked lists - you have an element, and a reference to next one.

Btw, even if there were a (probably non-idiomatic)* way to do that, its time complexity would be something like O(n^2) which is just not worth the effort compared to much easier solution in the linked post which is O(n^2) for lists and a much better O(n) for vectors (and it is quite elegant and idiomatic. In fact, the official reverse has that implementation).

EDIT:

A general advice: Don't try to do imperative programming in Clojure, it wasn't designed for it. Although many things may seem strange or counter-intuitive (as opposed to well known idioms from imperative programming) once you get used to the functional way of doing things it is a lot, and I mean a lot easier.

Specifically for this question, despite the same name Java (and other C-like) for and Clojure for are not the same thing! First is an actual loop - it defines a flow control. The second one is a comprehension - look at it conceptually as a higher function of a sequence and a function f to be done for each of its element, which returns another sequence of f(element) s. Java for is a statement, it doesn't evaluate to anything, Clojure for (as well as anything else in Clojure) is an expression - it evaluates to the sequence of f(element) s.

Probably the easiest way to get the idea is to play with sequence functions library: http://clojure.org/sequences. Also, you can solve some problems on http://www.4clojure.com/. The first problems are very easy but they gradually get harder as you progress through them.

*As shown in Alexandre's answer the solution to the problem in fact is idiomatic and quite clever. Kudos for that! :)

Upvotes: 7

Vladimir Matveev
Vladimir Matveev

Reputation: 127751

First of all, as Goran said, for is not a statement - it is an expression, namely sequence comprehension. It construct sequences by iteration through other sequences. So in the form it is meant to be used it is pure function (without side-effects). for can be seen as enhanced map infused with filter. Because of this it cannot be used to hold iteration state as e.g. reduce do.

Secondly, you can express sequence reversal using for and mutable state, e.g. using an atom, which is rough equivalent (not taking into account its concurrency properties) of java variable. But doing so you are facing several problems:

  1. You are breaking main language paradigm so you will definitely get worse looking and behaving code.
  2. Since all clojure mutable state cells are designed to be thread-safe, they all use some kind of illegal concurrent modification protection, and there is no ability to remove it. Consequently, you will get poorer performance characteristics.
  3. In this particular case, like Goran said, sequences are one of the wide-used Clojure abstractions. For example, there are lazy sequences, which could be potentially infinite, so you just cannot walk them to the end. You certainly will have difficulties trying to work with such sequences with imperative techniques.

So don't do it, at least in Clojure :)

EDIT: I forgot to mention it. for returns lazy sequence, so you have to evaluate it in some way in order to apply all state mutations you do in it. Another reason not to do so :)

Upvotes: 3

Alex Jasmin
Alex Jasmin

Reputation: 39496

Here's how you could reverse a string with for:

(defn reverse-with-for [s] 
    (apply str
        (for [i (range (dec (count s)) -1 -1)]
            (get s i))))

Note that this code is mutation free. It's the same as:

(defn reverse-with-map [s] 
    (apply str
        (map (partial get s) (range (dec (count s)) -1 -1))))

A simpler solution would be:

(apply str (reverse s))

Upvotes: 5

Related Questions