Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stuck in a Clojure loop, need some guidance

I am stuck in a Clojure loop and need help to get out.

I first want to define a vector

(def lawl [1 2 3 4 5])

I do

(get lawl 0)

And get "1" in return. Now, I want a loop that get each number in the vector, so I do:

(loop [i 0]
   (if (< i (count lawl)) 
     (get lawl i) 
       (recur (inc i))))

In my mind this is supposed to set the value of i to nil, then if i is lower then the count of the lawl vector, it should get each lawl value and then increase the i variable with 1 and try again, getting the next value in the vector.

However, this does not work and I have spent some time trying to get it working and are totally stuck, would appreciate some help. I have also tried changing "if" to "when" with the same result, it doesn't provide any data the REPL just enters a new line and blink.

EDIT: Fixed the recur.

like image 342
bleakgadfly Avatar asked Jul 11 '10 08:07

bleakgadfly


3 Answers

You need to consider what is "to get each lawl value" supposed to mean. Your get call does indeed "get" the appropriate value, but since you never do anything with it, it is simply discarded; Bozhidar's suggestion to add a println is a good one and will allow you to see that the loop does indeed access all the elements of lawl (just replace (get ...) with (println (get ...)), after fixing the (inc) => (inc i) thing Bozhidar mentioned also).

That said, if you simply want to do something with each number in turn, loop / recur is not a good way to go about it at all. Here are some others:

;;; do some side-effecty thing to each number in turn:
(dotimes [i (count lawl)]
  (println (str i ": " (lawl i)))) ; you don't really need the get either

;; doseq is more general than dotimes, but doesn't give you equally immediate
;; acess to the index
(doseq [n lawl]
  (println n))

;;; transform the lawl vector somehow and return the result:
; produce a seq of the elements of lawl transformed by some function
(map inc lawl)
; or if you want the result to be a vector too...
(vec (map inc lawl))
; produce a seq of the even members of lawl multiplied by 3
(for [n lawl
      :when (even? n)]
  (* n 3))

This is just the beginning. For a good tour around Clojure's standard library, see the Clojure -- Functional Programming for the JVM article by Mark Volkmann.

like image 97
Michał Marczyk Avatar answered Nov 19 '22 04:11

Michał Marczyk


(recur (inc)) should be (recur (inc i))

Even so this code will just return 1 in the end, if you want a listing of the number you might add a print expression :-) Btw index based loops are not needed at all in scenarios such as this.

(loop [list [1 2 3 4 5] ]
         (if (empty? list)
             (println "done")
             (do
              (println (first list))
              (recur (rest list)))))
like image 32
Bozhidar Batsov Avatar answered Nov 19 '22 03:11

Bozhidar Batsov


OK, I'm about 10-1/2 years too late on this, but here goes:

The problem here is a pretty common misunderstanding of how the arguments to the if function are used. if takes three arguments - the condition/predicate, the code to be executed if the predicate is true, and the code to be executed if the predicate is false. In this case both of the true and false cases are supplied. Perhaps if we fix the indentation and add some appropriate comments we'll be able to see what's happening more easily:

(loop [i 0]
  (if (< i (count lawl)) 
    (get lawl i)       ; then 
    (recur (inc i))))  ; else

So the problem is not that the code gets "stuck" in the loop - the problem is that the recur form is never executed. Here's how the execution flows:

  1. The loop form is entered; i is set to 0.
  2. The if form is entered.
  3. The predicate form is executed and found to be true.
  4. The code for the then branch of the if is executed, returning 1.
  5. Execution then falls out the bottom of the loop form.

Right now I hear people screaming "Wait! WHAT?!?". Yep - in an if form you can only have a single form in the "then" and "else" branches. "But...THAT'S STUPID!" I hear you say. Well...not really. You just need to know how to work with it. There's a way to group multiple forms together in Clojure into a single form, and that's done by using do. If we want to group (get lawl i) and (recur... together we could write it as

(loop [i 0]
  (if (< i (count lawl)) 
    (do
      (get lawl i)       ; then 
      (recur (inc i))
    )
  )
)

As you can see, we have no "else" branch on this if form - instead, the (get... and (recur... forms are grouped together by the (do, so they execute one after the other. So after recurring its way through the lawl vector the above snippet returns nil, which is kind of ugly. So let's have it return something more informative:

(loop [i 0]
  (if (< i (count lawl)) 
    (do
      (get lawl i)         ; then
      (recur (inc i)))
    (str "All done i=" i)  ; else
  )
)

Now our else branch returns "All done i=5".

like image 27