Hacker News new | ask | show | jobs
by kazinator 3925 days ago
Note how Lisp's TAGBODY/GO doesn't have this problem.

  (loop
    (tagbody
      (loop for x in '(1 2 3)
            do (go next))
    next
      (print 'out)))
(OUT is printed repeatedly.)

How it works is that tagbody contains a bunch of labels mixed with forms. The tagbody establishes an exit point for GO expressions used in these forms. When a (go label) occurs, it works by abandoning the current sub-form being evaluated, and initiating a control transfer to the exit point, which then transfers control to the appropriate label.

The upshot is that whatever form is interrupted will cleanly unwind, as necessary:

  (tagbody
    (unwind-protect (progn 
                      (go out)
                      (progn 'never-printed))
      (print 'bye-bye))
   out)
Note that you can only have labels and GO in certain forms like TAGBODY and PROG, not just willy nilly in any construct. The labels of a given TAGBODY are all on the same level of nesting: immediate children of the TAGBODY form. SO this isn't possible:

  (tagbody
     (go impossible)
     (let ((x 42))
       impossible x))
Here, impossible is not considered a label associated with the tagbody, so the (go impossible) is branching to a nonexistent label. If it were allowed, of course it would create the problem of what becomes of the initialization of x.

Thus, compilers only have to reason about GO within specific constructs, and rely on those GO's to only be performing abandonment followed by a simple lateral move.