3
votes

I have been trying to understand how to use loops in LISP and they still don't really seem to work correctly. I tried using the following code:

    (loop for i from 0 to (list-length y)
          (when (eq (values-list (nth i (car y))) 0)
             (return-from checkZero t)))

Which should loop through my list checking if my value is equal to 0 or not. If it is equal then it should return from and exit the loop, otherwise it should run until it reaches the list length. Am I thinking about this wrong and if so how do I go about fixing this loop?

(I'm not sure if my actual code works or not yet since I am still dealing with the errors generated by the incorrectly used loop and I can't find many good resources for using loops online)

1
Remove the parentheses around (when (eq ...) (return-from...)) so to have when (eq ...) (return-from...) and put a do before (return-from...). - Renzo
Practical Common Lisp has a chapter on loop called LOOP for Black Belts. In the book The land of lisp you have a practical cheat sheet - Sylwester

1 Answers

5
votes

The main problem in the loop is the WHEN-expression. There are two ways you can write that:

  1. Use the loop WHEN condition DO forms-clause:

    (loop for...
          when (eq ...) do (return-from ...))
    
  2. Use the regular WHEN-macro inside a loop DO-clause:

    (loop for...
          do (when (eq ...)
               (return-from ...)))
    

There are a few other things to fix in your code.

  1. When naming things in Lisp, use dashes between words rather than camelCase (check-zero rather than checkZero).
  2. Use = for general numeric comparison, or ZEROP to check that a number is zero. EQ is used to check if two objects are the same object.
  3. You can return from a loop using RETURN
  4. I'm not quite sure what you're trying to accomplish with the (VALUES-LIST (NTH ... (CAR ...))), but it's not going to work. If you're trying to simply loop over a flat list of values (such as (1 2 3 4 5 6)), you should be using the loop FOR item IN list-clause.

So now you should have something like:

(defun check-zero (list)
  (loop for item in list
        when (zerop item) do (return t)))

LOOP also has a THEREIS condition-clause that you could use:

(defun check-zero (list)
  (loop for item in list
        thereis (zerop item)))

This return as soon as it finds an item that satisfies ZEROP. However, there are easier ways to achieve the same. You could use MEMBER to check if the list contains a zero:

(defun check-zero (list)
  (member 0 list :test #'=))

CL-USER> (check-zero '(1 3 4 3 5 7))
NIL
CL-USER> (check-zero '(1 3 4 3 0 5 7))
(0 5 7)

This returns a generalized boolean. That is, any value that is not NIL is considered true in Common Lisp.

Since there is a predicate function (ZEROP) to check if an object is a zero, you could also use SOME or MEMBER-IF for this:

(some #'zerop '(1 3 4 6 2 0 45 6 7)) ;=> T
(member-if #'zerop '(1 3 4 6 2 0 45 6 7)) ;=> (0 45 6 7)