Rip implementation in Clojure
Is there a way to break out of the loop in Clojure if the condition is matched to return the last value? Most algorithms benefit from returning a result when they find it and avoid completing the entire execution.
Let's say that I have a vector of 100 numbers ranging from 0 to 100, and I want to find the number 10. Once 10 is found, I want execution to stop.
An even simpler case than my example is the following:
(defn MySearch
[y]
(when (< y 10)
;;Corrected. Thanks to dsm who pointed it out. Previously was (< y 5).
(if (= (nth [1 2 3 4 5 6 7 8 9 10] y) 10)
(println
"I found it! Now I want to stop executing!"
)
)
(recur
(inc y)
)
)
)
(MySearch 0)
How can I stop when I find 5?
I've searched enough and I can't seem to find a way to implement this. I also found an answer here that says that what I'm asking does not exist in Clojure, but I find it a little far-fetched. Even if so, can I implement something similar to myself?
(I'm new to Clojure.)
source to share
You almost got it right. Reformatting the code, we get
(defn MySearch [y]
(when (< y 10)
(if (= (nth [1 2 3 4 5 6 7 8 9 10] y) 10)
"I found it! Now I want to stop executing!")
(recur (inc y))))
... where - for the sake of simplicity - I got rid of println
and the function will hopefully return your message.
But, as you noticed, this is not the case:
(MySearch 0)
;nil
Why?
The problem is what (recur ...)
is outside if
. What does it do?
- If the condition
(< y 10)
for iswhen
met,(if ...)
and(recur ...)
are executed in turn, and the result of the latter is returned. - In the end,
y
-10
, so the conditionwhen
fails, so itwhen
returnsnil
.
Move the repeat inside if
:
(defn MySearch [y]
(when (< y 10)
(if (= (nth [1 2 3 4 5 6 7 8 9 10] y) 10)
"I found it! Now I want to stop executing!"
(recur (inc y)))))
Now, Lo and Here:
(MySearch 0)
;"I found it! Now I want to stop executing!"
Since we returned the message, we know that the function has stopped executing. Otherwise, it would have continued and returned nil
.
When used, the println
function will print a message and return nil
immediately as it would if it continued. So, as far as whether or not it stopped executing, you are no wiser.
By the way, as the author of the answer you found contrived , let me try again:
- There is no break statement in Clojure.
-
It's the other way around:
- You exit the default loop.
- To continue, you must use
recur
.
-
recur
is a special recursive call to the function (orloop
) you are doing:- where is the return value.
- It is said to be in a tail position.
Most Lisp systems detect these calls — the so-called tail calls — automatically. Therefore, they do not have or do not need a type construct recur
.
Having said that, Clojure 1.5 introduced the construct reduced
: a break
to shrink. You can read about it here .
source to share
You don't really need the operator break
because the loop works differently in clojure than in, say, java. For example, the following:
user=> (loop [[x & t] [0 1 2 3 4 5 6 7 8 9]]
#_=> (println "x=" x)
#_=> (if (= x 5)
#_=> x
#_=> (recur t)))
x= 0
x= 1
x= 2
x= 3
x= 4
x= 5
5
user=>
roughly equivalent if(x == 5) break;
in java.
You need to remember that in clojure loop
itself, not a loop, but rather sets the recur
target.
I would recommend that you go through the Rick Hickey video on this subject to familiarize yourself with the weirder aspects of it.
EDIT: You seem to have added some code while I was fooling around with my answer, but not to worry, the named function is also a repeat object, so everything I said above still applies :). Here's your code, reformatted in lisp -y style:
user=> ; FYI: This function will never print "success"
user=> (defn my-search
#_=> [y]
#_=> (if (< y 5) ; <-- Because of this.
#_=> (if (= (nth [1 2 3 4 5 6 7 8 9 10] y) 10)
#_=> (println "Success!")
#_=> (recur (+ y 1)))
#_=> (println "y is >= 5")))
#'user/my-search
user=> (my-search 3)
y is >= 5
nil
user=>
source to share