egallesio / STklos

STklos Scheme
http://stklos.net
GNU General Public License v2.0
67 stars 17 forks source link

`dolist` and `dotimes` inconsistent behavior #627

Closed jpellegrini closed 2 months ago

jpellegrini commented 2 months ago

With dotimes I can put x in the binding and use it in the expression result:

stklos> (dotimes (x 3 (- x)) (print x))
0
1
2
-3

But for dolist it doesn't work:

stklos> (dolist (x '(0 1 2) (- x)) (print x))
0
1
2
**** Error:
%execute: symbol `x' unbound in module `stklos'
jpellegrini commented 2 months ago

Ah, here's why!

(macro-expand '(dolist (x '(0 1 2) (- x)) (print x)))

(begin
   (for-each
     (lambda (x1)
       (let ((x x1))
        (print x)))
     '(0 1 2))
   (- x))

This probably should have an external LET for x, and a set! instead of let inside the lambda...

lassik commented 2 months ago

The Common Lisp spec says:

At the time result-form is processed, var is bound to nil.

jpellegrini commented 2 months ago

The Common Lisp spec says:

Ok, I see! The inconsistency comes from Common Lisp. And I see that Gauche does keep the same behavior as CL...

jpellegrini commented 2 months ago

So - this is a non-issue. Sorry for the noise!

lassik commented 2 months ago

There is no obviously correct value for var to have at the end. If the dolist list is empty (or the dotimes number is zero), should it be #f? In Common Lisp, nil is the usual "uninteresting value". That's why they use it.

lassik commented 2 months ago

Huh. Common Lisp really is inconsistent as you say. For dotimes:

At the time result-form is processed, var is bound to the number of times the body was executed.

Thank you for teaching me this :)

egallesio commented 2 months ago

I have hesitated for the value that must be returned by dolist. Gauche returns () to be similar to CL (and it is coherent with the cdr of the last cons). As @lassik said, probably #f could be a good value for Scheme. However, I choose to return a void value to avoid the printing of a final value when used in a REPL as:

stklos> (dolist (x '(1 2 3)) (print x))
1
2
3
stklos> 

But, having a result sometimes can be useful:

stklos> (let ((sum 0))
                (dolist (x '(1 2 3 4 5) sum)
                     (inc! sum (square x)))) 
55
stklos>

So, (dolist (x lst result) body) returns result if provided (and if result is not x); otherwise, its result is undefined. The special case when the result is explicitly the iteration variable is probably not a good idea (return #f or '() in this case?).

Imho, this form is practical to use, but I find it not too "pretty", and I really prefer a good old for-each :smile: