in-value: fix inlined binding

The inlined version of `in-value` in a `for` form did not bind the
left-hand identifier at the right layer relative to other bindings in
`for/fold`, which caused the inlined `in-value` to behave differently
than a non-inlined `in-value`. Confusing about this `in-value`, in
turn, had led to incorrect documentation on `for/fold`.

It would be nice to clean up a little more of the evaluator and
availability of bindings in `for/fold`, but doing so runs a
significant risk of breaking existing code (unlike fixing `in-value`,
which behaved even worse and where the repair seems less likely to
break existing programs).

Closes #1659 (again)
This commit is contained in:
Matthew Flatt 2021-04-28 14:19:50 -06:00
parent 09480c86e8
commit 94725ffb4e
3 changed files with 34 additions and 15 deletions

View File

@ -374,19 +374,19 @@ terminates, if a @racket[result-expr] is provided then the result of the
] ]
The binding and evaluation order of @racket[accum-id]s and The binding and evaluation order of @racket[accum-id]s and
@racket[init-expr]s does not follow the textual, left-to-right order @racket[init-expr]s do not completely follow the textual,
relative to the @racket[for-clause]s . Instead, the sequence left-to-right order relative to the @racket[for-clause]s. Instead, the
expressions in @racket[for-clause]s that determine the outermost sequence expressions in @racket[for-clause]s that determine the
iteration are evaluated first, the associated identifiers are bound, outermost iteration are evaluated first, then the @racket[init-expr]s
and then the @racket[init-expr]s are evaluated and the are evaluated and the @racket[accum-id]s are bound, and finally the
@racket[accum-id]s are bound. One consequence is that the outermost iteration's identifiers are bound. One consequence is that
@racket[accum-id]s are not bound in @racket[for-clause]s for the the @racket[accum-id]s are not bound in @racket[for-clause]s for the
outermost initialization. Another consequence is that when a outermost initialization. At the same time, when a @racket[accum-id]
@racket[accum-id] is used as a @racket[for-clause] binding for the is used as a @racket[for-clause] binding for the outermost iteration,
outermost iteration, the @racket[for-clause] binding is shadowed in the @racket[for-clause] binding shadows the @racket[accum-id] binding
the loop body (even though, syntactically, a @racket[for-clause] is in the loop body (which is what you would expect syntactically).
closer to the body). A fresh variable for each @racket[accum-id] (at a A fresh variable for each @racket[accum-id] (at a
fresh location) is bound to in each nested iteration created by a fresh location) is bound in each nested iteration that is created by a
later group for @racket[for-clause]s (after a @racket[#:when] or later group for @racket[for-clause]s (after a @racket[#:when] or
@racket[#:unless], for example). @racket[#:unless], for example).

View File

@ -461,6 +461,25 @@
(test 13 next) (test 13 next)
(test #f more?)) (test #f more?))
(test 1 'in-value-bind-correctly (for/fold ([x #f])
([x (in-value 1)])
x))
(test 2 'in-value-bind-correctly (for/fold ([x #f])
([x (values (in-value 2))])
x))
(let ([x 'out]
[prints '()])
(for/fold ([x (begin
(set! prints (cons (list 'top x) prints))
'top)])
([x (in-list (begin
(set! prints (cons (list 'rhs x) prints))
(list 1 2 3)))])
(set! prints (cons x prints))
x)
(test '(3 2 1 (top out) (rhs out)) values prints))
;; check ranges on `in-vector', especially as a value ;; check ranges on `in-vector', especially as a value
(test '() 'in-empty-vector (let ([v (in-vector '#())]) (for/list ([e v]) e))) (test '() 'in-empty-vector (let ([v (in-vector '#())]) (for/list ([e v]) e)))
(test '() 'in-empty-vector (let ([v (in-vector '#() 0)]) (for/list ([e v]) e))) (test '() 'in-empty-vector (let ([v (in-vector '#() 0)]) (for/list ([e v]) e)))

View File

@ -2309,8 +2309,8 @@
(lambda () #'in-value) (lambda () #'in-value)
(lambda (stx) (lambda (stx)
(syntax-case stx () (syntax-case stx ()
[[(id) (_ expr)] [[<(id) (_ expr)]
#'[(id) (:do-in ([(id) expr]) #t () #t () #t #f ())]] #'[(id) (:do-in ([(id*) expr]) #t () #t ([(id) id*]) #t #f ())]]
[_ #f]))) [_ #f])))
(define-sequence-syntax *in-producer (define-sequence-syntax *in-producer