(April 1999)
There are three basic styles of loops you can use, the first is the counting loop, the second is the continuously evaluating loop, and the third is the endless loop. The counting loop goes through the loop a certain predetermined number of times. The continuously evaluating loop, loops until some condition is met at which time it terminates. The endless loop executes forever, it’s the kind we don’t want to write in our lisp programs.
AutoLISP gives us some basic looping functions, which are quite useful, and as with everything else in lisp if we don’t like what they gave us we can write our own.
Looping functions
repeat (repeat number exp ...)
Evaluates each expression a given number of times. The number of times the loop operates is “num” times for each “exp”. Number represents any positive integer.
while (while testexp exp ...)
Evaluates the “exp” while the “testexp” remains true. While evaluates “testexp” and if “testexp” is true it evaluates all expressions enclosed in the loop. It then reevaluates “testexp”, if “testexp” is still true it goes through the loop again. This continues until “testexp” evaluates to nil.
The repeating loop
The repeat function evaluates an expression or expressions a given number of times. It’s a pretty basic concept and doesn’t really need a whole lot of explanation. If you know how many times you want your loop to execute the repeat loop is the one you want to use.
(repeat 5
(do_this))
The thing that limits the use of the repeat function is there are not many times you know how many times your going to need to go through the loop. Watch for the times you do know before hand, because repeat would be the cleanest way to deal with it.
The while loop
You can have one or more statements repeat many times in a row by using the “while” statement. The statements contained within the “while” loop will execute until the “testexp” becomes false.
(while (this_is_true)
(do_this))
The “testexp” can contain relational, as well as logical, operators. This operator must return a true or false value. As stated before a, “false” value in lisp is equivalent to “nil”. If the “testexp” is found to be false when the loop is entered, the body of the loop is never executed.
Because “testexp” determines when the loop ends, something inside the body of the loop must modify the variable or variables used by “testexp”. If the body of the loop doesn’t modify the variable used by the “testexp”, the test expression always remains true, and the loop never ends. This is what is known as an endless loop, and you should really avoid it.
Controlling the loop
When writing loops, there are several things you must do to keep control of your loops. Many things can go wrong when writing loops. Improper or omitted initialization of the loop, improper or omitted initialization of the control variables for the loop, forgetting to increment the loop counter or incrementing the variable improperly.
There are two things you can do to keep out of trouble when writing loops. First minimize the number of factors that effect the loop. Keep it simple. Second treat the inside of the routine as if it were a function, keep as much control as possible outside the body of the loop.
Be very clear about when the body of the function will execute. Don’t make the reader have to look all through the loop to find out what makes it work. A loop should be another black box, like a function, the surrounding program may know the controls for the loop, but not the contents of the loop.
When writing a loop put the code that initializes the loop directly in front of the loop. Keeping the initialization code close to the loop itself makes it easier to see how the loop works, and to make modifications later. If related statements are strewn throughout your program it will be very difficult to figure out later how it works.
When you are writing the terminator for your loop, try to visualize every condition that will make the loop end. Beyond anything else we want our loop to end. Think through the nominal cases the boundary conditions, and every exceptional case you can think of.
Writing a loop
There are some things you can do to make writing loops easier. Start writing the loop, case by case. Start with the first case you want to evaluate and write the loop for it, then if necessary write a loop to surround it. If you start with the inner loop and work your way out, you can keep track of what your loops are up to. The best way to write loops is to write the loops in pseudo-code. Pseudo-code is a way of writing down what your code should be doing before you write the code. If you’re doing it right the pseudo-code will look like the finished code when your done, but will be in plain English
;while this is true
;do this
Rather simplistic example but I think you get the point. What were trying for here is to get the feel of what your program will be doing. When we write the pseudo-code, we will write it using the comment markers for AutoLISP, that way when we are writing the code we can turn the pseudo-code into the comments for the loop. If we decide the loop is clear enough after we write it we can always delete the comments.
Recursion
Another form of loop is to use recursion. One of AutoLISP’s main characteristics is its recursive abilities. A recursive function is one in which it solves part of a problem and then calls itself to solve more of the problem, continuing until the problem is solved. It should be noted that recursion uses lots of stack memory and should be limited in its usage.
In those cases where recursion is the best way to do something, there are a few things you should keep in mind while writing recursive functions. Make sure at some point the recursion stops. Check your function when your done to make sure you have left a path out of the recursive loop. What this means is that you should have a test as part of the function to break out of the loop when a condition is met.
Sometimes it’s impossible to come up with a simple test to see if the loop should end. In cases such as this you should put a safety counter into the loop so the loop is limited in how many times it can recourse. The safety counter has to be a variable that is not reset each time through the loop. It will probably be a module level variable that is passed into the function. When a control value inside the function reaches the max value passed in the recursion tops.
Be sure when you write recursive functions not to nest them. Only one level of recursion should be going on at any given point. If you nest recursion you will get lost in the loops. If you see yourself getting in to cyclic recursion, try to redesign the function so you only have one recursive activity going on in the function.
The last thing you must keep in mind at all times when writing recursive functions is the stack is only so big. If you write a routine that recourses indefinitely, at some point you will run out of stack. When you run out of stack in AutoLISP, your program quits, which a user fails to see the humor in. Restrict your use of recursion to small loops.
DO-WHILE
Due to the fact that we can make any function we need, it is possible to construct different kinds of loops. One of the loops you may be familiar with in “C” is the do-while loop. As you can see below, we can approximate the do-while pretty easy in lisp.
(do
(do_this)
(this_is_true))
(defun do ( body_func chk_func / )
(foreach n (list_to_do))
(while (eval chk_func)
(foreach n (list_to_do)))
)
It doesn’t hold true to the “C” language syntax, but it comes pretty close. If you have used do-while you know how valuable it can be, and it’s worth it to have it around. The do-while you see above can be used to do one statement or many if you write do_this as a list.
(defun c:less8a ( / do )
;;Do-while function definition
(defun do ( body_func chk_func / )
(foreach n body_func (eval n))
(while (eval chk_func)
(foreach n body_func (eval n)))
)
(do
'((if (= x nil) ;|
(setq x 0) ;|____ Do this
(setq x (+ x 1))) ;|
(Prompt (strcat "n" (itoa x)))) ;|
'(< x 10)) ;While this is true
)
What the do-while does for you is give you a way to run the statements at least once even if the CHK_FUNC is false. The normal “while” loop only runs if the CHK_FUNC is true. This also allows us to initialize the loop, inside the loop. It’s a very clean way to keep everything together.
To use the do-while above you have to pass your parameters in, in a quoted manner. If you don’t follow this rule it won’t work. What I’m trying to show you is not that what I wrote above is the best way to do a do-while loop, but that the possibilities are there for you to write your own programming functions. Even the basic control functions can be rewritten.
FOR
The FOR loop is another easy to construct loop for lisp. The FOR loop is like the repeat loop on steroids. It gives us more flexibility and a cleaner way of writing the repeat loop.
(defun c:less8b ( / for )
(defun for ( initializer condition indexer body_code / )
(eval initializer)
(while (eval condition)
(foreach n body_code (eval n))
(eval indexer))
)
(for '(setq x 0) '(< x 10) '(setq x (+ x 1))
'((prompt (strcat "n" (itoa x)))))
)
Just like the do-while, the FOR loop needs everything you pass it quoted. It won’t work otherwise. Just like last time also, you may be able to think of a better way to do what I have done, but just be sure you understand, it can be done.