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 .xMisuse 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);