There have been many discussions that relate to goto. For example:
- PEP 3136 – Labeled break and continue | peps.python.org
- Add optional `if break:` suite to `for/while` - #3 by tim.one
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:
- Research the feature
- Plan an implementation
- Implement
- Review the implementation
- If needed, return to Implement
- If really needed, return to Plan
- 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?
markis a new keyword. It represents a control statement likewhile, and within that block acontinuemight direct control flow back to this location. It accepts a valid Python identifier as a label for themark.- Multiple
markstatements chain together, likeifandelif. A nestedcontinuecan jump to anymarks within the chained statement. breakwould be used to exit themarkstatement (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 usegoto,jump, or any other term. Butcontinuealready exists and already carries the semantics of “jump from here to another location”. This would just add an extended syntax. - How are nested
markstatements handled? Can youcontinueto an outermark? We allow nestedtry-exceptblocks, so my hunch would be yes.continuewould find the firstmarkstatement containing a matching label, searching from inner to outer, just like nestedexceptblocks. - How do we determine the end of a
markchain? I’m not decided on this. You could have another keyword, likeend, but it is only needed when the scope (i.e. function) requires multiplemarkchains that must stay separate. It seems like a rare case to impose so much verbosity. My inclination is to just say that allmarkstatements of the same indentation within a scope are automatically chained. The code author can decide whichmarklabels to jump to or not. - This has nice similarities to
whileand addresses some nestedwhileuse-cases; what aboutfor? This is definitely an open question, but my hunch so far is this:foris by definition more structured and bounded thanwhile, and this more amenable to decomposition with function calls. Becauseforis 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,whileis 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-forscenarios can be cleanly handled with function calls, while many nested-whilescenarios might be simplified withmarksyntax.