Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

weird cond expansion #835

Open
olopierpa opened this issue May 22, 2024 · 8 comments
Open

weird cond expansion #835

olopierpa opened this issue May 22, 2024 · 8 comments

Comments

@olopierpa
Copy link
Contributor

olopierpa commented May 22, 2024

Hello,

this expansion is surprising:

> (expand '(cond (else (define x 7) x)))
(begin (set! x 7) x)

No difference between cond from chezscheme or from rnrs.

If I understand correctly the spec, in R6RS it's an error.

It would be better to either make all cond branches bodies, as Racket does, or raise an error.

As it is now:

> (cond (else (define x 7) x))
7
> x
7

Cheers

@jltaylor-us
Copy link
Contributor

R6RS specifies that each of the pieces of syntax in the else clause are an <expression>, but I'm not aware of any way to reliably distinguish between a bit of syntax that is only valid in expression context (vs. definition context) in a syntax transformer. Using the example definition of the cond syntax given in appendix B, the very first clause in the transformer turns (cond (else result1 result2 ...)) into (begin result1 result2 ...). When you couple that with the description of begin as a "splicing" construct, it means that the contents of that clause can begin with definitions if the containing context is valid for definitions. I assume you are trying this out in the REPL, where any new form can be a definition.

Since the default environment for expand is the interaction environment, I think you will always get that result from expand. However, if you try to actually run it in a context that is not valid for definitions then you will get different results; likewise if you run it in a context that has a different scope from top-level you will get different results.

> (let () (expand '(cond (else (define x 7) x))))
(begin (set! x 7) x)

> (let () (cond (else (define x 7) x)))
7
> x
Exception: variable x is not bound

> (expand '(let () (cond (else (define x 7) x))))
(letrec* ([#{x cqotkwzgeeepeo4i4c5k004i4-0} 7])
  #{x cqotkwzgeeepeo4i4c5k004i4-0})

> (expand '(let () 23 (cond (else (define x 7) x))))
Exception: invalid context for definition (define x 7)

@olopierpa
Copy link
Contributor Author

olopierpa commented May 23, 2024

Got it. Thank you.

Indeed when the cond is not in a definition position it works as I expected:
> (let ()
42
(cond (else (define x 7) 7)))
Exception: invalid context for definition (define x 7)

But, even understanding the cause, the following still "feels" wrong:
> (let ()
(cond (else (define x 7) 7))
x)
7

If instead of expanding (cond (else result1 result2 ...)) into (begin result1 result2 ...)
it expanded into (begin <void> result1 result2 ...) would this problem be solved?

Like:
> (let ()
(begin (define x 7) 7))
7
> (let ()
(begin (void) (define x 7) 7))
Exception: invalid context for definition (define x 7)
Type (debug) to enter the debugger.

Thanks!

@gwatt
Copy link
Contributor

gwatt commented May 23, 2024

It's worth noting that this behavior only appears for (cond [else ...]) forms. Having other branches for cond will always fail:

> (cond
    [#f (define x 1234) x]
    [else (define y 5678) y])
Exception: invalid context for definition (define x 1234)
> (cond
    [#f (define x 1234) x])
Exception: invalid context for definition (define x 1234)

Though I'm not sure if that's an argument for making only-child else branches behave as if there were other branches in the cond, or just saying "well don't write code that way"

@damien-mattei
Copy link

damien-mattei commented May 23, 2024

but this should not works:
Chez Scheme Version 10.0.0
Copyright 1984-2024 Cisco Systems, Inc.
`
(define (foo) (cond (else (define x 7) 3)) x)

(foo)

7
`

@mflatt
Copy link
Contributor

mflatt commented May 23, 2024

but I'm not aware of any way to reliably distinguish between a bit of syntax that is only valid in expression context (vs. definition context) in a syntax transformer.

Instead of detecting non-expression contexts, the macro could make sure that the result is an expression context. For example, (cond [else e1 e2 ...]) could expand to (if #t (begin e1 e2)). Some form with a reliable expression context is needed (that allows any number of return values), and (if #t ....) is the simplest option I see among the expander's core forms. We could also add a core form like $expression , but I'd be inclined to stick with the available option.

It looks like (or (define x 7)) and (and (define x 7)) have the same issue — not because they use cond, but because they have a similar expansion strategy.

Fixing cond, or, and and with a wrapper like (if #t (begin ....)) seems like a good idea to me. It also seems possible that existing Chez Scheme code relies on the current behavior — but hopefully that kind of code is rare, and maybe better matching the standard is enough reason to break any programs like that.

@mflatt
Copy link
Contributor

mflatt commented May 23, 2024

I see that case does have a similar difference from R6RS, after all: it puts the content of an else clause into an internal-definition context, while other clauses of a case form are not internal-definition contexts. Changing that behavior seems likely to break existing code, though, since internal-definition behavior is reasonable, and since it happens for else even when there are other clauses before.

@jltaylor-us
Copy link
Contributor

jltaylor-us commented May 23, 2024

I remain unconvinced that anything is broken, even if it's a little strange. Both the r6rs and r7rs specs give definitions of cond that work this way - as a "sample definition" in the r6rs spec, but in the chapter titled "Formal syntax and semantics" in r7rs. Similarly, syntax definitions for and and or are also given in both specs. On the other hand, the description of the semantics of cond says that each element in the else clause is an <expression>, and is silent on what an implementation may/should/must do if one of them is not (possibly because the authors assumed that it would be flagged as an error post-expansion). It would be ideal if this tension were resolved in the errata of the specs (but that seems unlikely).

I'm a bit hesitant about any non-backwards-compatible change that makes previously valid code no longer valid, absent a major version number change. It does seem rather unlikely that something is relying on this specific behavior, though.

@olopierpa
Copy link
Contributor Author

olopierpa commented May 24, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants