by Panicz Maciej Godek

This SRFI is currently in *draft* status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to `srfi-202@nospamsrfi.schemers.org`

. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

- Received: 2020-06-19
- 60-day deadline: 2020-08-24
- Draft #1 published: 2020-06-25

The SRFI-2 library introduced the `and-let*`

form
for short-circuited evaluation in the style of the `and`

form with the ability to capture the (non-`#f`

) results
in the style of the `let*`

form. This document extends
the `and-let*`

form with the abilities to pattern-match
(or "destructurally bind") the values of evaluated expressions
(where the match failure causes short-circuiting rather than
raising an error), and to handle multiple values (where only the
falsehood of the first value causes short-circuiting).

It is well known that, in the context of the `if`

condition,
Scheme treats `#f`

as a *false* value, and every other value
is considered true (or "true-ish").

This design decision has led to the development of the idiom in which the
`#f`

is considered to represent "the absence of value" or a "failure
of computation", whereas and other value is considered to represent a meaningful
result.

In strongly typed languages, this pattern is usually expressed in terms of algebraic
data types. For example, the Haskell language defines the polymorphic type
`Maybe a`

as either `Just a`

or `Nothing`

,
where `Just a`

is used to represent the value of type `a`

,
and `Nothing`

is used to represent the absence of value.

Moreover, in many functional languages, the types isomorphic to `Maybe`

are considered to be "monads", which means that they can use some facilities
that those languages provide for sequencing functions that return things that
are considered to be "monads". In particular, Haskell provides a special syntactic
form called the `do`

notation, which allows the function like

h x = case (f x) of Just y -> case (g (k y)) of Just z -> Just (l z) Nothing -> Nothing Nothing -> Nothingto be rewritten as

h x = do y <- (f x) z <- (g (k y)) return (l z)

where `f`

and `g`

(and `h`

) return
optional values, and `k`

and `l`

return obligatory
values.

Since these facilities rely on some advanced features of type systems
that are lacking from most Scheme implementations, Schemers have invented
a special form specifically for dealing with optional values. The form
is called `and-let*`

and has been defined in the SRFI-2 document.

Accordingly, the equivalent code using Scheme can be written as

(define (h x) (and-let* ((y (f x)) (z (g (k y)))) (l z)))

However, the original definition of `and-let*`

does not allow
one to handle multiple values. It also doesn't allow one to destructure the value
of the evaluated expression. The purpose of this SRFI is to extend the
`and-let*`

form with these capabilities.

Therefore, with the capabilities from this SRFI, the two first examples from the Rationale section of SRFI 2, namely:

(and-let* ((my-list (compute-list)) ((not (null? my-list)))) (do-something my-list)) (define (lookup key alist) (and-let* ((x (assq key alist))) (cdr x)))

could roughly be rewritten as:

(and-let* ((`(,my . ,list) (compute-list))) (do-something `(,my . ,list))) (define (lookup key alist) (and-let* ((`(,key . ,value) (assq key alist))) key))

(The first example is not exactly equivalent, because it assumes
that the test `(not (null? my-list))`

has the same meaning
as `(pair? my-list)`

, which doesn't need to be true, even
if usually is. Moreover, the body of the form contains an unnecessary
`cons`

operation which may incur performance penalty.)

The extensions provided here do not invalidate or modify any of the prior
uses of the SRFI 2's `and-let*`

form.

In particular, the `and-let*`

form allowed two types of expressions
inside of its "claws":

(variable expression)

and

(guard-expression)

This SRFI document allows `variable`

to be an arbitrary
pattern that is a valid pattern of the underlying pattern matcher
(see SRFI 200
Pattern Matching for more details):

(pattern expression)

In particular, since patterns can be literal values, it is possible to negate the condition:

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

If the value of an expression does not match the pattern, then the
value of the whole `and-let*`

form is `#f`

.

This variant of the `and-let*`

form also supports multiple
values, each of which can be a pattern:

(pattern patterns ... expression)

If the first `pattern`

is a variable, then it must have
a non-`#f`

value in order to proceed with evaluation
— otherwise, the value of the whole `and-let*`

form
becomes `#f`

.

If the `expression`

returns fewer values than expected,
then the value of the whole `and-let*`

expression becomes
`#f`

. If it returns more values than expected, then the
additional values are ignored.

(define-syntax and-let* (lambda (stx) (syntax-case stx () ((_) #'#t) ((_ ()) #'#t) ((_ () body ...) #'(let () body ...)) ((_ ((value expression) rest ...) body ...) (identifier? #'value) #'(let ((value expression)) (and value (and-let* (rest ...) body ...)))) ((_ ((value expression) rest ...) body ...) #'(match expression (value (and-let* (rest ...) body ...)) (_ #f))) ((_ ((condition) rest ...) body ...) #'(and condition (and-let* (rest ...) body ...))) ((_ ((value values ... expression) rest ...) body ...) (identifier? #'value) #'(call-with-values (lambda () expression) (lambda args (match args ((value values ... . _) (and value (and-let* (rest ...) body ...))) (_ #f))))) ((_ ((values ... expression) rest ...) body ...) #'(call-with-values (lambda () expression) (lambda args (match args ((values ... . _) (and-let* (rest ...) body ...)) (_ #f))))) )))

A working implementation for Guile Scheme can be found in the (grand syntax) module of the (grand scheme) glossary.

A working implementation for the Racket language can be found in the "grand-syntax.rkt" module of the Sracket framework.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Editor: Arthur A. Gleckler