29
78
Fork
You've already forked r7rs
2

R6RS when and unless #321

Open
opened 2026年04月18日 03:14:53 +02:00 by johnwcowan · 16 comments

In my opinion, when and unless in R6RS are mis-designed. If the test argument is true, they return the value(s) of the last expression; otherwise, unspecified values are returned. But you cannot safely make use of the result, because the unspecified value(s) might happen to be the same as the value(s) of the last expression. Consequently, R7RS returns an unspecified value in both cases.

I think we should simply say that the value(s) of these forms are unspecified whether the macro is imported from (scheme base) or (rnrs base).

In my opinion, `when` and `unless` in R6RS are mis-designed. If the *test* argument is true, they return the value(s) of the last expression; otherwise, unspecified values are returned. But you cannot safely make use of the result, because the unspecified value(s) might happen to be the same as the value(s) of the last expression. Consequently, R7RS returns an unspecified value in both cases. I think we should simply say that the value(s) of these forms are unspecified whether the macro is imported from `(scheme base)` or `(rnrs base)`.
Member
Copy link

+1

+1
Member
Copy link

I believe the reason that when and unless return the values of their body is because it would be very easy to make them tail-evaluate their bodies. Compare:

(define-syntax r6rs:when
 (syntax-rules ()
 ((_ cond body ...)
 (if cond (let () body ...)))))
(define-syntax r7rs:when
 (syntax-rules ()
 ((_ cond body ...)
 (if cond
 (let ()
 body ...
 (unspecified-value)))))) ; If this was not here, then
 ; the form could return
 ; multiple values.
(r6rs:when #t (values 1 2)) => 1 2
(r6rs:when #t (values 1 2)) => unspecified

EDIT: the R7RS-Small's sample implementation of when doesn't return an unspecified value on true. I don't know of any Scheme that is pedantically correct about that. We could probably adopt R6RS behavior in that case.

I believe the reason that `when` and `unless` return the values of their body is because it would be very easy to make them tail-evaluate their bodies. Compare: ```scheme (define-syntax r6rs:when (syntax-rules () ((_ cond body ...) (if cond (let () body ...))))) (define-syntax r7rs:when (syntax-rules () ((_ cond body ...) (if cond (let () body ... (unspecified-value)))))) ; If this was not here, then ; the form could return ; multiple values. (r6rs:when #t (values 1 2)) => 1 2 (r6rs:when #t (values 1 2)) => unspecified ``` EDIT: the R7RS-Small's sample implementation of `when` doesn't return an unspecified value on true. I don't know of any Scheme that is pedantically correct about that. We could probably adopt R6RS behavior in that case.

@phm wrote in #321 (comment):

I believe the reason that when and unless return the values of their body is because it would be very easy to make them tail-evaluate their bodies. Compare:

(define-syntax r6rs:when
 (syntax-rules ()
 ((_ cond body ...)
 (if cond (let () body ...)))))
(define-syntax r7rs:when
 (syntax-rules ()
 ((_ cond body ...)
 (if cond
 (let ()
 body ...
 (unspecified-value)))))) ; If this was not here, then
 ; the form could return
 ; multiple values.
(r6rs:when #t (values 1 2)) => 1 2
(r6rs:when #t (values 1 2)) => unspecified

EDIT: the R7RS-Small's sample implementation of when doesn't return an unspecified value on true. I don't know of any Scheme that is pedantically correct about that. We could probably adopt R6RS behavior in that case.

@dpk had posted a thread on scheme-reports-wg1 mailing list about it https://groups.google.com/g/scheme-reports-wg1/c/f-6osK_Idv4

In short, we can make it return "unspecified values" (which entails the multiple values case). However, Alex Shinn (another editor of R7RS-small) opposed making this change in small. He'd prefer making when and unless a non-tail form.

Personally, I don't agree with Alex's opinion. Making when/unless non-tail form will break the code that use tail call and when to express while loop. For example:

(let loop! ()
 (when <cond-expr>
 ...
 (loop!)))

may not be able to run in constant space.

@phm wrote in https://codeberg.org/scheme/r7rs/issues/321#issuecomment-13279482: > I believe the reason that `when` and `unless` return the values of their body is because it would be very easy to make them tail-evaluate their bodies. Compare: > > ```scheme > (define-syntax r6rs:when > (syntax-rules () > ((_ cond body ...) > (if cond (let () body ...))))) > > (define-syntax r7rs:when > (syntax-rules () > ((_ cond body ...) > (if cond > (let () > body ... > (unspecified-value)))))) ; If this was not here, then > ; the form could return > ; multiple values. > (r6rs:when #t (values 1 2)) => 1 2 > (r6rs:when #t (values 1 2)) => unspecified > ``` > > EDIT: the R7RS-Small's sample implementation of `when` doesn't return an unspecified value on true. I don't know of any Scheme that is pedantically correct about that. We could probably adopt R6RS behavior in that case. @dpk had posted a thread on scheme-reports-wg1 mailing list about it https://groups.google.com/g/scheme-reports-wg1/c/f-6osK_Idv4 In short, we can make it return "unspecified values" (which entails the multiple values case). However, Alex Shinn (another editor of R7RS-small) opposed making this change in small. He'd prefer making `when` and `unless` a non-tail form. Personally, I don't agree with Alex's opinion. Making `when`/`unless` non-tail form will break the code that use tail call and `when` to express while loop. For example: ```scheme (let loop! () (when <cond-expr> ... (loop!))) ``` may not be able to run in constant space.
Owner
Copy link

The final resolution of this issue depends on the SC decision on whether we can bend small language compatibility in certain areas, specifically whether we are allowed to return to the R6RS ‘unspecified values’ semantics for forms with no sensible specific return value in the general case.

In the meanwhile, since the R7RS small spec is a nonsense here, and WG1 has failed to clean up their mess, I feel we are at liberty to clean it up in any way we deem sensible.

The draft spec allows ‘unspecified values’, plural, for these two forms only as a temporary compromise.

The final resolution of this issue depends on the SC decision on whether we can bend small language compatibility in certain areas, specifically whether we are allowed to return to the R6RS ‘unspecified values’ semantics for forms with no sensible specific return value in the general case. In the meanwhile, since the R7RS small spec is a nonsense here, and WG1 has failed to clean up their mess, I feel we are at liberty to clean it up in any way we deem sensible. The [draft spec](https://codeberg.org/scheme/r7rs/src/branch/main/fascicles/procedural-fascicle.txt#L1848-L1860) allows ‘unspecified values’, plural, for these two forms only as a temporary compromise.
dpk changed title from (削除) R6RS when and until (削除ここまで) to R6RS when and unless 2026年04月18日 14:56:36 +02:00
Member
Copy link

@johnwcowan wrote in #321 (comment):

In my opinion, when and until in R6RS are mis-designed. If the test argument is true, they return the value(s) of the last expression; otherwise, unspecified values are returned. But you cannot safely make use of the result, because the unspecified value(s) might happen to be the same as the value(s) of the last expression. Consequently, R7RS returns an unspecified value in both cases.

I think we should simply say that the value(s) of these forms are unspecified whether the macro is imported from (scheme base) or (rnrs base).

The R6RS specification is not nonsensical. As others have remarked, the last expression would otherwise not be in tail context.

@johnwcowan wrote in https://codeberg.org/scheme/r7rs/issues/321#issue-4423416: > In my opinion, `when` and `until` in R6RS are mis-designed. If the _test_ argument is true, they return the value(s) of the last expression; otherwise, unspecified values are returned. But you cannot safely make use of the result, because the unspecified value(s) might happen to be the same as the value(s) of the last expression. Consequently, R7RS returns an unspecified value in both cases. > > I think we should simply say that the value(s) of these forms are unspecified whether the macro is imported from `(scheme base)` or `(rnrs base)`. The R6RS specification is not nonsensical. As others have remarked, the last expression would otherwise not be in tail context.
Member
Copy link
  1. Requiring when and unless to evaluate their bodies in tail-position is useful in an imperative state machine where iteration is expressed in tail-calls.
  2. Is there a single -Small implementation that doesn't return the values of when and unless? If nobody implements the behavior "correctly" then we shouldn't be bound to it. The R7RS-Small is not a suicide pact.

EDIT:
Maybe just leave the when and unless forms from (scheme base) as is, and let people who want the better behavior import from (rnrs base). And a note explaining the difference. The two variants are trivial to define.

1. Requiring `when` and `unless` to evaluate their bodies in tail-position is useful in an imperative state machine where iteration is expressed in tail-calls. 2. Is there a single -Small implementation that doesn't return the values of `when` and `unless`? If nobody implements the behavior "correctly" then we shouldn't be bound to it. [The R7RS-Small is not a suicide pact.](https://en.wikipedia.org/wiki/The_Constitution_is_not_a_suicide_pact) EDIT: Maybe just leave the `when` and `unless` forms from `(scheme base)` as is, and let people who want the better behavior import from `(rnrs base)`. And a note explaining the difference. The two variants are trivial to define.
Member
Copy link

I am also in favor of the R6RS (or, at least, tail-calling) semantics here--I use (when ... (loop)) all the time.

I am also in favor of the R6RS (or, at least, tail-calling) semantics here--I use `(when ... (loop))` all the time.
Owner
Copy link

Me, too. I agree on tail calling.

Me, too. I agree on tail calling.
Member
Copy link

According to the rules of the R7RS-Small, the following must always evaluate to the empty list:

(let-values (((x . rest) (when #t (values 1 2))))
 rest)

Foment, Chibi, Cyclone, CHICKEN 5, CHICKEN 6, TR7, Gauche, Gambit, MIT-Scheme, SKINT, STklos, Racket‡, Guile†, Loko†, Sagittarius†, and Mosh† all have this evaluate to (2): They follow the R6RS semantics of this expression. I conjecture that all R7RS implementations with a userbase larger than a single author break with the standard here.

†: Hybrid R6RS/R7RS implementations. (import (scheme base)) was run to import the R7RS definitions.
‡: Unofficial R7RS mode

According to the rules of the R7RS-Small, the following must always evaluate to the empty list: ```scheme (let-values (((x . rest) (when #t (values 1 2)))) rest) ``` Foment, Chibi, Cyclone, CHICKEN 5, CHICKEN 6, TR7, Gauche, Gambit, MIT-Scheme, SKINT, STklos, Racket‡, Guile†, Loko†, Sagittarius†, and Mosh† all have this evaluate to `(2)`: They follow the R6RS semantics of this expression. I conjecture that all R7RS implementations with a userbase larger than a single author break with the standard here. †: Hybrid R6RS/R7RS implementations. `(import (scheme base))` was run to import the R7RS definitions. ‡: [Unofficial R7RS mode](https://pkgs.racket-lang.org/package/r7rs)
Author
Owner
Copy link

Concedo: this argument is compelling. So this is a necessary change to R7RS-small.

Concedo: this argument is compelling. So this is a necessary change to R7RS-small.
Member
Copy link

Thanks for investigating that, @phm. I wonder if there are other cases in which R7's single unspecified result causes similarly unexpected behavior.

Thanks for investigating that, @phm. I wonder if there are other cases in which R7's *single* unspecified result causes similarly unexpected behavior.
Member
Copy link

@Zipheir wrote in #321 (comment):

Thanks for investigating that, @phm. I wonder if there are other cases in which R7's single unspecified result causes similarly unexpected behavior.

R6RS allows for-each (and similar procedures) to tail-call the passed procedure on the final iteration, i.e.

(for-each (lambda (x) (values x 2)) '(a b))

is allowed to evaluate to b 2.

A quick test shows that only Chez and Loko (R6RS impls.) return 1 2, everyone else returns a single unspecified value.

@Zipheir wrote in https://codeberg.org/scheme/r7rs/issues/321#issuecomment-13388655: > Thanks for investigating that, @phm. I wonder if there are other cases in which R7's _single_ unspecified result causes similarly unexpected behavior. R6RS allows `for-each` (and similar procedures) to tail-call the passed procedure on the final iteration, i.e. ```scheme (for-each (lambda (x) (values x 2)) '(a b)) ``` is allowed to evaluate to `b 2`. A quick test shows that only Chez and Loko (R6RS impls.) return `1 2`, everyone else returns a single unspecified value.
Author
Owner
Copy link

the R7RS-Small's sample implementation of when doesn't return an unspecified value on true

It's academic now, but I will point out that "'returns an unspecified value" does not mean that there must be a value of a special type called "unspecified" that has to be returned. If the spec says "returns an unspecified value", and a particular implementation returns 0 or 3.141592653 or a 1024-element vector, that implementation is compliant. So there is no way to test an implementation to see if it returns an unspecified value: only if it returns the wrong number of values is it out of compliance.

One-armed if has the opposite problem in both R6 and R7: if the test is true, it returns the values of the the consequent, otherwise it returns a (single) unspecified value. However, this is not true of cond or case expressions without else clauses: they may return multiple values in R6 but not in R7.

What's unique about these syntactic forms is that, unlike every other expression or procedure in R6RS, they conditionally return unspecified values: in all the other cases they are returned unconditionally. Anything that returns an unpredictable number of values can only be invoked in a context where its values will be discarded, in tail position, or as an initialization of a let-values or equivalent. Racket exceptionally rejects one-armed if syntactically except in those contexts. Fortunately, no Scheme goes to the effort of returning more than one unspecified value.

Lastly, R7 does require that the final expressions of when and unless be tail called if the test is true.

> the R7RS-Small's sample implementation of `when` doesn't return an unspecified value on true It's academic now, but I will point out that "'returns an unspecified value" does not mean that there must be a value of a special type called "unspecified" that has to be returned. If the spec says "returns an unspecified value", and a particular implementation returns 0 or 3.141592653 or a 1024-element vector, that implementation is compliant. So there is no way to *test* an implementation to see if it returns an unspecified value: only if it returns the wrong number of values is it out of compliance. One-armed `if` has the opposite problem in both R6 and R7: if the test is true, it returns the values of the the consequent, otherwise it returns a (single) unspecified value. However, this is not true of `cond` or `case` expressions without `else` clauses: they may return multiple values in R6 but not in R7. What's unique about these syntactic forms is that, unlike every other expression or procedure in R6RS, they *conditionally* return unspecified values: in all the other cases they are returned unconditionally. Anything that returns an unpredictable number of values can only be invoked in a context where its values will be discarded, in tail position, or as an initialization of a `let-values` or equivalent. Racket exceptionally rejects one-armed `if` syntactically except in those contexts. Fortunately, no Scheme goes to the effort of returning more than one unspecified value. Lastly, R7 *does* require that the final expressions of `when` and `unless` be tail called if the test is true.
Owner
Copy link

@johnwcowan wrote in #321 (comment):

One-armed if has the opposite problem in both R6 and R7: if the test is true, it returns the values of the the consequent, otherwise it returns a (single) unspecified value. However, this is not true of cond or case expressions without else clauses: they may return multiple values in R6 but not in R7.

Why do you believe one-armed if must return only one value in R6? Like any expression of unspecified result, it may return any number of values.

@johnwcowan wrote in https://codeberg.org/scheme/r7rs/issues/321#issuecomment-13391862: > One-armed `if` has the opposite problem in both R6 and R7: if the test is true, it returns the values of the the consequent, otherwise it returns a (single) unspecified value. However, this is not true of `cond` or `case` expressions without `else` clauses: they may return multiple values in R6 but not in R7. Why do you believe one-armed `if` must return only one value in R6? Like any expression of unspecified result, it may return any number of values.
Member
Copy link

R6RS §11.4.3, (emphasis added):

If 〈test〉 yields #f and no 〈alternate〉 is specified, then the result of the expression is unspecified.

(削除) I assume that it returns unspecified values like most other situations, but (削除ここまで) It uses the singular here. (Also set!.) Compare to cond:

If all 〈test〉s evaluate to #f, and there is no else clause, then the conditional expression returns unspecified values.

EDIT: The only identifiers that use the singular form is if and set!: everything else uses unspecified values (plural). Given the preciseness of the R6RS, I think if (and set!) are required to return precisely one unspecified value.

R6RS §11.4.3, (emphasis added): > If 〈test〉 yields #f and no 〈alternate〉 is specified, then the **result** of the expression is unspecified. ~~I assume that it returns unspecified values like most other situations, but~~ It uses the singular here. (Also `set!`.) Compare to `cond`: > If all 〈test〉s evaluate to #f, and there is no else clause, then the conditional expression returns unspecified values. EDIT: The only identifiers that use the singular form is `if` and `set!`: everything else uses `unspecified values` (plural). Given the preciseness of the R6RS, I think `if` (and `set!`) are required to return precisely one unspecified value.
Author
Owner
Copy link

Exactly. I assume that R6RS says what it means.

Exactly. I assume that R6RS says what it means.
Sign in to join this conversation.
No Branch/Tag specified
main
pages
proc-draft
c-trichotomy
global-id-prop-ref
allow-van-tonder-like-expanders
No results found.
Labels
Clear labels
Batteries
Issues for the Batteries volume of the report, providing a standard library
Cleanup
Issues where inconsistencies or other infelicities have arisen
Consent Docket
Items which will be automatically be given a certain resolution if no objection is made by a certain date
Environments
Issues for the Environments volume of the report, providing OS interfaces and similar
Foundations
Issues for the Foundations volume of the report, related to the core language semantics
Macrological Fascicle
Meta
Procedural and organisational issues
Multiple proposals exist
Multiple competing, concrete proposals to resolve this issue exist (whether they are SRFI or still only pre-SRFI)
Pie-in-the-sky ideas
Ideas currently lacking any form of worked-out proposal
Problems without solutions
Problems we *must* solve but haven’t worked out any solution for yet
Procedural Fascicle
Public Comment
Publications
Issues related to the practicalities of making the report available
Question
For information and clarification
R6RS Compatibility
Issue touches on questions of R6RS compatibility
R7RS-small
Issues with the Small language Report
Resolution in draft
A resolution (not necessarily final) is specified in the current draft of the relevant fascicle
Specification exists
Proposals for which a viable, but incomplete specification text exists
SRFI/RnRS spec exists
A SRFI for this proposal exists, or a solution from another RnRS
Milestone
Clear milestone
No items
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
8 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
scheme/r7rs#321
Reference in a new issue
scheme/r7rs
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?