SRFI 2 - and-let*

The (srfi 2) library provides the and-let* macro, an and with local bindings.

Overview

Like an ordinary and, an and-let* special form evaluates its arguments – expressions – one after another in order, till the first one that yields #f. Unlike and, however, a non-#f result of one expression can be bound to a fresh variable and used in the subsequent expressions. and-let* is a cross-breed between let* and and.

See the SRFI document for more information.

Rationale

In case of an ordinary and formed of proper boolean expressions:

(and E1 E2 ...)

expression E2, if it gets to be evaluated, knows that E1 has returned non-#f. Moreover, E2 knows exactly what the result of E1 was – #t – which E2 can use to its advantage. If E1 however is an extended boolean expression, E2 can no longer tell which particular non-#f value E1 has returned. Chances are it took a lot of work to evaluate E1, and the produced result (a number, a vector, a string, etc) may be of value to E2. Alas, the and form merely checks that the result is not an #f, and throws it away. If E2 needs it, it has to compute that value anew. This proposed and-let* special form lets constituent expressions get hold of the results of already evaluated expressions, without re-doing their work.

and-let* can be thought of as a combination of let* and and, or a generalization of cond’s send operator =>. An and-let* form can also be considered a sequence of guarded expressions. In a regular program, forms may produce results, bind them to variables and let other forms use these results. and-let* differs in that it checks to make sure that every produced result “makes sense” (that is, not an #f). The first “failure” triggers the guard and aborts the rest of the sequence (which presumably would not make any sense to execute anyway). Examples:

(and-let* ((my-list (compute-list)) ((not (null? my-list))))
          (do-something my-list))

(define (look-up key alist)
  (and-let* ((x (assq key alist))) (cdr x)))

(or
  (and-let* ((c (read-char))
    ((not (eof-object? c))))
    (string-set! some-str i c)  
    (set! i (+ 1 i)))
  (begin (do-process-eof)))

      ; A more realistic example
                        ; Parse the 'timestamp' ::= 'token1' 'token2'
                        ;   token1 ::= 'YY' 'MM' 'J'
                        ;   token2 ::= 'GG' 'gg' "/"
(define (parse-full-timestamp token1 token2)
  (and-let* (((= 5 (string-length token1)))
             ((= 5 (string-length token2)))
             (timestamp
               (OS:string->time "%m/%d/%y %H:%M"
                 (string
                   (string-ref token1 2) (string-ref token1 3) #\/
                   (string-ref token1 0) (string-ref token1 1) #\/
                   (case (string-ref token1 4)
                     ((#\8 #\9) #\9) (else #\0))
                   (string-ref token1 4) #\space
                   (string-ref token2 0) (string-ref token2 1) #\:
                   (string-ref token2 2) (string-ref token2 3))))
             ((positive? timestamp)))
           timestamp))

and-let* is also similar to an “anaphoric AND” LISP macro [Rob Warnock, comp.lang.scheme, 26 Feb 1998 09:06:43 GMT, Message-ID: 6d3bb3$3804h@fido.asd.sgi.com]. and-let* allows however more than one intermediate result, each of which continues to be bound through the rest of the form.

and-let*

Syntax

(and-let* (claws) body)

claws ::= '() | (cons claw claws)
claw  ::=  (variable expression) | (expression) |
           bound-variable