Errata for SRFI 5 Philip McGrath (19 Jan 2022 21:45 UTC)
Re: Errata for SRFI 5 Philip McGrath (20 Jan 2022 04:09 UTC)
Re: Errata for SRFI 5 Arthur A. Gleckler (20 Jan 2022 04:52 UTC)

Errata for SRFI 5 Philip McGrath 19 Jan 2022 21:44 UTC

Hi,

(I'm sending this to srfi-discuss as well as the srfi-5 list, since the
last message there is from 2005, and the last real discussion from 1999.)

I believe I have found some errata and ambiguities in SRFI 5.

First, the "informal specification" is not consistent with the "formal
specification". "Informal syntax 1" allows `let` forms without a
`let-name`, per
<https://github.com/scheme-requests-for-implementation/srfi-5/blob/ead1ea67b3a5746d640afc70fd2a09422450cea0/srfi-5.html#L167-L173>:

--8<---------------cut here---------------start------------->8---
<li>Unnamed

<p><pre>
(let ((&lt;parameter&gt; &lt;argument&gt;)...)
   &lt;body&gt;...)
</pre>
</li>
--8<---------------cut here---------------end--------------->8---

However, the "formal sepcification" does not have a case for bindings
with no `let-name`, per
<https://github.com/scheme-requests-for-implementation/srfi-5/blob/ead1ea67b3a5746d640afc70fd2a09422450cea0/srfi-5.html#L147-L151>:

--8<---------------cut here---------------start------------->8---
             let = "(" "let" let-bindings body ")"
     expressions = nothing | expression expressions
    let-bindings = let-name bindings
                 | "(" let-name "." bindings ")"
        let-name = identifier
--8<---------------cut here---------------end--------------->8---

Since a major component of SRFI 5 overall is that its `let` should be
fully compatible with the `let` from SRFI 5, I think "informal syntax 1"
should prevail here.

Second, I believe the specification is ambiguous about the semantics of
the following example, in which I use `s5:let` to refer to the form
defined by SRFI 5 and plain `let` to refer to the form defined in R5RS
(or your favorite Scheme standard):

--8<---------------cut here---------------start------------->8---
;; Should this expression:
(s5:let (ambiguous (+ 1) (- 2) (list 5))
   ambiguous)
;; be equivalent to:
(let ambiguous ([+ 1]
                 [- 2]
                 [list 5])
   ambiguous)
;; or should it be:
(let loop ([ambiguous (list (+ 1) (- 2) (list 5))])
   ambiguous)
;; or should it be a syntax error?
--8<---------------cut here---------------end--------------->8---

Reviewing the Git history, I found that the original draft of SRFI 5
addressed the ambiguity between `rest` bindings and
`define`/"signature"-style bindings somewhat more explicitly in
<https://github.com/scheme-requests-for-implementation/srfi-5/blob/3f1748c1c25c7c5ec75b1b0376074da583f39577/srfi-5.html#L245-L258>:

--8<---------------cut here---------------start------------->8---
<H1>Implementation</H1>

Here is an implementation, a macro written in Clinger's explicit-renaming
style.

<p>In explanation...  A symbol in the bindings position, or a
symbol as the first element of the bindings, indicates that a
named-let expander should be used.  Otherwise, a normal let
expander is used, which generates the equivalent lambda
expression.  The named-let expander traverses the bindings
supplied to it, accumulating parameters and arguments. Note that
a symbol as a binding indicates that it is a rest parameter, and the
remaining list of bindings are actually the rest arguments.
</p>
--8<---------------cut here---------------end--------------->8---

That explanation was removed in commit
f2d9ac8c3f07c9480d7e4bdf1dab1c84369ca366, which replaced the
explicit-renaming implementation with one based on `syntax-rules`, as
discussed in <https://srfi-email.schemers.org/srfi-5/msg/2776363/>.

Even when it was present, that explanation was presented as an
implementation detail, but the semantics of these cases matters to the
specification.

Further, the explanation and (to the extent that I understand it---I've
never worked with explicit-renaming macros) implementation from the
first draft pose a further problem, in that they seem as though they
might to forbid using "rest" bindings without a `let-name`, but the
final implementation explicitly supports such expressions, and they are
no less consistent with the "formal specification" than "informal syntax
1" is. For example, the following expression is currently supported:

--8<---------------cut here---------------start------------->8---
(s5:let ([x 1] . [y 2 3])
   (cons x y))
;; -> '(1 2 3)
--8<---------------cut here---------------end--------------->8---

Third, while this is not an error or ambiguity in the text of SRFI 5,
the use of dot notation obscures an important difference between SRFI 5
and RNRS forms that support "rest" bindings. (I guess consider this my
rebuttal to <https://srfi-email.schemers.org/srfi-5/msg/2776367/>.) In
other Scheme forms, such as:

--8<---------------cut here---------------start------------->8---
(lambda args args)
(lambda (x . args)
   (cons x args))
(case-lambda
   [(x . args)
    (cons x args)]
   [args
    args])
(define (list . args)
   args)
--8<---------------cut here---------------end--------------->8---

the dot must be used because the "rest" argument identifier is the tail
of an improper (syntactic) list: at the extreme, the argument "list" is
actually an identifier/symbol. In contrast, in the SRFI 5 grammar at
<https://github.com/scheme-requests-for-implementation/srfi-5/blob/ead1ea67b3a5746d640afc70fd2a09422450cea0/srfi-5.html#L152-L159>:

--8<---------------cut here---------------start------------->8---
        bindings = "(" ")"
                 | rest-binding
                 | "(" normal-bindings ["." rest-binding] ")"
normal-bindings = nothing
                 | normal-binding normal-bindings
  normal-binding = "(" binding-name expression ")"
    binding-name = identifier
    rest-binding = "(" binding-name expressions ")"
--8<---------------cut here---------------end--------------->8---

the `bindings` and `rest-bindings` productions will always be proper
lists. Concretely, that is, the following expressions are all allowed
and evaluate to the empty list:

--8<---------------cut here---------------start------------->8---
(s5:let loop ((x 1) . (args))
   args)
(s5:let loop ((x 1) args)
   args)
(s5:let (loop . (args))
   args)
(s5:let (loop args)
   args)
--8<---------------cut here---------------end--------------->8---

whereas the following would be an error:

--8<---------------cut here---------------start------------->8---
(s5:let loop ((x . 1) . args)
   args)
--8<---------------cut here---------------end--------------->8---

This difference---which the text of SRFI 5 never points out explicitly,
despite its general emphasis on "consistency" with other Scheme
forms---may contribute to confusion over the ambiguous case I explained
above: at least, it did for me.

As far as resolving the ambiguity, the implementation in SRFI 5 requires
that there be either a `loop-name` (either "define-style" or
"defun-style") or at least one binding pair before the
`rest-bindings-production`, so the example I gave above is treated as
the "define-style" case:

--8<---------------cut here---------------start------------->8---
(let (ambiguous (+ 1) (- 2) (list 5))
   ambiguous)
;; In practice:
(let ambiguous ([+ 1]
                 [- 2]
                 [list 5])
   ambiguous)
--8<---------------cut here---------------end--------------->8---

When I wrote a free/libre* reimplementation of SRFI 5 for Racket back in
2019 (see <https://github.com/racket/srfi/pull/7>), my goal was to have
the same behavior as the sample implementation, but my understanding of
this situation was faulty, so both that example and `(let a (b (+ 1))
b)` are currently syntax errors: I intend to fix that soon.

I haven't studied the other implementations in email and the Git history
as closely, but I think they all agree with the sample implementation
(with the possible exception of the first draft, due to the issue I
pointed out above).

I think implementers should be advised to do likewise for the sake of
compatibility.

* Background:

According to
<https://srfi-email.schemers.org/srfi-announce/msg/2652023/>, when the
SRFI editors adopted the MIT license in 2005, no one was able to contact
the author of SRFI 5, Andy Gaynor, so it is still subject to the
original SRFI license. Because that license included some well-intended
but problematically-worded restrictions on modifications, Debian,
Fedora, and other distributions with rigorous license standards are not
able to ship it. Racket has long included SRFI 5 in its main
distribution, so I wrote a free/libre implementation to mitigate the
impact: I'm currently writing free replacement documentation, which led
me to revisit these questions and discover the issues with the original
document.

I also wrote a test suite to check compatibility between the sample
implementation and my implementation: once I've fixed the poorly-chosen
test case that reinforced my confusion, I'd be happy to port that, at
least, to vanilla Scheme and contribute it upstream.

-Philip