An if with one branch is executed only for its potential side-effect(s).
We define when to signal this situation to the reader.
Without a second branch we can interpret multiple arguments after the condition
as a sequence to be executed, a common situation when we execute code for its side-effects.
In a two-branch if it's never necessary to use a negated condition
(branches can simply be switched), but it may be necessary in a one-branch if.
We define unless as a linguistically natural form for this common situation.
For safety we require at least one statement following the condition since without one there is no point. On the other hand, other macros and code generators might find such a base case convenient.
;;; Conditionally execute statement(s) for side-effect(s).
;
; (when condition do! do! ...)
; when 'condition': execute subsequent statements
;
; (unless condition do! do! ...)
; when not 'condition': execute subsequent statements
;
(define-syntax when
(syntax-rules ()
((_ condition do0! do1! ...) (if condition (begin do0! do1! ...)))))
(define-syntax unless
(syntax-rules ()
((_ condition do0! do1! ...) (if (not condition) (begin do0! do1! ...)))))
We define a fairly standard while
and the linguistically natural variant until.
Any non-#f condition ends until.
The particular value might be of interest
so we make it the value of the until.
An until with a disjunctive termination condition is often followed
by post-processing based on which disjunctive sub-condition was true.
We define a special form of condition to specify the post-processing with the
sub-conditions, removing repetition in the code and execution:
(one-of (<sub-condition> then <expr> ...)
(<sub-condition> then <expr> ...)
...)
This is similar to cond
(but not the same as literally using cond as a condition!),
which is used in the implementation.
We will eventually add analogous => and no-expression clauses
(but not an else, which is essentially the body).
We could have left out the keyword "then", as cond does,
but it will be only a small part of the expression and makes the meaning
more intuitive for readers new to the form.
We allow an empty body which might be meaningful if the condition has side-effects.
For safety we require at least one sub-condition in one-of conditions
since without one there is no point. But a single sub-condition might be worthwhile
if the loop reads better with post-processing at the top.
;;; Iteration: while and until.
;
; (while condition do0! ...)
; (until condition do0! ...), evaluates to condition
;
; (until (one-of (sub-condition then expr expr ...)
; (sub-condition then expr expr ...)
; ...)
; do0! ...)
;
(define-syntax while
(syntax-rules ()
((_ condition do0! ...)
(letrec ((loop (lambda () (if condition (begin do0! ... (loop)))))) (loop)))))
(define-syntax until
(syntax-rules (one-of then)
((_ (one-of (sub-condition0 then expr00 expr01 ...)
(sub-condition1 then expr10 expr11 ...) ...)
do0! ...)
(letrec ((loop (lambda () (cond (sub-condition0 expr00 expr01 ...)
(sub-condition1 expr10 expr11 ...)
...
(#t do0! ... (loop))))))
(loop)))
((_ condition do0! ...)
(letrec ((loop (lambda () (cond (condition) (else do0! ... (loop))))))
(loop)))))
Terminating a loop from within its body is considered by some to be bad style. It buries the terminating conditions, making the post-condition of the loop less clear. In the 1970s Knuth urged the adoption of an approach by Zahn, where the termination conditions are named in the loop header and termination occurs by name.
We define repeat in the spirit of Knuth and Zahn.
The header contains named exits which may be called within the body to terminate the loop.
Continuing to blend the imperative and functional styles as we did above with until,
exit calls have an optional argument which specifies the value of the loop.
Furthermore, post-processing of the value returned from the body may be specified after the name of the exit,
using the name itself to refer to the body's returned value.
;;; Loop with termination from within the body.
;
; (repeat (exits x ...) body ...)
; where x is a name or (name then expr expr ...)
;
; Repeats the body, terminating when one of the exit names is called.
; If the exit is called with an argument, it is the value of the loop.
; If the exit name was specified by (name then expr expr ...) then
; post-processing occurs, with the original value bound to the exit name.
;
; Example: (repeat (exits (a then (+ a 1)) b)
; (case (random 10) ((0) (a 3)) ((1) (b 5)) ((2) (b))))
; => sometimes 4, sometimes 5,
; sometimes undefined (#f in current implementation),
;
(define-syntax repeat
(syntax-rules (exits _exits)
((repeat (exits x ...) body ...)
(repeat (_exits (x ...) ()) body ...))
((repeat (_exits ((x then e0 e1 ...) xs ...) (a ...)) body ...)
(repeat (_exits (xs ...) (a ... (x e0 e1 ...))) body ...))
((repeat (_exits (x xs ...) (a ...)) body ...)
(repeat (_exits (xs ...) (a ... (x))) body ...))
((repeat (_exits () ((x e ...) ...)) body ...)
(call-with-current-continuation
(lambda (exit)
(let ((x
(lambda args
(let ((x (and (not (null? args)) (car args))))
(exit (begin x e ...))))) ...)
(letrec ((repeat (lambda () body ... (repeat)))) (repeat))))))))