Add perchance, a soft keyword for exception-tolerant expressions (PoC)#153767
Closed
ambv wants to merge 2 commits into
Closed
Add perchance, a soft keyword for exception-tolerant expressions (PoC)#153767ambv wants to merge 2 commits into
perchance, a soft keyword for exception-tolerant expressions (PoC)#153767ambv wants to merge 2 commits into
Conversation
This introduces an expression-level fallback for exceptions — the thing `dict.get()` does for one specific case, generalized to any fallible expression: `port = int(os.environ["PORT"]) perchance 8080` evaluates the left operand and, if it raises an exception matching the guard (default: `Exception`, never `BaseException`), yields the fallback instead. An explicit guard is spelled `first = seq[0] perchance None from IndexError`, accepting anything an `except` clause accepts. The fallback and guard are lazily evaluated only when an exception occurs, so it short-circuits like `or` but over exceptions rather than falsiness, and chains left-associatively: `d["a"] perchance d["b"] perchance "default"`. This is PEP 463 (exception-catching expressions) with the syntactic objections fixed: no colon inside the expression, no parentheses required, and it reads as English at exactly the point where the program acknowledges chance. The killer use case is comprehensions, where a fallible step today forces a named helper function or an inner try statement: `[int(line) perchance None from ValueError for line in text.splitlines()]`. There is also a statement form, `perchance FileNotFoundError, PermissionError: os.unlink(tmp)`, which is sugar for `try:`/`except (...): pass` — the pattern the stdlib alone spells out ~1360 times. The exception list is mandatory: there is no bare `perchance:`, so swallowing everything requires writing `perchance Exception:` and owning it in review. Design decisions worth noting: the exception object is not bindable (no `as`) — if you need it, you need a try statement; this is the lambda principle, keeping the expression form too small to hide logic in. The default guard is loaded via a new `LOAD_COMMON_CONSTANT` slot (`CONSTANT_EXCEPTION`) rather than a name lookup, so a local `Exception = ...` cannot change what gets caught. A swallowed exception rides along as `__context__` if the fallback itself raises. Mixing with a conditional expression requires parentheses. `perchance` is a soft keyword like `match`: juxtaposed NAME NAME is a syntax error today, so no existing code changes meaning, and `perchance` remains usable as an identifier — `x = perchance perchance 0` parses and means what you'd hope. Implementation: new `Perchance(expr value, expr fallback, expr? guard)` node in Python.asdl; a left-recursive `perchance_expression` grammar rule next to `if_expression`; the statement form is desugared to `Try` directly in the parser action, so it needs no new statement node, symtable, or codegen support. Code generation mirrors `codegen_try_except` and uses the zero-cost exception tables: the happy path executes no extra instructions, with the handler reachable only through the exception table. Both forms are gated with `CHECK_VERSION(16)`. Known wart for a real version: in `raise f() perchance g() from cause`, the `from` binds to the perchance guard, not the raise cause — parenthesize to disambiguate; dedicated invalid-syntax rules with helpful errors are left for later, as are docs and tests. To sleep, perchance to dream. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
|
Perchance not |
The REPL highlights soft keywords only in contexts recognized by `is_soft_keyword_used()` in Lib/_pyrepl/utils.py, which knew about `match`, `case`, `type`, and `lazy` but not `perchance`. Add patterns for both forms: the expression form highlights when the previous token can end an expression (name, number, string, closing bracket, `...`, or a constant keyword) and the next token can start one, via a new `_starts_expression()` helper; the statement form highlights at line start. Identifier uses (`perchance = 5`, `f(perchance=3)`, `perchance.method()`) stay uncolored, and in `x = perchance perchance 0` only the second occurrence — the operator — is highlighted. Also relax the `bracket_level == 0` gate for `perchance` alone: the other soft keywords can only start statements and thus cannot legally appear inside brackets, but the perchance expression form is most at home there, e.g. `[int(v) perchance None for v in vs]`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
|
Rejected |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


This introduces an expression-level fallback for exceptions — the thing
dict.get()does for one specific case, generalized to any fallible expression:evaluates the left operand and, if it raises an exception matching the guard (default:
Exception, neverBaseException), yields the fallback instead.An explicit guard is spelled
accepting anything an
exceptclause accepts.The fallback and guard are lazily evaluated only when an exception occurs, so it short-circuits like
orbut over exceptions rather than falsiness, and chains left-associatively:d["a"] perchance d["b"] perchance "default". This is PEP 463 (exception-catching expressions) with the syntactic objections fixed: no colon inside the expression, no parentheses required, and it reads as English at exactly the point where the program acknowledges chance. The killer use case is comprehensions, where a fallible step today forces a named helper function or an inner try statement:[int(line) perchance None from ValueError for line in text.splitlines()].There is also a statement form:
which is sugar for
try:/except (...): pass— the pattern the stdlib alone spells out ~1360 times. The exception list is mandatory: there is no bareperchance:, so swallowing everything requires writingperchance Exception:and owning it in review.Design decisions worth noting: the exception object is not bindable (no
as) — if you need it, you need a try statement; this is the lambda principle, keeping the expression form too small to hide logic in. The default guard is loaded via a newLOAD_COMMON_CONSTANTslot (CONSTANT_EXCEPTION) rather than a name lookup, so a localException = ...cannot change what gets caught. A swallowed exception rides along as__context__if the fallback itself raises. Mixing with a conditional expression requires parentheses.perchanceis a soft keyword likematch: juxtaposed NAME NAME is a syntax error today, so no existing code changes meaning, andperchanceremains usable as an identifier —x = perchance perchance 0parses and means what you'd hope.Implementation: new
Perchance(expr value, expr fallback, expr? guard)node in Python.asdl; a left-recursiveperchance_expressiongrammar rule next toif_expression; the statement form is desugared toTrydirectly in the parser action, so it needs no new statement node, symtable, or codegen support. Code generation mirrorscodegen_try_exceptand uses the zero-cost exception tables: the happy path executes no extra instructions, with the handler reachable only through the exception table. Both forms are gated withCHECK_VERSION(16). Known wart for a real version: inraise f() perchance g() from cause, thefrombinds to the perchance guard, not the raise cause — parenthesize to disambiguate; dedicated invalid-syntax rules with helpful errors are left for later, as are docs and tests.To sleep, perchance to dream.
To be clear, this is a joke. Please don't close this before Pablo reviews it.