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.
If a lambda re-enters a
letrecinitialisation expression using a continuation fromcall/cc, and specifically that lambda is referred to in both branches of an if-expression, it will have the wrong behaviour.For background,
letrecdiffers fromletrec*, in thatletrecassigns all of its variables after all of their corresponding initialisation expressions have been evaluated, whereasletrec*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 theletrecvariables before re-entering another variable by its contiuation:With
letrec*the mutation persists and results in the value9, becauseahas already been initialised by the point of thecall/cc. Withletrec, the mutation is overwritten back to a4, becauseahas not yet been initialised by the point of thecall/cc, and is assigned4again after re-entering the initialisation expression forj.Chez Scheme has the correct behaviour in this example. However, if
fis instead reached through an if-expression, and particularly iffis referenced in both branches,letrechas the incorrect behaviour which seems to correspond withletrec*, and gives9rather than4.This example incorrectly produces
9. The correct behaviour should be to produce4. This happens regardless if the true or false branch is taken. It does not happen iffis only referred to in one of the branches, i.e.(if #t (f) 0)gives4.Tested on 10.2.0, NixOS. Correct behaviour witnessed on Chicken.