-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
If a lambda re-enters a letrec initialisation expression using a continuation from call/cc, and specifically that lambda is referred to in both branches of an if-expression, it will have the wrong behaviour.
For background, letrec differs from letrec*, in that letrec assigns all of its variables after all of their corresponding initialisation expressions have been evaluated, whereas letrec* assigns its variables one at a time after each corresponding initialisation expression has been evaluated. This difference leads to different behaviours with the following program, which mutates one of the letrec variables before re-entering another variable by its contiuation:
(let ((f (lambda ()
(letrec[*] ((a 4) (j (call/cc (lambda (cc) cc))))
(if (eqv? j 0)
a
(begin
(set! a 9)
(j 0)))))))
(f))
With letrec* the mutation persists and results in the value 9, because a has already been initialised by the point of the call/cc. With letrec, the mutation is overwritten back to a 4, because a has not yet been initialised by the point of the call/cc, and is assigned 4 again after re-entering the initialisation expression for j.
Chez Scheme has the correct behaviour in this example. However, if f is instead reached through an if-expression, and particularly if f is referenced in both branches, letrec has the incorrect behaviour which seems to correspond with letrec*, and gives 9 rather than 4.
(let ((f (lambda ()
(letrec ((a 4) (j (call/cc (lambda (cc) cc))))
(if (eqv? j 0)
a
(begin
(set! a 9)
(j 0)))))))
(if #t (f) (f)))
This example incorrectly produces 9. The correct behaviour should be to produce 4. This happens regardless if the true or false branch is taken. It does not happen if f is only referred to in one of the branches, i.e. (if #t (f) 0) gives 4.
Tested on 10.2.0, NixOS. Correct behaviour witnessed on Chicken.