♻️ Share the Matchable universe between both matchers
This commit is contained in:
1 parent
d88a4eeb45
commit
6228b4922d
4 files changed
+24
-17
No files matched your search
@@ -97,9 +97,9 @@ Each factory is two overloads whose order is load-bearing:
|
|||||||
|
|
||||||
#### Decision (2026-09)
|
#### Decision (2026-09)
|
||||||
|
|
||||||
`src/matcher-shared.ts` holds the six universe-agnostic pieces both matchers
|
`src/matcher-shared.ts` holds the seven universe-agnostic pieces both matchers
|
||||||
use: `UnaryFn`, `PatternReturns`, `RedundantFallback`, `HandlerMap`, and the
|
use: `UnaryFn`, `PatternReturns`, `RedundantFallback`, `HandlerMap`, the shared
|
||||||
`PatternKey` / `PatternParam` key projection.
|
`Matchable` universe, and the `PatternKey` / `PatternParam` key projection.
|
||||||
|
|
||||||
#### Why
|
#### Why
|
||||||
|
|
||||||
@@ -107,6 +107,10 @@ use: `UnaryFn`, `PatternReturns`, `RedundantFallback`, `HandlerMap`, and the
|
|||||||
keeps the two matchers' message from drifting; the other pieces appear
|
keeps the two matchers' message from drifting; the other pieces appear
|
||||||
verbatim in both public signatures or are the same projection over each
|
verbatim in both public signatures or are the same projection over each
|
||||||
matcher's universe.
|
matcher's universe.
|
||||||
|
- **`Matchable` is one definition, not two.** The primitive-union matcher's
|
||||||
|
universe and the tagged-union matcher's allowed `Tag` values are the same set,
|
||||||
|
so aliasing them keeps the two matchers from drifting apart on what they
|
||||||
|
accept (`symbol`/`bigint` rejected once).
|
||||||
|
|
||||||
#### Rejected
|
#### Rejected
|
||||||
|
|
||||||
@@ -114,8 +118,8 @@ use: `UnaryFn`, `PatternReturns`, `RedundantFallback`, `HandlerMap`, and the
|
|||||||
`Fallback` and `MustBePartial`.** Each is built from its own universe
|
`Fallback` and `MustBePartial`.** Each is built from its own universe
|
||||||
(`Tags`/`MapTaggedUnion` vs the primitive values); abstracting over the
|
(`Tags`/`MapTaggedUnion` vs the primitive values); abstracting over the
|
||||||
F-bounded `Handled` constraint that makes the remainder work risks the
|
F-bounded `Handled` constraint that makes the remainder work risks the
|
||||||
contextual typing it exists to preserve. The `PatternKey` / `PatternParam`
|
contextual typing it exists to preserve. `Matchable` and the `PatternKey` /
|
||||||
projection is the one piece both universes genuinely share.
|
`PatternParam` projection are the pieces both universes genuinely share.
|
||||||
|
|
||||||
## Tagged-union matcher
|
## Tagged-union matcher
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import type { ValueOf } from "type-fest";
|
|||||||
// A handler: one universe member in, one return value out.
|
// A handler: one universe member in, one return value out.
|
||||||
export type UnaryFn<T, R> = (shape: T) => R;
|
export type UnaryFn<T, R> = (shape: T) => R;
|
||||||
|
|
||||||
|
// The primitive universe a matcher can discriminate. The primitive-union matcher
|
||||||
|
// uses it directly; the tagged-union matcher uses it as the set of allowed
|
||||||
|
// discriminant (`Tag`) values. `boolean` is admitted as the pair `true | false`;
|
||||||
|
// see README § Caveats for the unsupported members.
|
||||||
|
export type Matchable = string | number | boolean | null | undefined;
|
||||||
|
|
||||||
// `boolean`, `null` and `undefined` cannot be property keys, so a mapped type
|
// `boolean`, `null` and `undefined` cannot be property keys, so a mapped type
|
||||||
// over a universe that includes one keys each such member by its
|
// over a universe that includes one keys each such member by its
|
||||||
// stringification. `PatternParam` inverts that projection, so a handler callback
|
// stringification. `PatternParam` inverts that projection, so a handler callback
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { Exact } from "type-fest";
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
HandlerMap,
|
HandlerMap,
|
||||||
|
Matchable,
|
||||||
PatternKey,
|
PatternKey,
|
||||||
PatternParam,
|
PatternParam,
|
||||||
PatternReturns,
|
PatternReturns,
|
||||||
@@ -9,10 +10,6 @@ import type {
|
|||||||
UnaryFn,
|
UnaryFn,
|
||||||
} from "./matcher-shared.ts";
|
} from "./matcher-shared.ts";
|
||||||
|
|
||||||
// The primitive universe a matcher can discriminate. `boolean` is admitted as
|
|
||||||
// the pair `true | false`; see README § Caveats for the unsupported members.
|
|
||||||
type Matchable = string | number | boolean | null | undefined;
|
|
||||||
|
|
||||||
type Handlers<T extends Matchable, R> = {
|
type Handlers<T extends Matchable, R> = {
|
||||||
[K in PatternKey<T>]: UnaryFn<PatternParam<K>, R>;
|
[K in PatternKey<T>]: UnaryFn<PatternParam<K>, R>;
|
||||||
};
|
};
|
||||||
|
|||||||
+8
-8
@@ -2,6 +2,7 @@ import type { Exact, UnknownRecord } from "type-fest";
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
HandlerMap,
|
HandlerMap,
|
||||||
|
Matchable,
|
||||||
PatternKey,
|
PatternKey,
|
||||||
PatternParam,
|
PatternParam,
|
||||||
PatternReturns,
|
PatternReturns,
|
||||||
@@ -9,22 +10,21 @@ import type {
|
|||||||
UnaryFn,
|
UnaryFn,
|
||||||
} from "./matcher-shared.ts";
|
} from "./matcher-shared.ts";
|
||||||
|
|
||||||
// A tagged union is discriminated by one property whose values are the tags.
|
// A tagged union is discriminated by one property whose values are the tags (a
|
||||||
// `string` and `number` tags key a handler map directly; `boolean`, `null` and
|
// `Matchable`). `string` and `number` tags key a handler map directly;
|
||||||
// `undefined` are admitted too but are not property keys, so they go through the
|
// `boolean`, `null` and `undefined` are admitted too but are not property keys,
|
||||||
// `PatternKey` projection. `symbol` has no literal syntax to write a handler
|
// so they go through the `PatternKey` projection. `symbol` has no literal syntax
|
||||||
// under, and `bigint` is not a property key.
|
// to write a handler under, and `bigint` is not a property key.
|
||||||
type Tag = string | number | boolean | null | undefined;
|
|
||||||
|
|
||||||
// The discriminant values of `T` under `K`. `Extract` keeps the finite literal
|
// The discriminant values of `T` under `K`. `Extract` keeps the finite literal
|
||||||
// tags and leaves a widened `string`/`number` as itself, so an open universe
|
// tags and leaves a widened `string`/`number` as itself, so an open universe
|
||||||
// keeps an open fallback.
|
// keeps an open fallback.
|
||||||
type Tags<T extends object, K extends keyof T> = Extract<T[K], Tag>;
|
type Tags<T extends object, K extends keyof T> = Extract<T[K], Matchable>;
|
||||||
|
|
||||||
// The keys of `T` that can act as a discriminant. `getTaggedUnionMatcher<T>()`
|
// The keys of `T` that can act as a discriminant. `getTaggedUnionMatcher<T>()`
|
||||||
// accepts only these, so the factory rejects a key whose values are not tags.
|
// accepts only these, so the factory rejects a key whose values are not tags.
|
||||||
type Discriminated<T extends object> = {
|
type Discriminated<T extends object> = {
|
||||||
[K in keyof T]: T[K] extends Tag ? K : never;
|
[K in keyof T]: T[K] extends Matchable ? K : never;
|
||||||
}[keyof T];
|
}[keyof T];
|
||||||
|
|
||||||
// The member(s) of `T` tagged `V`. `Extract` distributes over the union, so a
|
// The member(s) of `T` tagged `V`. `Extract` distributes over the union, so a
|
||||||
|
|||||||
Reference in new issue
Block a user