Patternia v0.9.4 Release Note¶
Release Date: August 2026 Version: 0.9.4
Overview¶
Patternia v0.9.4 is a guard-focused release. It introduces PTN_BIND
member-anchored named placeholders for structural guards, unifies the
placeholder syntax around _ and PTN_BIND, and adds operator sugar
(!p, (a || b), (a && b)) for the pattern combinators.
This release contains breaking changes; see Breaking Changes and the Migration Guide below.
Breaking Changes¶
Positional guard APIs removed¶
The positional guard spellings are removed in favor of _ and
PTN_BIND:
| Removed | Replacement |
|---|---|
__ (double-underscore) |
_ |
_0 |
_ |
arg<N> |
PTN_BIND names |
PTN_LET(v, expr) |
PTN_BIND + guard expression |
PTN_WHERE((a, b), expr) |
PTN_BIND + guard expression |
_ now serves both as the single-value guard placeholder and as the
wildcard fallback.
PTN_BIND names must be members of the type¶
PTN_BIND(Type, names...) expands each name to
member_t<&Type::name>. Placeholder names that were aliases rather
than real members of Type no longer compile; use the member names
themselves. Misspellings are now caught at the PTN_BIND line.
New Features¶
PTN_BIND(Type, names...) — named guard placeholders¶
Declares member-anchored named placeholders for guards attached to
has<...>, supporting one to ten names:
struct Point { int x; int y; };
PTN_BIND(Point, x, y);
match(p) | on(
$(has<&Point::x, &Point::y>)[x * x + y * y == 25] >> "on circle",
_ >> "elsewhere"
);
Names follow members, not positions: each name resolves to the
position of its member in the has<...> member list at compile time,
so the order of member pointers in has<...> does not matter:
$(has<&Point::y, &Point::x>)[x == 3 && y == 4] // x is still .x
Misuse is caught at compile time: a misspelled member name fails at
the PTN_BIND line, and a name whose member is not listed in
has<...> fails a static_assert.
Declarations are valid at namespace or block scope. Block-scope
declarations use static storage duration, so they can be referenced
inside PTN_ON's captureless caching lambda:
int classify(const Point &p) {
PTN_BIND(Point, x, y);
return match(p) | PTN_ON(
$(has<&Point::x, &Point::y>)[x * x + y * y == 25] >> 1,
_ >> 0);
}
Operator sugar for combinators¶
The pattern algebra now has operator forms:
match(status) | on(
!val<200> >> "error", // !p == neg(p)
(lit(301) || lit(302)) >> "redirect", // (a||b) == any(a, b)
(pred(is_json) && !lit(0)) >> "json", // (a&&b) == all(a, b)
_ >> "ok"
);
The operators only accept pattern operands, so guard-level && / ||
inside [...] keep their existing meaning. Note that >> binds
tighter than || and &&, so combined patterns need parentheses in a
case; unary ! needs none.
Internal Improvements¶
- CI: commit-style PR validation, clang-format check, LLVM source-based coverage, and a fix letting docs-only PRs satisfy branch protection.
- Bench: compile-time translation units are generated into the build tree instead of the source tree.
- Docs: contributing guide now documents the CI-enforced PR rules.
Migration Guide¶
PTN_LET / PTN_WHERE¶
// v0.9.3
auto g1 = PTN_LET(v, v == 1);
auto g2 = PTN_WHERE((x, y), x < y);
// v0.9.4 — single bound value: use `_`
$[_ == 1] >> ...;
// v0.9.4 — multiple bound values: declare names once
PTN_BIND(Point, x, y);
$(has<&Point::x, &Point::y>)[x < y] >> ...;
arg<N> / _0 / __¶
// v0.9.3
$[_0 > 0] >> ...;
$(has<&Point::x, &Point::y>)[arg<0> < arg<1>] >> ...;
// v0.9.4
$[_ > 0] >> ...;
PTN_BIND(Point, x, y);
$(has<&Point::x, &Point::y>)[x < y] >> ...;
Alias-style PTN_BIND names¶
// main-between-releases only (never in a tagged release):
PTN_BIND(Point, point_x, point_y); // alias names
// v0.9.4 — names must be members of Point:
PTN_BIND(Point, x, y);