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)
|
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 ((<parameter> <argument>)...) <body>...) </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