Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Expert skill for implementing pattern matching including exhaustiveness checking, decision tree compilation, and efficient match dispatch code generation.
.claude/skills/a5c-ai-pattern-matching/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-18 | ✗→✓ | ▲ Improved | 26% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 80% | 0% |
| case-20 | ✗→✓ | ▲ Improved | 45% | 0% |
| case-23 | ✗→✓ | ▲ Improved | 135% | 0% |
| case-25 | ✗→✓ | ▲ Improved | 123% | 0% |
Implement pattern matching for programming languages including exhaustiveness checking, usefulness analysis, and efficient compilation to decision trees.
Invoke this skill when you need to:
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | patternTypes | array | Yes | Types of patterns to support | | targetLanguage | string | Yes | Language for implementation | | compilationStrategy | string | No | Strategy (decision-tree, backtracking) | | features | array | No | Advanced features to implement |
json{ "patternTypes": [ "wildcard", "variable", "literal", "constructor", "tuple", "record", "list", "or-pattern", "as-pattern", "guard" ] }
json{ "features": [ "exhaustiveness-checking", "usefulness-checking", "decision-tree-compilation", "guard-clauses", "nested-patterns", "view-patterns", "active-patterns" ] }
pattern-matching/
├── syntax/
│ ├── pattern.grammar # Pattern syntax
│ └── match-expr.grammar # Match expression syntax
├── analysis/
│ ├── exhaustiveness.ts # Exhaustiveness checker
│ ├── usefulness.ts # Usefulness/redundancy checker
│ └── pattern-types.ts # Pattern type inference
├── compilation/
│ ├── decision-tree.ts # Decision tree builder
│ ├── code-generator.ts # Code generation
│ └── optimizer.ts # Pattern optimization
├── runtime/
│ ├── matcher.ts # Runtime matching (interpreter)
│ └── guards.ts # Guard evaluation
└── tests/
├── exhaustiveness.test.ts
├── compilation.test.ts
└── runtime.test.tstypescript// Pattern ADT type Pattern = | { type: 'wildcard' } // _ | { type: 'variable'; name: string } // x | { type: 'literal'; value: Literal } // 42, "hello", true | { type: 'constructor'; name: string; args: Pattern[] } // Some(x), Cons(h, t) | { type: 'tuple'; elements: Pattern[] } // (x, y, z) | { type: 'record'; fields: Map<string, Pattern> } // { name: n, age: a } | { type: 'list'; elements: Pattern[]; rest?: Pattern } // [x, y, ...rest] | { type: 'or'; patterns: Pattern[] } // p1 | p2 | { type: 'as'; pattern: Pattern; name: string } // p as x | { type: 'guard'; pattern: Pattern; guard: Expr }; // p if cond // Match expression interface MatchExpr { scrutinee: Expr; arms: MatchArm[]; } interface MatchArm { pattern: Pattern; guard?: Expr; body: Expr; }
typescript// Based on "Warnings for Pattern Matching" (Maranget) type PatternMatrix = Pattern[][]; // rows = arms, columns = scrutinees // Check if pattern matrix is exhaustive function isExhaustive(matrix: PatternMatrix, types: Type[]): boolean { if (matrix.length === 0) return false; if (types.length === 0) return true; const firstCol = matrix.map(row => row[0]); const sigma = getConstructorSignature(types[0]); if (sigma.isComplete(firstCol)) { // All constructors present - check specializations return sigma.constructors.every(ctor => isExhaustive(specialize(matrix, ctor), specializationTypes(types, ctor)) ); } else { // Some constructors missing - check default matrix return isExhaustive(defaultMatrix(matrix), types.slice(1)); } } // Generate witness for non-exhaustiveness function findUncoveredCase(matrix: PatternMatrix, types: Type[]): Pattern[] | null { if (matrix.length === 0) { // Empty matrix - any value is uncovered return types.map(generateWildcard); } if (types.length === 0) return null; // Exhaustive const sigma = getConstructorSignature(types[0]); const firstCol = matrix.map(row => row[0]); if (sigma.isComplete(firstCol)) { for (const ctor of sigma.constructors) { const witness = findUncoveredCase( specialize(matrix, ctor), specializationTypes(types, ctor) ); if (witness) { return [applyConstructor(ctor, witness.slice(0, ctor.arity)), ...witness.slice(ctor.arity)]; } } return null; } else { // Find missing constructor const missing = sigma.constructors.find(c => !firstCol.some(p => matchesCtor(p, c))); if (missing) { return [generatePattern(missing), ...types.slice(1).map(generateWildcard)]; } return findUncoveredCase(defaultMatrix(matrix), types.slice(1)); } }
typescript// Decision tree for efficient matching type DecisionTree = | { type: 'fail' } | { type: 'leaf'; bindings: Map<string, Access>; body: Expr } | { type: 'switch'; access: Access; cases: SwitchCase[]; default?: DecisionTree }; interface SwitchCase { constructor: Constructor; tree: DecisionTree; } interface Access { root: string; path: AccessStep[]; } type AccessStep = | { type: 'field'; index: number } | { type: 'deref' }; // Compile patterns to decision tree function compilePatterns(arms: MatchArm[], scrutinee: Access): DecisionTree { if (arms.length === 0) return { type: 'fail' }; // Find best column to split on (heuristic) const column = selectColumn(arms); // Group arms by constructor in that column const groups = groupByConstructor(arms, column); if (groups.size === 0) { // All wildcards - just use first arm const bindings = extractBindings(arms[0].pattern, scrutinee); return { type: 'leaf', bindings, body: arms[0].body }; } // Build switch node const cases: SwitchCase[] = []; for (const [ctor, ctorArms] of groups) { const specializedAccess = extendAccess(scrutinee, ctor); cases.push({ constructor: ctor, tree: compilePatterns(specializeArms(ctorArms, ctor), specializedAccess) }); } const defaultArms = arms.filter(arm => isWildcard(arm.pattern, column)); const defaultTree = defaultArms.length > 0 ? compilePatterns(defaultArms, scrutinee) : undefined; return { type: 'switch', access: scrutinee, cases, default: defaultTree }; }
typescript// Guards complicate exhaustiveness - we must be conservative interface GuardedArm { pattern: Pattern; guard: Expr | null; body: Expr; } // For exhaustiveness: treat guarded patterns as potentially failing function exhaustivenessWithGuards(arms: GuardedArm[], types: Type[]): Warning[] { const warnings: Warning[] = []; // Remove guards for exhaustiveness check (conservative) const unguardedMatrix = arms.map(arm => [arm.pattern]); if (!isExhaustive(unguardedMatrix, types)) { // May still be exhaustive if guards cover all cases // But we can't know statically - warn warnings.push({ type: 'possibly-non-exhaustive', message: 'Match may not be exhaustive (guards present)', suggestion: 'Consider adding a catch-all pattern' }); } return warnings; } // Decision tree with guards type GuardedTree = | { type: 'fail' } | { type: 'guard'; test: Expr; success: GuardedTree; failure: GuardedTree } | { type: 'leaf'; bindings: Map<string, Access>; body: Expr } | { type: 'switch'; access: Access; cases: SwitchCase[]; default?: GuardedTree };
typescript// Generate code from decision tree function generateCode(tree: DecisionTree, target: CodeTarget): Code { switch (tree.type) { case 'fail': return target.emitMatchFailure(); case 'leaf': const setup = Array.from(tree.bindings.entries()) .map(([name, access]) => target.emitBinding(name, access)); return target.emitBlock([...setup, target.emitExpr(tree.body)]); case 'switch': return target.emitSwitch( target.emitAccess(tree.access), tree.cases.map(c => ({ test: target.emitConstructorTest(c.constructor), body: generateCode(c.tree, target) })), tree.default ? generateCode(tree.default, target) : target.emitMatchFailure() ); } } // Example output for Rust function emitRustMatch(tree: DecisionTree): string { // Input: match x { Some(y) => y + 1, None => 0 } // Output: // match x { // Some(ref __0) => { // let y = __0; // y + 1 // } // None => 0 // } }
typescript// Or-pattern: matches if any sub-pattern matches // (Red | Green | Blue) => "color" function expandOrPattern(pattern: Pattern): Pattern[] { if (pattern.type === 'or') { return pattern.patterns.flatMap(expandOrPattern); } // Recursively expand in sub-patterns // ... return [pattern]; } // As-pattern: binds entire match to name // (x :: xs) as list => (list, x) function handleAsPattern( pattern: AsPattern, access: Access, bindings: Map<string, Access> ): void { // Bind the name to current access bindings.set(pattern.name, access); // Continue with inner pattern extractBindings(pattern.pattern, access, bindings); }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 41,328 | 51,648 | +25% | 1 | 1 | 0% | 8,252 | 11,231 | +36% | 0 | 0 | — |
case-02 | fail→fail | 44,557 | 35,524 | -20% | 1 | 1 | 0% | 8,252 | 11,231 | +36% | 0 | 0 | — |
case-03 | fail→fail | 56,539 | 42,249 | -25% | 1 | 1 | 0% | 8,252 | 11,231 | +36% | 0 | 0 | — |
case-04 | pass→pass | 34,298 | 30,383 | -11% | 1 | 1 | 0% | 5,444 | 9,616 | +77% | 0 | 0 | — |
case-05 | pass→pass | 24,385 | 28,493 | +17% | 1 | 1 | 0% | 5,581 | 8,918 | +60% | 0 | 0 | — |
case-06 | pass→pass | 27,226 | 27,482 | +1% | 1 | 1 | 0% | 6,158 | 8,071 | +31% | 0 | 0 | — |
case-07 | pass→pass | 17,738 | 19,337 | +9% | 1 | 1 | 0% | 3,801 | 7,178 | +89% | 0 | 0 | — |
case-08 | fail→fail | 17,665 | 13,851 | -22% | 1 | 1 | 0% | 3,083 | 5,254 | +70% | 0 | 0 | — |
case-09 | fail→fail | 21,319 | 23,248 | +9% | 1 | 1 | 0% | 3,755 | 6,381 | +70% | 0 | 0 | — |
case-10 | fail→fail | 17,791 | 19,529 | +10% | 1 | 1 | 0% | 2,996 | 6,312 | +111% | 0 | 0 | — |
case-11 | fail→fail | 22,034 | 15,050 | -32% | 1 | 1 | 0% | 3,483 | 5,753 | +65% | 0 | 0 | — |
case-12 | fail→fail | 18,433 | 18,510 | +0% | 1 | 1 | 0% | 3,346 | 6,616 | +98% | 0 | 0 | — |
case-13 | pass→fail | 20,622 | 15,776 | -23% | 1 | 1 | 0% | 4,119 | 6,049 | +47% | 0 | 0 | — |
case-14 | fail→fail | 16,167 | 14,043 | -13% | 1 | 1 | 0% | 2,718 | 5,637 | +107% | 0 | 0 | — |
case-15 | pass→pass | 16,335 | 19,743 | +21% | 1 | 1 | 0% | 2,536 | 5,712 | +125% | 0 | 0 | — |
case-16 | pass→pass | 15,022 | 13,200 | -12% | 1 | 1 | 0% | 2,419 | 4,959 | +105% | 0 | 0 | — |
case-17 | fail→fail | 16,300 | 21,980 | +35% | 1 | 1 | 0% | 3,199 | 7,887 | +147% | 0 | 0 | — |
case-18 | fail→pass | 18,477 | 6,211 | -66% | 1 | 1 | 0% | 3,277 | 4,136 | +26% | 0 | 0 | — |
case-19 | fail→pass | 20,032 | 5,813 | -71% | 1 | 1 | 0% | 2,325 | 4,182 | +80% | 0 | 0 | — |
case-20 | fail→pass | 16,808 | 5,478 | -67% | 1 | 1 | 0% | 2,595 | 3,775 | +45% | 0 | 0 | — |
case-21 | fail→fail | 20,117 | 15,918 | -21% | 1 | 1 | 0% | 3,766 | 6,131 | +63% | 0 | 0 | — |
case-22 | fail→fail | 19,537 | 22,810 | +17% | 1 | 1 | 0% | 3,653 | 7,433 | +103% | 0 | 0 | — |
case-23 | fail→pass | 15,951 | 19,949 | +25% | 1 | 1 | 0% | 2,706 | 6,354 | +135% | 0 | 0 | — |
case-24 | fail→fail | 12,733 | 3,796 | -70% | 1 | 1 | 0% | 2,007 | 3,667 | +83% | 0 | 0 | — |
case-25 | fail→pass | 12,356 | 8,483 | -31% | 1 | 1 | 0% | 2,042 | 4,550 | +123% | 0 | 0 | — |
case-26 | fail→pass | 17,679 | 10,835 | -39% | 1 | 1 | 0% | 2,723 | 5,063 | +86% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 26 cases were attempted. The headline lift of +19 percentage points is the difference between those two pass rates over the 26 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.