سؤال

If I understand correctly Clojure can return lists (as in other Lisps) but also vectors and sets.

What I don't really get is why there's not always a collection that is returned.

For example if I take the following code:

(loop [x 128]
  (when (> x 1)
    (println x)
    (recur (/ x 2))))

It does print 128 64 32 16 8 4 2. But that's only because println is called and println has the side-effect (?) of printing something.

So I tried replacing it with this (removing the println):

(loop [x 128]
  (when (> x 1)
    x
    (recur (/ x 2))))

And I was expecting to get some collecting (supposedly a list), like this:

(128 64 32 16 8 4 2)

but instead I'm getting nil.

I don't understand which determines what creates a collection and what doesn't and how you switch from one to the other. Also, seen that Clojure somehow encourages a "functional" way of programming, aren't you supposed to nearly always return collections?

Why are so many functions that apparently do not return any collection? And what would be an idiomatic way to make these return collections?

For example, how would I solve the above problem by first constructing a collection and then iterating (?) in an idiomatic way other the resulting list/vector?

First I don't know how to transform the loop so that it produces something else than nil and then I tried the following:

(reduce println '(1 2 3))

But it prints "1 2nil 3nil" instead of the "1 2 3nil" I was expecting.

I realize this is basic stuff but I'm just starting and I'm obviously missing basic stuff here.

(P.S.: retag appropriately, I don't know which terms I should use here)

هل كانت مفيدة؟

المحلول

A few other comments have pointed out that when doesn't really work like if - but I don't think that's really your question.

The loop and recur forms create an iteration - like a for loop in other languages. In this case, when you are printing, it is indeed just for the side effects. If you want to return a sequence, then you'll need to build one:

(loop [x 128                                                                                                        
       acc []]                                                                                                      
  (if (< x 1)                                                                                                       
    acc                                                                                                             
    (recur (/ x 2)                                                                                                  
           (cons x acc))))                                                                                          

=> (1 2 4 8 16 32 64 128)

In this case, I replaced the spot where you were calling printf with a recur and a form that adds x to the front of that accumulator. In the case that x is less than 1, the code returns the accumulator - and thus a sequence. If you want to add to the end of the vector instead of the front, change it to conj:

(loop [x 128                                                                                                    
       acc []]                                                                                                  
  (if (< x 1)                                                                                                   
    acc                                                                                                         
    (recur (/ x 2)                                                                                              
           (conj acc x))))                                                                                      

=> [128 64 32 16 8 4 2 1] 

You were getting nil because that was the result of your expression -- what the final println returned.

Does all this make sense?

reduce is not quite the same thing -- it is used to reduce a list by repeatedly applying a binary function (a function that takes 2 arguments) to either an initial value and the first element of a sequence, or the first two elements of the sequence for the first iteration, then subsequent iterations are passed the result of the previous iteration and the next value from the sequence. Some examples may help:

(reduce + [1 2 3 4])                                                                                            

10  

This executes the following:

(+ 1 2) => 3
(+ 3 3) => 6
(+ 6 4) => 10

Reduce will result in whatever the final result is from the binary function being executed -- in this case we're reducing the numbers in the sequence into the sum of all the elements.

You can also supply an initial value:

(reduce + 5 [1 2 3 4])                                                                                            

15  

Which executes the following:

(+ 5 1)  => 6
(+ 6 2)  => 8
(+ 8 3)  => 11
(+ 11 4) => 15

HTH,

Kyle

نصائح أخرى

The generalized abstraction over collection is called a sequence in Clojure and many data structure implement this abstraction so that you can use all sequence related operations on those data structure without thinking about which data structure is being passed to your function(s).

As far as the sample code is concerned - the loop, recur is for recursion - so basically any problem that you want to solve using recursion can be solved using it, classic example being factorial. Although you can create a vector/list using loop - by using the accumulator as a vector and keep appending items to it and in the exist condition of recursion returning the accumulated vector - but you can use reductions and take-while functions to do so as shown below. This will return a lazy sequence.

Ex:

(take-while #(> % 1) (reductions (fn [s _] (/ s 2)) 128 (range)))
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top