Skip to content

Proposal: Add range and countBy to @std/collections, and isAlpha/isAlphanumeric/isNumeric to @std/text #7282

Description

@hey-amanthakur

Is your feature request related to a problem? Please describe.

New APIs need a proposal issue and maintainer sign-off before implementation, as requested in the review of PR #7229 (@bartlomieju, @tomas-zijdemans). This is that proposal.

Five APIs are missing from the std library and have no std equivalent today:

  • @std/collections lacks a range() function. It's absent from std and hard to write correctly — a naive i += step accumulation compounds float error (range(0, 1, { step: 0.1 }) returns 11 elements ending in 0.9999999999999999).
  • @std/collections lacks a countBy() helper; the natural workaround is composing Object.groupBy + .length.
  • @std/text lacks string predicates isAlpha, isAlphanumeric, and isNumeric. The review flagged that the current ASCII-only drafts are a trap under Unicode-sounding names (isAlpha("café") → false, isNumeric("١٢٣") → false), especially since the package's own internals already use \p{L} (see text/_util.ts).

Describe the solution you'd like

Both target packages are stable (@std/collections 1.3.0, @std/text 1.0.19), so all APIs land as unstable_.ts files with an @experimental JSDoc tag, not re-exported from mod.ts, following the collections/unstable_cycle.ts / text/unstable_slugify.ts exemplars.

  1. @std/collections/unstable_range — range(startOrEnd, end?, options?): number[]
    Numbers from start (default 0) up to but not including end, with optional { step }, including a one-arg overload (range(5) → [0, 1, 2, 3, 4]). Elements are computed from the index (start + i * stepValue, count = Math.max(0, Math.ceil((stop - start) / stepValue))) to avoid float accumulation, and stepValue is validated as finite.
  • Open question: should range(5, 0) auto-descend (default step to -1), or require an explicit { step: -1 }? The auto-descend behavior is currently undocumented and untested.
  1. @std/collections/unstable_countBy — countBy<T, K>(array, keyFn): Record<K, number>
    Counts occurrences of each key from keyFn into a Record<K, number>, accumulated in a Map and returned via Object.fromEntries (this also fixes a review-found bug where a "proto" key silently lost its count by hitting the object prototype setter).
  • Open question: is this worth adding over composing Object.groupBy + .length? My argument is that it returns counts directly in a single well-scoped call, with stable semantics and no Map-ordering dependency.
  1. @std/text predicates — isAlpha, isAlphanumeric, isNumeric
  • Open decision: ASCII-only (renamed to say so, e.g. isAsciiAlpha) vs Unicode-aware (/^\p{L}+$/u, /^\p{Nd}+$/u). My lean is Unicode-aware to match the existing \p{L} precedent, but this semantics can't change after 1.0, so a maintainer call is needed.
    On sign-off, implementation is split into two PRs (one per package) so each releases as its own patch, per the review's suggestion.

Describe alternatives you've considered

  • range: hand-rolled loops or Array.from({ length }, (_, i) => ...) — both re-implement the off-by-one and float-accuracy details this API centralizes.
  • countBy: Object.groupBy(array, keyFn) then mapping groups to lengths — viable, which is exactly why the review asked for a case for countBy itself.
  • Text predicates: keep ASCII-only with explicit names/documentation instead of Unicode semantics — listed as an option in the open decision.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions