Mark and Continue (a constrained case of GOTO)

There have been many discussions that relate to goto. For example:

There are many more, but as a new contributor, I’m not allowed to include more than two links in this post. The continuing recurrence of this theme suggests to me a general unsettledness on the issue.

I think Dijkstra was wise to champion the elimination of general goto in favor of while, for, and function calls. He argued that human comprehension was important, and the ability to reason about an abstraction required a contract around that abstraction. If you call a function, you need confidence that control flow will return to the call site and not jump to some arbitrary location in the code.

But why does this still keep coming up?

Nested loops (using break, continue) is one recurring theme. Another is workflow logic.

Consider an AI software-factory pipeline:

  1. Research the feature
  2. Plan an implementation
  3. Implement
  4. Review the implementation
    1. If needed, return to Implement
    2. If really needed, return to Plan
  5. Wrap up

Right now, to implement this logic, you’d need something like:

  • Multiple while loops with boolean flags to control continue
  • Multiple while loops with try-except blocks to use exceptions to return

In each case, we depend on while as the only construct for the idea “return to this earlier point of code.” But while was intended to mean “this block of code should run zero or more times”. While related, these are different concepts, and I believe that treating them the same is once source of the ongoing malcontent around break and continue.

As one who has written many workflows throughout his career to date, I would love the ability to present clear, concise workflow logic in Python code. Extra booleans that must be modified in concert—carefully set and cleared—just to communicate “go back to this point” introduces unnecessary complexity and violates the fundamental intent of a programming language: a language serves to bring the articulation of the solution closer to the human’s intuition while providing a deterministic, exact articulation of the bits to the processor. When the human must deviate from their intuition to accommodate the limitations of the language, and opportunity to consider the language emerges.

So, here’s what I propose.

print('hi')
mark research:
    print('Did research')

mark plan:
    print('Planned')

mark impl:
    print('Implemented')

mark review:
    decision = decide(...)
    if decision == 'bad approach':
        continue impl
    if decision == 'bad idea':
        continue plan

mark wrap:
    print('All done!')

What does this show?

  • mark is a new keyword. It represents a control statement like while, and within that block a continue might direct control flow back to this location. It accepts a valid Python identifier as a label for the mark.
  • Multiple mark statements chain together, like if and elif. A nestedcontinue can jump to any marks within the chained statement.
  • break would be used to exit the mark statement (i.e. the chain).

Importantly, this is not arbitrary goto. Just as break and continue are bounded by a containing loop, mark provides a scope for the logical jump, maintaining the boundaries of the abstraction.

Other thoughts:

  • Does it need to be continue? No—we could use goto, jump, or any other term. But continue already exists and already carries the semantics of “jump from here to another location”. This would just add an extended syntax.
  • How are nested mark statements handled? Can you continue to an outer mark? We allow nested try-except blocks, so my hunch would be yes. continue would find the first mark statement containing a matching label, searching from inner to outer, just like nested except blocks.
  • How do we determine the end of a mark chain? I’m not decided on this. You could have another keyword, like end, but it is only needed when the scope (i.e. function) requires multiple mark chains that must stay separate. It seems like a rare case to impose so much verbosity. My inclination is to just say that all mark statements of the same indentation within a scope are automatically chained. The code author can decide which mark labels to jump to or not.
  • This has nice similarities to while and addresses some nested while use-cases; what about for? This is definitely an open question, but my hunch so far is this: for is by definition more structured and bounded than while, and this more amenable to decomposition with function calls. Because for is bound to the state of the list (or other iterable), the logic of the loop can be transferred to another function that accepts the list. However, while is used when the state controlling the loop is more complex and often relies on and mutates multiple values within the scope. These scenarios are not amenable to decomposition because the new function isolates the state too much. Thus, it may be that most nested-for scenarios can be cleanly handled with function calls, while many nested-while scenarios might be simplified with mark syntax.
print('hi')
def research():
    print('Did research')
    return plan

def plan():
    print('Planned')
    return impl

def impl():
    print('Implemented')
    return review

def review():
    decision = decide(...)
    if decision == 'bad approach':
        return impl
    if decision == 'bad idea':
        return plan
    return wrap

def wrap():
    print('All done!')

step = research
while step:
    step = step()
7 Likes

The example you include reads more like a state machine and there are many ways currently without new syntax to achieve state machines in Python.

I find the new continue syntax you are recommending to be something that would hinder readability. With continue <label> the “where am I” information lives only in the control flow, so a reader has to find every continue that targets a mark to know what can be true when that block starts.