-
Notifications
You must be signed in to change notification settings - Fork 605
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
std / collections #978
std / collections #978
Conversation
@LionC asked me to move my comment here from the discussion: On the topic of syntactic sugar functions: Any time syntax sugar aligns with your needs, it feels very welcome indeed. However, an individual's vocabulary/grammar as well as their programming experience plays a huge role in this. Unrelated to those subjective aspects, both maintainability and performance are also crucial considerations. From a performance perspective: while calling a function is cheap, it's not free. There's definitely something to be said about providing highly performant functions, but ones that merely rename a concise combination of built-ins would be better left to the user to implement IMO. On the other hand, it can be very useful to provide a consistent grammar. As an example, using
@wperron said:
There is a precedent of removal of std functions which have only provided nominal syntax sugar. Here is one example: denoland/deno#7059. Searching the PR/commit history will surface many others. |
@LionC Sorry for the delayed response. I took a look at each API and I'm currently in favor of adding 43 of them, and against adding 17 of them. Among proposed 60 APIs, I found the following 43 APIs useful: // Array predicates (3)
declare function includesAll<T, O extends T>(collection: Array<T>, needles: Array<O>): boolean
declare function includesAny<T, O extends T>(collection: Array<T>, needles: Array<O>): boolean
declare function includesNone<T, O extends T>(collection: Array<T>, needles: Array<O>): boolean
// Array transformations to Array (12)
declare function mapNotNullish<T, O>(collection: Array<T>, transformer: Selector<T, O>): Array<NonNullable<O>>
declare function takeFirstWhile<T>(collection: Array<T>, predicate: Predicate<T>): Array<T>
declare function takeLastWhile<T>(collection: Array<T>, predicate: Predicate<T>): Array<T>
declare function dropFirstWhile<T>(collection: Array<T>, predicate: Predicate<T>): Array<T>
declare function dropLastWhile<T>(collection: Array<T>, predicate: Predicate<T>): Array<T>
declare function distinct<T>(collection: Array<T>): Array<T>
declare function distinctBy<T>(collection: Array<T>, selector: Selector<T>): Array<T>
declare function sortBy<T>(collection: Array<T>, selector: Selector<T, number | string>): Array<T>
declare function intersect<T>(collection: Array<T>, withCollection: Array<T>): Array<T>
declare function union<T>(collection: Array<T>, withCollection: Array<T>): Array<T>
declare function without<T>(collection: Array<T>, toRemove: T): Array<T>
declare function withoutAll<T>(collection: Array<T>, toRemove: Array<T>): Array<T>
// Array transformations to value (10)
declare function firstNotNullishOf<T, O>(collection: Array<T>, selector: Selector<T, O>): NonNullable<O> | undefined
declare function findLast<T>(collection: Array<T>, predicate: Predicate<T>): T | undefined
// overlaps with https://github.com/tc39/proposal-array-find-from-last though
declare function sumOf<T>(collection: Array<T>, selector: Selector<T, number>): number
declare function maxBy<T>(collection: Array<T>, selector: Selector<T, number | string>): T | undefined
declare function minBy<T>(collection: Array<T>, selector: Selector<T, number | string>): T | undefined
declare function maxWith<T>(collection: Array<T>, comparator: (a: T, b: T) => number): T | undefined
declare function minWith<T>(collection: Array<T>, comparator: (a: T, b: T) => number): T | undefined
declare function maxOf<T, O extends number | string>(collection: Array<T>, selector: Selector<T, O>): O | undefined
declare function minOf<T, O extends number | string>(collection: Array<T>, selector: Selector<T, O>): O | undefined
declare function average(collection: Array<number>): number | undefined
// Array transformations to other formats (8)
declare function zip<T, U>(collection: Array<T>, withCollection: Array<U>): Array<[ T, U ]>
declare function unzip<T, U>(pairs: Array<[ T, U ]>): [ Array<T>, Array<U> ]
declare function associateBy<T>(collection: Array<T>, selector: Selector<T, string>): Record<string, T>
declare function groupBy<T>(collection: Array<T>, selector: Selector<T, string>): Grouping<T>
declare function partition<T>(collection: Array<T>, predicate: Predicate<T>): [ Array<T>, Array<T> ]
declare function chunked<T>(collection: Array<T>, size: number): Array<Array<T>>
declare function permutations<T>(collection: Array<T>, size?: number): Array<Array<T>>
declare function windowed<T>(collection: Array<T>, size: number, config?: { step?: number, partial?: boolean }): Array<Array<T>>
// Record Predicates (1)
declare function includesValue<T>(collection: Record<string, T>, value: T): boolean
// Record Transformations to Records (7)
declare function filterEntries<T>(collection: Record<string, T>, predicate: Predicate<[string, T]>): Record<string, T>
declare function filterKeys<T>(collection: Record<string, T>, predicate: Predicate<string>): Record<string, T>
declare function filterValues<T>(collection: Record<string, T>, predicate: Predicate<T>): Record<string, T>
declare function filterValuesNotNullish<T>(collection: Record<string, T>): Record<string, NonNullable<T>>
declare function mapEntries<T, O>(collection: Record<string, T>, transformer: Selector<[string, T], [string, O]>): Record<string, O>
declare function mapKeys<T>(collection: Record<string, T>, transformer: Selector<string, string>): Record<string, T>
declare function mapValues<T, O>(collection: Record<string, T>, transformer: Selector<T, O>): Record<string, O>
// Groups (2)
declare function reduceGroups<T, A>(collection: Grouping<T>, reducer: (accumulator: A, current: T) => A, initialValue: A): Record<string, A>
declare function aggregateGroups<T, A>(collection: Grouping<T>, aggregator: (accumulator: A, current: T, key: string, first: boolean) => A, initalValue: A): Record<string, A> Especially On the other hand, the following 16 APIs look too trivial to include in the standard modules because these can be implemented too easily with JS builtin APIs and also because there seem no performance gains by providing these. // Array predicates (3)
declare function none<T>(collection: Array<T>, predicate: Predicate<T>): boolean
// seems better to write `arr.every((x) => !predicate)`
declare function isEmpty(collection: Array<unknown>): boolean
// seems better to write `arr.length === 0`
declare function isNotEmpty(collection: Array<unknown>): boolean
// seems better to write `arr.length > 0`
// Array transformations to Array (7)
declare function takeFirst<T>(collection: Array<T>, n: number): Array<T>
// seems better to write `arr.slice(0, n)`
declare function takeLast<T>(collection: Array<T>, n: number): Array<T>
// seems better to write `arr.slice(arr.length - n)`
declare function dropFirst<T>(collection: Array<T>, n: number): Array<T>
// seems better to write `arr.slice(n)`
declare function dropLast<T>(collection: Array<T>, n: number): Array<T>
// seems better to write `arr.slice(0, arr.length - n)`
declare function filterNot<T>(collection: Array<T>, predicate: Predicate<T>): Array<T>
// seems better to write `arr.filter((x) => !predicate(x))`
declare function filterNotNullish<T>(collection: Array<T>): Array<NonNullable<T>>
// seems better to write `arr.filter((x) => x)`
declare function runningReduce<T, A>(collection: Array<T>, reducer: (accumulator: A, current: T) => A, initialValue: A): Array<A>
// seems better to write `arr.reduce(reducer, initalValue)`
// Array transformations to value (4)
declare function lastIndex(collection: Array<unknown>): number
// seems better to write `arr.length - 1`
declare function first<T>(collection: Array<T>): T | undefined
// seems better to write `arr[0]`
declare function last<T>(collection: Array<T>): T | undefined
// seems better to write `arr[arr.length - 1]` or `arr.at(-1)`
declare function single<T>(collection: Array<T>, predicate: Predicate<T>): T | undefined
// seems better to write `arr.find(predicate)`
// Record Predicates (1)
declare function includesKey<T>(collection: Record<string, T>, value: string): boolean
// seems better to write `record.hasOwnProperty(value)`
// Groups (1)
declare function countGroups(collection: Grouping<unknown>): Record<string, number>
// seems redundant if we have mapValues (`mapValues(group, (x) => x.length)`) And lastly, I'm against including |
@kt3k I would also exclude the following: // includesAll(collection, needles)
// => needles.every((v) => collection.includes(v))
declare function includesAll<T, O extends T>(collection: Array<T>, needles: Array<O>): boolean
// includesAny(collection, needles)
// => needles.some((v) => collection.includes(v))
declare function includesAny<T, O extends T>(collection: Array<T>, needles: Array<O>): boolean
// This is a negation of `includesAny()`...
declare function includesNone<T, O extends T>(collection: Array<T>, needles: Array<O>): boolean
// mapNotNullish(collection, transformer)
// => collection.filter((v) => v != null).map(transformer)
declare function mapNotNullish<T, O>(collection: Array<T>, transformer: Selector<T, O>): Array<NonNullable<O>>
// without(collection, toRemove)
// => collection.filter((v) => v == toRemove)
declare function without<T>(collection: Array<T>, toRemove: T): Array<T>
// withoutAll(collection, toRemove)
// => collection.filter((v) => toRemove.includes(v))
declare function withoutAll<T>(collection: Array<T>, toRemove: Array<T>): Array<T> If these one-liners are deemed too long, we're essentially saying that higher-order In addition we should consider that functions like |
The example in the commit doesn't make the implementation completely clear here, but your suggestion is different than my interpretation. Based on the return type using the utility
Thanks for mentioning this stage 3 proposal method: |
@nayeemrmn Ok. I agree with your suggestion of exclusion of For
I partly agree with this. What do you think if these APIs return
Or maybe better to accept sets as parameters as well?
BTW, Nayeem, do you agree with addition of the other 37 APIs? @jsejcksn I agree with you.
Yup, we should definitely keep the higher stage proposals in mind to avoid the duplication in the future. |
@kt3k I think we can (and should) be more conservative than that. I agree with @nayeemrmn but I think we can remove even more from this proposal. I made a quick gist showing quick and easy alternatives to some of the proposed functions. I'd keep the following functions: // Array transformations to Array (12)
declare function distinct<T>(collection: Array<T>): Array<T>
declare function distinctBy<T>(collection: Array<T>, selector: Selector<T>): Array<T>
declare function intersect<T>(collection: Array<T>, withCollection: Array<T>): Array<T>
declare function union<T>(collection: Array<T>, withCollection: Array<T>): Array<T>
// Array transformations to value (10)
declare function findLast<T>(collection: Array<T>, predicate: Predicate<T>): T | undefined
// overlaps with https://github.com/tc39/proposal-array-find-from-last though
// Array transformations to other formats (8)
declare function zip<T, U>(collection: Array<T>, withCollection: Array<U>): Array<[ T, U ]>
declare function unzip<T, U>(pairs: Array<[ T, U ]>): [ Array<T>, Array<U> ]
declare function groupBy<T>(collection: Array<T>, selector: Selector<T, string>): Grouping<T>
declare function partition<T>(collection: Array<T>, predicate: Predicate<T>): [ Array<T>, Array<T> ]
declare function chunked<T>(collection: Array<T>, size: number): Array<Array<T>>
declare function permutations<T>(collection: Array<T>, size?: number): Array<Array<T>>
// Record Transformations to Records (7)
declare function filterEntries<T>(collection: Record<string, T>, predicate: Predicate<[string, T]>): Record<string, T>
declare function filterKeys<T>(collection: Record<string, T>, predicate: Predicate<string>): Record<string, T>
declare function filterValues<T>(collection: Record<string, T>, predicate: Predicate<T>): Record<string, T>
declare function mapEntries<T, O>(collection: Record<string, T>, transformer: Selector<[string, T], [string, O]>): Record<string, O>
declare function mapKeys<T>(collection: Record<string, T>, transformer: Selector<string, string>): Record<string, T>
declare function mapValues<T, O>(collection: Record<string, T>, transformer: Selector<T, O>): Record<string, O> I think the first 3 groups clearly add something to the standard library by implementing functions that are not-so-trivial yet are pretty common. They also don't have anything close to them in the ECMAScript spec at the moment, and I believe nothing in the proposals either (with the exception of The last group of functions ( There's a few functions I'm on the fence about as well:
|
Ok a lot of new comments - thanks everyone for the tremendous amount of input! I am really happy to see that there is interest in this and I will try and add my personal reasoning and mindset for all of the mentioned points here. ClarificationsSome functions seem to be misunderstood (the examples provided in the spec might be unclear, please feel free to point that out!):
|
Because this proposal has so many items and so much information already, I suggest we'd split this into 2 parts. First part includes less controversial 17 APIs: I'll create a draft PR for the first part. |
Landed in #993 Please open new PRs for modifications |
This is the proposal to add an
std/collections
module, which has been discussed on Discord and in this Github Discussion. It contains a spec, horrible jsdoc descriptions and an okay-ish example for every function.The module and it's terminology are heavily inspired by the
kotlin
standardcollections
library and the respective (very similiar) non-mutatinglodash
parts, which are both battle tested and see a lot of use.There are still a lot of TODOS:
I moved the discussion into a draft PR as proposed in the linked Discussion by @kt3k , because Discussions do not seem to be set up well for large files and continues changes on the spec / the implementation code.
I am looking to finalize the list of functions soon and am asking for input on that, given the recently added examples that have been requested.