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

feat(partition): stricter types, add test #89

Merged
merged 4 commits into from
Feb 19, 2024

Conversation

GerkinDev
Copy link
Contributor

No description provided.

<L extends T = T>(list: readonly L[]): [(L & U)[], Exclude<L, U>[]];
(list: readonly T[]): [(T & U)[], Exclude<T, U>[]];
};
export function partition<T>(fn: (a: T) => boolean): <L extends T = T>(list: readonly L[]) => [L[], L[]];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fn: (a: T) => a is U support is a great addition.

Thank you for removing the string specific overload as well. That was copy/pasted from the original definition over in @types/ramda. I'm not sure why it was needed as the generic overload covers that case as well

Couple things:

The order of the overloads is important, you need to have partition(fn)(list) defined before partition(fn, list). When passing partition to another function, eg flip(partition), typescript asumes the last overload as the most general and uses that

export function partition<T, U extends T>(fn: (a: T) => a is U): {
  <L extends T = T>(list: readonly L[]): [(L & U)[], Exclude<L, U>[]];
  (list: readonly T[]): [(T & U)[], Exclude<T, U>[]];
};

I don't understand the 2 overloads in the middle

  • <L extends T = T>(list: readonly L[]): [(L & U)[], Exclude<L, U>[]];
    • What is the generic L accomplishing here?
  • (list: readonly T[]): [(T & U)[], Exclude<T, U>[]];
    • The intersection T & U seems extranious. U is already defied to extend T, and therefore is a subset of T. So by definition T & U === T.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • the first overload is required for this test:
    expectType<[true[], false[]]>(partition((v): v is true => !!v)([true, false]));
    I also added another test with list type resolved at call-time here:
    const isBool = (v: any): v is boolean => typeof v === 'boolean';
    const extractBool = partition(isBool);
    expectType<[boolean[], number[]]>(extractBool([1, true]));
  • I removed the useless intersection

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, the <L extends T= T> generic makes sense to me now, here is an example that uses an isNumber function: https://tsplay.dev/mpz56w

We actually don't need both of these then:

  <L extends T = T>(list: readonly L[]): [(L & U)[], Exclude<L, U>[]];
  (list: readonly T[]): [(T & U)[], Exclude<T, U>[]];

Just have it be

  <L extends T = T>(list: readonly L[]): [(L & U)[], Exclude<L, U>[]];

I'm glad to know about this trick, there should be many other places where we can apply it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out that's how filter works, so at least we're consistent now!

Copy link
Contributor Author

@GerkinDev GerkinDev Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, types of rxjs, which have a great implementation of a pipe pattern, are almost all functions retuning generic functions just like this

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For pipe, yes, but I was thinking about operators

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which operators?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry one last thing, the overloads, both (fn)(arr)s need to be before (fn, arr)
so this:

partition(fn: val is T)(arr)
partition(fn: boolean)(arr)
partition(fn: val is T, arr)
partition(fn: boolean, arr)

The rule is "specific first, general last", and while it doesn't exactly explain it here: https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#function-overloads, that does include for currying

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

partition, map, filter, etc. Almost all of them returns generic unbound unary functions

test/partition.test.ts Show resolved Hide resolved
@Harris-Miller
Copy link
Collaborator

While testing this, I discovered a very interesting edge case for isNil: https://tsplay.dev/m3Kpjm

For partition to work correctly with isNil, that will need to be fixed first: #90

@GerkinDev
Copy link
Contributor Author

What do you need from me to consider this PR as mergeable ?

@Harris-Miller
Copy link
Collaborator

What do you need from me to consider this PR as mergeable?

@GerkinDev I merged #90, so

  • Rebase on develop
  • Re-order the overloads
    • The full signature always needs to come last

Harris-Miller
Harris-Miller previously approved these changes Feb 1, 2024
<L extends T = T>(list: readonly L[]): [(L & U)[], Exclude<L, U>[]];
(list: readonly T[]): [(T & U)[], Exclude<T, U>[]];
};
export function partition<T>(fn: (a: T) => boolean): <L extends T = T>(list: readonly L[]) => [L[], L[]];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry one last thing, the overloads, both (fn)(arr)s need to be before (fn, arr)
so this:

partition(fn: val is T)(arr)
partition(fn: boolean)(arr)
partition(fn: val is T, arr)
partition(fn: boolean, arr)

The rule is "specific first, general last", and while it doesn't exactly explain it here: https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#function-overloads, that does include for currying

Copy link
Collaborator

@Harris-Miller Harris-Miller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@Harris-Miller Harris-Miller merged commit 468e104 into ramda:develop Feb 19, 2024
3 checks passed
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.

2 participants