## A deeper look at *for*

### Introduction

The*for*function is at the same time the most important and the most complex control-flow function in PicoLisp. There is some empirical evidence for the first claim, at least an analysis of more than 600 Rosetta Code Solutions yielded the following numbers:

| function | count | |----------+-------| | for | 458 | | do | 145 | | mapcar | 143 | | while | 106 | | mapc | 80 | | loop | 69 | | until | 36 | | map | 20 | | mapcan | 10 | | mapcon | 2 |

While the selection of control-flow functions in this table is a bit subjective (the original Rosetta Code Analysis gives numbers for some 370 global symbols in PicoLisp), and the Rosetta Code Solutions might not be representative for PicoLisp programs in other domains, the overall picture is pretty clear:

*for*is used ca. three times as often as the next popular functions

*do*and

*mapcar*, and more than four times as often as

*while*.

The second claim is of a more subjective nature - I find it often quite difficult to understand the

*for*-loops in PicoLisp code, and this article is an attempt to learn more about the

*for*function while letting others benefit from the effort. However, the following original documentation of

*for*should somehow support the claim that it is a complex function.

### The Official (rather terse) Documentation

(for sym 'num ['any | (NIL 'any . prg) | (T 'any . prg) ..]) -> any (for sym|(sym2 . sym) 'lst ['any | (NIL 'any . prg) | (T 'any . prg) ..]) -> any (for (sym|(sym2 . sym) 'any1 'any2 [. prg]) ['any | (NIL 'any . prg) | (T 'any . prg) ..]) -> any Conditional loop with local variable(s) and multiple conditional exits: In the first form, the value of sym is saved, sym is bound to 1, and the body is executed with increasing values up to (and including) num. In the second form, the value of sym is saved, sym is subsequently bound to the elements of lst, and the body is executed each time. In the third form, the value of sym is saved, and sym is bound to any1. If sym2 is given, it is treated as a counter variable, first bound to 1 and then incremented for each execution of the body. While the condition any2 evaluates to non-NIL, the body is repeatedly executed and, if prg is given, sym is re-bound to the result of its evaluation. If a clause has NIL or T as its CAR, the clause's second element is evaluated as a condition and - if the result is NIL or non-NIL, respectively - the prg is executed and the result returned. If the body is never executed, NIL is returned. See also do and loop. # First form: : (for N 5 (printsp N)) 1 2 3 4 5 -> 5 : (for N 5 (printsp N) (NIL (< N 3) (printsp 'enough))) 1 2 3 enough -> enough : (for N 5 (T (> N 3) (printsp 'enough)) (printsp N)) 1 2 3 enough -> enough # Second form: : (for X (1 a 2 b) (printsp X)) 1 a 2 b -> b : (for (I . X) '(a b c) (println I X)) 1 a 2 b 3 c -> c # Third form: : (for (L (1 2 3 4 5) L) (printsp (pop 'L))) 1 2 3 4 5 -> 5 : (for (N 1 (>= 5 N) (inc N)) (printsp N)) 1 2 3 4 5 -> 5 : (for ((I . L) '(a b c d e f) L (cddr L)) (println I L)) 1 (a b c d e f) 2 (c d e f) 3 (e f) -> (e f)

### The First Form

(for sym 'num ['any | (NIL 'any . prg) | (T 'any . prg) ..]) -> any In the first form, the value of sym is saved, sym is bound to 1, and the body is executed with increasing values up to (and including) num.

Lets walk through the examples given in the documentation.

#### Example 1.1

: (for N 5 (printsp N)) 1 2 3 4 5 -> 5

First, as a reminder, the documentation of printsp:

(printsp 'any ..) -> any Prints all any arguments to the current output channel, followed by a space. If there is more than one argument, a space is printed between successive arguments.

In this example, N is the symbol sym whose value is saved first and which is then bound to 1. The number 5 is the upper limit num. (printsp N) is the body of the function, and it is a clause that neither has NIL or T as its CAR.

So the body is executed five times while the counter N is incremented from 1 to 5. Simple enough.

#### Example 1.2

: (for N 5 (printsp N) (NIL (< N 3) (printsp 'enough))) 1 2 3 enough -> enough

The second example is identical to the first one except for an additional clause in the body of for:

(printsp N) (NIL (< N 3) (printsp 'enough))

Lets recall the original documentation to understand this:

If a clause has NIL or T as its CAR, the clause's second element is evaluated as a condition and - if the result is NIL or non-NIL, respectively - the prg is executed and the result returned. If the body is never executed, NIL is returned.

The second clause has NIL as its CAR, therefore its second element, (< N 3), is evaluated as a condition. Here, in the first two iterations, N has the value 1 and then 2, thus the condition returns T. In the third iteration, the condition (< 3 3) returns NIL and the associated

*prg*((printsp 'enough)) is executed and the result

*enough*returned.

#### Example 1.3

The third example yields the same result as the second, but in a different way:: (for N 5 (T (> N 3) (printsp 'enough)) (printsp N)) 1 2 3 enough -> enough

Here, the first clause has T in its CAR, but the condition is (> N 3) this time, thus in the first three iterations NIL is returned and the second clause (printsp N) evaluated. When N is incremented to 4, the condition becomes true, the associated prg is executed and its result (

*enough*) returned.

### The Second Form

(for sym|(sym2 . sym) 'lst ['any | (NIL 'any . prg) | (T 'any . prg) ..]) -> any In the second form, the value of sym is saved, sym is subsequently bound to the elements of lst, and the body is executed each time.

While the part inside the square brackets ['any ...] is the same as in the first form, we can see two differences in the definition:

- 'lst is used instead of 'num, i.e. instead of incrementing a counter N until upper limit 'num is reached, sym is subsequently bound to the elements of 'lst.
- instead of a single sym, a cons pair or dotted pair (sym2 . sym) can be used.

The PicoLisp Reference gives the following explanation for

*cons pairs*like (sym2 . sym):

Lists are used in PicoLisp to emulate composite data structures like arrays, trees, stacks or queues. In contrast to lists, numbers and symbols are collectively called "Atoms". Typically, the CDR of each cell in a list points to the following cell, except for the last cell which points to NIL. If, however, the CDR of the last cell points to an atom, that cell is called a "dotted pair" (because of its I/O syntax with a dot '.' between the two values).

Lets walk again through the examples given in the documentation. Of course, the special clauses with T or NIL in the CAR can be used like in the first form, although no further examples are given for this.

#### Example 2.1

: (for X (1 a 2 b) (printsp X)) 1 a 2 b -> b

Here, X is sym, (1 a 2 b) is 'lst and (printsp X) is 'any. Hence, there are 4 iterations, since the list has 4 elements, and the symbol X is bound to one list element after another. In each iteration, the program (printsp X) is executed with the actual value of X.

#### Example 2.2

: (for (I . X) '(a b c) (println I X)) 1 a 2 b 3 c -> c

In this example, a dotted pair (sym2 . sym) is used instead of a single symbol sym. The documentation explains its use:

If sym2 is given, it is treated as a counter variable, first bound to 1 and then incremented for each execution of the body.

The output of the example shows that symbol X is bound subsequently to a, b and c, while the counter variable I is incremented in each iteration.

### Third Form

(for (sym|(sym2 . sym) 'any1 'any2 [. prg]) ['any | (NIL 'any . prg) | (T 'any . prg) ..]) -> any In the third form, the value of sym is saved, and sym is bound to any1. If sym2 is given, it is treated as a counter variable, first bound to 1 and then incremented for each execution of the body. While the condition any2 evaluates to non-NIL, the body is repeatedly executed and, if prg is given, sym is re-bound to the result of its evaluation.

The part in the square brackets ['any ...] is the same as in the two previously discussed forms. And, like in form 2, either a single symbol sym or a cons pair (sym2 . sym) can be used. However, there are a few differences that make this the most general, versatil and complex of the three forms:

- the whole part between the keyword for and the ['any ...] part is given in parenthesis (that do, in contrast to the grouping square brackets, appear as PicoLisp syntax in the code examples.
- instead of 'num (form 1) or 'lst (form 2) we encounter 'any1 'any2 [. prg] in form 3.

Here, sym is bound to 'any1, and, if sym2 is given too, it is treated as a counter variable just like in form 2. 'any2 is a condition, and the body of the

*for*-function is repeated as often as this condition evaluates to non-NIL. If [. prg] is given, sym is rebound to the result of its evaluation after each iteration.

Lets have a look at the examples:

#### Example 3.1

: (for (L (1 2 3 4 5) L) (printsp (pop 'L))) 1 2 3 4 5 -> 5

First, as a reminder, the documentation of pop:

(pop 'var) -> any Pops the first element (CAR) from the stack in var.

In this example, L is sym, the list (1 2 3 4 5) is 'any1, and L is again used as condition 'any2. The body of the function is (printsp (pop 'L)).

Thus the symbol L is bound to the whole list (1 2 3 4 5) in this case, and not, as in form 2, subsequently to its elements. The number of iterations in form 3 is determined by the condition 'any2, in this case the symbol L again. Evaluating L returns the list 'any1, i.e. a non-NIL value. Without changing the value of L, this

*for*-function would loop forever. But in the body of the function, the CAR of the list in L is

*popped*in each iteration, resulting in a list with one element less. After five iterations, the list is empty, and since in PicoLisp (like in other Lisps) the empty list () means NIL, evaluating the condition 'any2 (the list L) in iteration 6 returns NIL and the

*for*-loop is terminated.

#### Example 3.2

: (for (N 1 (>= 5 N) (inc N)) (printsp N)) 1 2 3 4 5 -> 5

Lets start with the documentation of inc:

(inc 'num) -> num [inc] returns the value of num incremented by 1.

In this example, the

*for*keyword is followed by two clauses, with the second clause (printsp N) representing the body of the function.

The interesting things happen in the first clause here: N is sym, which is bound to 'any1, i.e. the number 1 in this case. Then follows a condition 'any2, (>= 5 N), and a [.prg] associated with this condition, (inc N).

Thus, in the first iteration, N is bound to 1, it is checked that 5 >= 1, the function body is executed, i.e. 1 is printed followed by a space, and finally [.prg] in the first clause is evaluated and the symbol N bound to the result of this evaluation (2).

This is repeated five times, until the condition (>= 5 6) returns NIL in the sixth iteration.

#### Example 3.3

: (for ((I . L) '(a b c d e f) L (cddr L)) (println I L)) 1 (a b c d e f) 2 (c d e f) 3 (e f) -> (e f)

Lets start again with a bit of documentation for cddr, println should be obvious:

(c[ad]*ar 'var) -> any (c[ad]*dr 'lst) -> any List access shortcuts. Combinations of the car and cdr functions, with up to four letters 'a' and 'd'. : (cdr '(a b c d)) -> (b c d) : (cdr '(b c d)) -> (c d) : (cddr '(a b c d)) -> (c d)

Here, like in Example 2.2, (sym2 . sym) instead of just sym is used, i.e. the L in the cons pair (I . L) is used as symbol and the I as counter variable starting with 1 and then incremented in each iteration. The function body is the same as in Example 2.2, except that the printed results are separated by newlines rather than spaces.

Again, we find 'any1, 'any2 and [.prg] in the first clause after the

*for*keyword. This time, like in Example 3.1, 'any1 is a list instead of a number, and symbol L is bound to the whole list. The condition 'any2 checked in each iteration is simply L - is L the empty list returning NIL on evaluation, or does it still contain elements? At the end of each iteration, [.prg] is evaluated and symbol L bound to the result. In this case, (cddr L) returns the list L without its first two elements. Since the original list has 6 elements, L is the empty list after 3 iterations, and evaluating the condition 'any2 (in this case simply the list L) in the fourth iteration returns NIL what leads to the termination of the

*for*loop.

https://picolisp.com/wiki/?deeperlookatfor

24nov12 | tj |