Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PrincipledArray #7

Closed
danielnixon opened this issue Sep 25, 2019 · 8 comments
Closed

PrincipledArray #7

danielnixon opened this issue Sep 25, 2019 · 8 comments

Comments

@danielnixon
Copy link
Collaborator

danielnixon commented Sep 25, 2019

It's too easy to "escape" into mutable arrays from a ReadonlyArray (e.g. map, filter return mutable arrays). Let's provide a principled, immutable array type that doesn't make this so easy.

@danielnixon
Copy link
Collaborator Author

@danielnixon
Copy link
Collaborator Author

Such a type would look something like this:

// https://github.com/agiledigital/readonly-types/issues/518
export type VeryImmutableArray<T> = ImmutableShallow<
  OmitStrict<ImmutableArray<T>, "map">
> & {
  readonly map: <U>(
    callbackfn: (value: T, index: number, array: ImmutableArray<T>) => U,
    thisArg?: any
  ) => ImmutableArray<U>;
};

Usage:

const foo: VeryImmutableArray<string> = [""] as const;
const bar = foo.map((a) => a.toUpperCase());


// Doesn't compile 🚀!
bar[0] = "";

@danielnixon
Copy link
Collaborator Author

A complication here is that the methods available on the ReadonlyArray type depend on which lib(s) are configured. For example, flatMap is only available if you've got a modern lib like lib.es2019.array.d.ts.

I wonder if, at the type level, we can dynamically detect if those methods are present and only provide the principled versions if so.

Alternatively we do the chauvinist thing and assume they're available in every runtime, regardless of which libs are configured.

@danielnixon
Copy link
Collaborator Author

Although, we've already committed an amount of that chauvinism by including this:

"lib": ["DOM", "DOM.Iterable"]

in tsconfig 🤔

@danielnixon
Copy link
Collaborator Author

More things to consider changing if we're going to lean into "principled":

  1. predicates (e.g. filter predicate) must return boolean, not unknown.
  2. remove forEach entirely
  3. remove thisArg from the callback signature?

@danielnixon danielnixon changed the title VeryReadonlyArray PrincipledArray Feb 4, 2023
@danielnixon
Copy link
Collaborator Author

I have published an early version of this type in version 4.3.0 of this lib for testing. Usage examples are in the unit tests.

It:

  • provides principled versions of: "map" | "filter" | "flatMap" | "flat" | "concat" | "slice"
  • removes forEach entirely
  • requires a true boolean predicate to filter
  • does not yet (but eventually will) provide principled versions of: every, find, findIndex, reduce, reduceRight, some

I may remove thisArg eventually.

Note that these changes render the PrincipledArray type incompatible with ImmutableArray, ReadonlyArray and Array, all of which are type compatible (because typescript doesn't do much/any enforcement of readonly modifiers).

That means it'll have "worse" ergonomics when interacting with code (functions, libraries, ...) that expects built in readonly or mutable arrays. This is known / by design, so don't complain about it. If you want something that is "as readonly as possible" while remaining type compatible with those built-in types, ImmutableArray is as far as you can go.

@danielnixon
Copy link
Collaborator Author

Another principled decision:

Don't expose the versions of reduce and reduceRight that don't require an initialValue. These throw if the array is empty. If you want this, use a type that is verifiably non-empty at compile time.

This is equivalent to the situation with reduce versus reduceOption (or fold) in Scala: https://www.wartremover.org/doc/warts.html#iterableops

@danielnixon
Copy link
Collaborator Author

Released in 4.4.0:

  • provides principled versions of: "map" | "filter" | "flatMap" | "flat" | "concat" | "slice" | "every" | "find" | "findIndex" | "reduce" | "reduceRight" | "some"
  • removes forEach entirely
  • requires a true boolean predicate to filter and other methods that take predicates
  • removes the versions of reduce and reduceRight that throw at runtime if the array is empty (i.e. those that don't require caller to specify an initial value)

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

No branches or pull requests

1 participant