Skip to content

refactor(fjl): #32-type-system-cleanup remove deprecated data/list generics and the Slice type - #134

Open
elycruz wants to merge 3 commits into
mainfrom
32-type-system-cleanup
Open

refactor(fjl): #32-type-system-cleanup remove deprecated data/list generics and the Slice type#134
elycruz wants to merge 3 commits into
mainfrom
32-type-system-cleanup

Conversation

@elycruz

@elycruz elycruz commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

Work unit: 32-type-system-cleanup (epic #32, v2.0 — Type system cleanup).

Removes every @deprecated generic from packages/fjl/src/types/data.ts and
packages/fjl/src/types/list.ts, migrating all call sites to the replacement named
in each doc block; removes the leftover Slice type (which carried no deprecation
marker and no named replacement, so it needed a decision — see below); renames
MapAccumOp's stray positional generic; and records the "keep generic defaults on
method types" convention in packages/fjl/src/types/README.md.

Closes #114
Closes #113
Contributes to #116
Contributes to #32

Three commits, one concern each:

  1. d34edf82 — remove the deprecated data/list generics + rename MapAccumOp's generics
  2. 350eb32e — remove Slice
  3. ae02796f — record the conventions, backfill missing generic defaults

The Slice replacement decision

Decision: Slice is removed with no 1:1 successor. Each call site now names what
it actually needs — NumberIndexable<T> where only length/numeric indexing is
read, string | T[] where slice/concat/at are actually invoked.

Justification:

  1. Slice was never an abstraction over anything. Its own doc block said it
    modelled "the intersection of string, array, and/or (compatible) array-like types"
    and explicitly ruled out typed arrays. That is precisely the native union
    string | T[], hand-rolled as an interface.
  2. It leaked any, which is what v2.0 - Type system cleanup (epic) #32 exists to fix. [index: number]: any and
    at(i): any erased the element type T it was parameterised on — Slice<string>[0]
    was any. string | T[] keeps the element type, and NumberIndexable<T> is already
    the library's designated replacement for Lengthable.
  3. Iterable<T> is not usable at these sites. next: Replace Slice type with Iterable type  #43 replaced Slice with Iterable
    at the iteration API surface, and that remains correct there. But 15 of the 21
    internal call sites call .slice, .concat, .at, or index numerically, none of
    which Iterable provides — a naive swap does not typecheck.
  4. ArrayLike<T> would be a third spelling of a type we already export. It is
    NumberIndexable minus the readonly/string-friendly branch; adding it would grow
    the surface for nothing.

Where the "same container type in, same container type out" property mattered
(sliceCopy, sliceFrom, sliceTo, tails, inits, subsequences, span,
breakOnList, dropWhileEnd, groupBy, insertBy, cycle, last), it is preserved
by keeping a generic constraint — TS extends string | T[] — with an as TS on the
return where TSC cannot prove (string | T[]).slice() narrows back to TS. Those casts
are the honest cost of dropping an interface that faked the same guarantee via this.

Per-type migration table

Removed Replaced with Call sites migrated
ForEachOp Ternary none (already unused)
MapOp Ternary object/mapObj
ReduceOp Quaternary list/foldl1, list/foldr1, list/utils/reduce, reduceRight, reduceUntil, reduceUntilRight
PredForSlice TernaryPred 5 test files
SliceConstructor StringConstructor / ArrayConstructor directly none (already unused)
Lengthable NumberIndexable none (already unused)
Nameable own/native types ({readonly name: string}) fjl/tests/object/index_test, fjl-labs/data/maybe_test
ArrayType ArrayTypeConstructor / array constructor types directly none (already unused)
Slice NumberIndexable<T> or string | T[] (per site) _platform/slice; 17 list/ modules; 3 list/utils/ modules; fjl-inputfilter/src/input.ts; fjl-validator/src/lengthValidator.ts; 25 test files

ArrayType was not on the ticket's table, but it lives in types/data.ts, was
@deprecated with a named replacement, and had zero call sites — removing it is
exactly what #114's title asks for. Flagging it explicitly in case you'd rather keep it.

Both downstream consumers only ever read .length, so both took NumberIndexable<T>:

  • packages/fjl-inputfilter/src/input.ts!(value as Slice).length
  • packages/fjl-validator/src/lengthValidator.tsLenValidatorOptions, $lengthValidatorNoNormalize, lengthValidator

#116 (my half)

MapAccumOp had four named generics plus a stray positional B. Because MapOfB and
SliceOfBs were named in terms of B, renaming B alone would have left dangling
references, so the type adopts the all-named target shape already documented in
types/README.md:

// before
export type MapAccumOp<AccumVal=any, B=any, MapOfB=any, Index=number|string, SliceOfBs extends Slice<B>=Slice<B>>

// after
export type MapAccumOp<AccumT = any, ElementT = any, MappedT = any, IndexT = number | string,
                       ElementsT extends NumberIndexable<ElementT> = ElementT[]>

UnfoldrOp's in-code @todo (list/unfoldr.ts) is not in this PR — it belongs to
the 121-generators work unit. list/unfoldr.ts and list/replicate.ts are untouched.

The types/arity.ts positional-generic exemption (Quinary, Quaternary, …) is
respected — none of their generics were renamed.

#113 (keep generic defaults)

This is a "keep/affirm" ticket, so the work was verification plus recording:

  • Confirmed 42467e2c already landed defaults on ScanlOp and ZipWith3Op; nothing
    there was redone or undone.
  • Every generic that survived migration kept its default, and the new MapAccumOp
    carries a default on all five — including the "generic parameterised by another
    declared generic" case the ticket calls out (ElementsT ... = ElementT[]).
  • Backfilled the defaults that were still missing from method types outside arity.ts:
    DefinePropertyFunc, DefinePropertiesFunc, OrderingFunc, ScanrOp, TuplizeOp.
  • Added a Generic defaults section to packages/fjl/src/types/README.md stating the
    convention and its rationale, so it survives as documentation rather than as a closed
    issue.

#32 module completion

I believe this legitimately completes:

  • list/utils/ — sources, tests, and docs are free of the removed types; OrderingFunc now carries a default.
  • list/ — with one caveat: every list/ module is migrated except unfoldr.ts, whose UnfoldrOp @todo is owned by the 121-generators unit. Tick list/ once that lands.

Not complete, and not claimed:

  • _platform/_platform/slice/ is done, but _platform/object/index.ts (and the ObjectStatics interface behind it) still takes and returns any. That's a separate redesign.
  • errorThrowing/ — untouched by this work unit.

I have not edited issue #32.

Breaking changes

This removes exported types from published packages (fjl, and transitively the
type surface used by fjl-validator / fjl-inputfilter). Anything importing these
names from fjl will fail to compile:

  • Slice
  • ForEachOp, MapOp, ReduceOp, PredForSlice
  • SliceConstructor, Lengthable, Nameable, ArrayType

Signature changes visible to consumers:

  • sliceCopy, sliceFrom, sliceTo, inits, tails, subsequences, concat are now
    generic over TS extends string | any[] instead of taking Slice.
  • group, intercalate, remove, removeBy now take/return string | T[] instead of Slice<T>.
  • groupBy/$groupBy, cycle, last changed their second generic's default from
    Slice<T> to T[].
  • _platform/slice's at/$at are now generic over the element type.
  • fjl-validator's LenValidatorOptions<T> extends ValidatorOptions<NumberIndexable<T>>
    instead of ValidatorOptions<Slice<T>>.

Runtime behaviour is unchanged throughout — this is a types-only change.

Consumers should migrate per the table above; the same guidance is now in
packages/fjl/src/types/README.md under "Removed types".

Testing

Test Suites: 132 passed, 132 total
Tests:       1063 passed, 1063 total

Baseline on origin/main was 133 suites / 1066 tests. The delta is exactly one file:
packages/fjl/tests/list/test-slice-type.ts (3 tests) was removed. That suite existed
solely to assert that the Slice type compiled — its own header reads "General ephemeral
tests for Slice type - Tests just ensure that TSC doesn't throw any errors when using the
Slice type."
With the type gone the file has nothing left to assert. No other test was
deleted, skipped, or weakened.

Type-level verification (this ticket is entirely about types, so a green jest run isn't
sufficient evidence on its own):

npx tsc --noEmit -p packages/fjl/tsconfig.json                   -> clean
npx tsc --noEmit -p packages/fjl/tsconfig.spec.json              -> clean
npx tsc --noEmit -p packages/fjl-validator/tsconfig.spec.json    -> clean*
npx tsc --noEmit -p packages/fjl-inputfilter/tsconfig.spec.json  -> clean*

* modulo the pre-existing packages/fjl/dist/esm/object/setTheory.d.ts TS1110 errors,
which reproduce identically on origin/main and are not from this change.

Build: rollup --config rollup.config.mjs exits 0, no warnings, no new .d.ts warnings.

Lint (not a gate): origin/main = 62 problems / 0 errors; this branch = 59 problems /
0 errors. No new errors, three fewer warnings.

No git hooks were bypassed — commit-msg, pre-commit (lint-staged), and pre-push
all ran and passed on every commit.

🤖 Generated with Claude Code

elycruz and others added 3 commits July 31, 2026 10:27
…nerics

Removes the `@deprecated` generics from `types/data.ts` and `types/list.ts`,
migrating every call site to the replacement named in each doc block:

- `ForEachOp` -> `Ternary` (no call sites)
- `MapOp` -> `Ternary` (`object/mapObj`)
- `ReduceOp` -> `Quaternary` (`list/foldl1`, `list/foldr1`,
  `list/utils/reduce{,Right,Until,UntilRight}`)
- `PredForSlice` -> `TernaryPred` (tests only)
- `SliceConstructor` -> direct type constructors (no call sites)
- `Lengthable` -> `NumberIndexable` (no call sites)
- `Nameable` -> own/native types (`{readonly name: string}`) in tests
- `ArrayType` -> constructor array types directly (no call sites)

Also renames `MapAccumOp`'s generics to the all-named convention recorded in
`types/README.md` (`AccumT`, `ElementT`, `MappedT`, `IndexT`, `ElementsT`),
removing the stray positional `B`, and keeps a default on every one of them.

Contributes to #116.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`Slice` was a hand-rolled interface standing in for exactly one thing: the
built-in `string | T[]` union (its own doc block said so, and explicitly ruled
out typed arrays). It also leaked `any` through `[index: number]: any` and
`at(i): any`, erasing the element type it was parameterised on.

It is removed with no 1:1 successor; each call site now names what it actually
needs:

- `NumberIndexable<T>` where only `length`/numeric indexing is read - both
  downstream consumers (`fjl-inputfilter/input`, `fjl-validator/lengthValidator`)
  and the `NumberIndexable`-based list utils' tests.
- `string | T[]` where `slice`/`concat`/`at` are actually invoked - kept as a
  generic constraint (`TS extends string | T[]`) so "same container in, same
  container out" is preserved.

`Iterable<T>` (the #43 API-level replacement) is not usable at these sites: the
majority of them call `slice`, `concat`, `at`, or index numerically.

Removes `tests/list/test-slice-type.ts`, which existed solely to assert that the
`Slice` type compiled.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the "keep generic defaults on method types" convention to
`types/README.md` and backfills the defaults that were missing from method
types outside `arity.ts`: `DefinePropertyFunc`, `DefinePropertiesFunc`,
`OrderingFunc`, `ScanrOp`, and `TuplizeOp`.

Replaces the README's "Deprecations" section with a "Removed types" table
covering everything dropped in this change, including the `Slice` decision and
when to reach for `NumberIndexable<T>` vs. `string | T[]`.

Closes #113.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove the use of generics defined in 'data/list'. Keep generic defaults for method types

1 participant