Skip to content

Bug with letrec call/cc interaction #946

Open
@pylasnier

Description

@pylasnier

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions