Collections
find, some, every, groupBy, and filterMap — variant-aware collection operations over discriminated unions in a single pass.
Five purpose-built collection operations for discriminated unions: search, test, group, and filter-transform — all narrowed by variant, all in a single pass.
import { find, some, every, groupBy, filterMap } from "dismatch";Shared semantics
These rules apply to every operation on this page:
- Optional discriminant. Each function accepts an optional trailing
discriminant key (default
"type"), e.g.find(items, "circle", "kind"). - Non-union items are silently skipped. Items that don't structurally look like a discriminated union (missing or non-string discriminant) are ignored — no throws, no errors.
- Bound forms. Every op is also available on
createUnionfactories (Shape.find(...),Shape.some(...), …) and oncreatePipeHandlersbindings, pre-bound to the union type and discriminant.
find
Returns the first item matching the given variant(s), narrowed to that type,
or undefined. Accepts a single variant or an array of variants.
import { find } from "dismatch";
type Shape =
| { type: "circle"; radius: number }
| { type: "rectangle"; width: number; height: number }
| { type: "triangle"; base: number; height: number };
const shapes: Shape[] = [
{ type: "rectangle", width: 4, height: 6 },
{ type: "circle", radius: 5 },
{ type: "triangle", base: 3, height: 8 },
];
const firstCircle = find(shapes, "circle");
// ^? { type: "circle"; radius: number } | undefined
const firstRound = find(shapes, ["circle", "triangle"]);
// ^? Circle | Triangle | undefinedfind is to Array.prototype.find what partition is to a two-pass
filter — the variant-aware version narrows the return type for free.
some
Returns true if any item matches the given variant(s).
import { some } from "dismatch";
if (some(shapes, "circle")) {
// there is at least one circle in the collection
}
const hasRound = some(shapes, ["circle", "triangle"]);every
Returns true if every item matches the given variant(s).
import { every } from "dismatch";
const allCircles = every(shapes, "circle");
const allRound = every(shapes, ["circle", "triangle"]);Non-union items are skipped. A collection of only non-union items is
treated as empty and returns true — vacuous truth, consistent with
Array.prototype.every.
groupBy
Groups a collection by variant in a single pass. Each present group is narrowed to its specific variant type.
import { groupBy } from "dismatch";
const groups = groupBy(shapes);
// ^? {
// circle?: Circle[];
// rectangle?: Rectangle[];
// triangle?: Triangle[];
// }
groups.circle?.forEach((c) => console.log(c.radius));Result keys are optional: absent variant groups are absent at runtime,
and the type now reflects that. Access groups with groups.circle?.…,
groups.circle ?? [], or an existence check.
filterMap
Filters and transforms in one pass. Each handler returns either a value to
keep or undefined to skip. Unhandled variants are silently skipped.
import { filterMap } from "dismatch";
const areas = filterMap(shapes, {
circle: ({ radius }) => Math.PI * radius ** 2,
rectangle: ({ width, height }) => width * height,
// triangle: skipped — no handler
});
// ^? number[]null is a valid kept value — only undefined skips. This lets you
produce nullable results from a filterMap without losing them to the
filter.
Bound forms
The same operations are pre-bound on createUnion factories and
createPipeHandlers bindings — no discriminant argument needed.
import { createUnion } from "dismatch";
const Shape = createUnion({
circle: (radius: number) => ({ radius }),
rectangle: (w: number, h: number) => ({ w, h }),
});
Shape.find(items, "circle");
Shape.some(items, ["circle", "rectangle"]);
Shape.every(items, "circle");
Shape.groupBy(items);
Shape.filterMap(items, {
circle: ({ radius }) => radius * 2,
});The five names find, some, every, groupBy, filterMap are reserved
factory keys — see createUnion.
Reach for what next
- Need exhaustive aggregation instead of optional grouping? See
fold. - Need a count or a two-way partition? See
countandpartition. - Need an exhaustive transform that keeps the discriminant? See
mapAll. - Composing into a pipeline? Every op is available on
createPipeHandlers.