-
Notifications
You must be signed in to change notification settings - Fork 127
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(partialBindTo): Add partialBindTo, partialRightBindTo #466
base: master
Are you sure you want to change the base?
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 5ecb0c3:
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #466 +/- ##
==========================================
+ Coverage 98.41% 98.54% +0.12%
==========================================
Files 127 137 +10
Lines 7954 9251 +1297
Branches 724 779 +55
==========================================
+ Hits 7828 9116 +1288
- Misses 123 132 +9
Partials 3 3 ☔ View full report in Codecov by Sentry. |
I'm sorry it took me so long to review this. I'm having a hard time finding the unique value of this function over modern arrow functions. I must admit that when I said I was supportive of this, I thought we were discussing a function that handles objects and not functions. I understand this is part of lodash, but that is due to lodash being a very old library that predates ES6, which added arrow functions to the language. Can you show a use-case where this would provide substantial benefit over using arrow functions? cc: @viveleroi |
🤷 i personally dont have one are you thinking about https://ramdajs.com/docs/#partialObject ? |
The problem with arrow functions is that we'd have to either redefine all target function arguments and types or use the rest operator and exclude the param we're overriding in the We don't need this very often but here's an example - a grid library provides a cell formatter and we provide a modified version that uses our defaultDateFormat const.
|
I would assume that valueFormatter: (...args) => cellDateFormatter(...args, [defaultDateFormat]); But regardless, I get your broader point about the fact that typescript not being able to infer "backwards" from the function implementation to the function signature makes certain typing situations harder. It's still not a very useful utility because it doesn't solve many other common cases (like functions that take an "options" object as its param). I'm OK with these functions, but I think they should be renamed to include some hint that they are used for functions. One name that comes to mind from the Lodash documentation of |
@cjquines - going over the code, I'm curious to get your thoughts on the API for this function. I feel like the variadic args, although that's how it is done in lodash, is actually less useful than how it's done in Ramda where the args are an array. Making the function take an array and a function, and flipping the order of things so that the args are the "data" component, and the function is the param, e.g. This would allow things like: const partialFoo = pipe(
data,
map(...),
filter(...),
groupBy(...),
values,
...,
partial(foo),
);
const value = partialFoo(...otherArgs); Whereas the current implementation doesn't permit it. This does make the "partial" name even less suitable, though, as the "data" now isn't the function at all; it's the arguments. Maybe |
ChatGPT thinks that |
i do like that api better (and it is something that'll be more useful in my code too). let me rewrite the function and we can bikeshed the name—don't have any opinions right now |
ok done—as for the name, i am liking |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great, let's go over the points I added, and if you agree with them, incorporate them throughout (i only commented on the dataFirst of partial
.
And let's decide on a name for the functions. I like partiallyBindTo
so that we keep the partial
part for people coming from Lodash and Ramda, but make it a little more easier to grok when reading the code for someone who doesn't know the function. the To
reverses the roles so that the function provided is the "param" and not the "data".
src/partial.ts
Outdated
export function partial< | ||
A extends ReadonlyArray<unknown>, | ||
B extends ReadonlyArray<unknown>, | ||
C, | ||
>(args: [...A], func: (...args: B) => C): (...rest: RemovePrefix<B, A>) => C; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To get a more generic and flexible implementation we can use a function type instead of just the array of it's arguments, this provides typescript more power to infer for itself the types:
export function partial< | |
A extends ReadonlyArray<unknown>, | |
B extends ReadonlyArray<unknown>, | |
C, | |
>(args: [...A], func: (...args: B) => C): (...rest: RemovePrefix<B, A>) => C; | |
export function partial< | |
T extends IterableContainer, | |
// This is how functions are defined in ReturnType and Paramters by typescript | |
F extends (...args: any) => any, | |
>(data: T, func: F): (...rest: RemovePrefix<Parameters<F>, T>) => ReturnType<F>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense. note that data has to be data: [...T]
for typescript to infer properly though (for some reason)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is solved by using our IterableContainer
helper type (i tried it!), I think it has something to do with how typescript infers tuples generically.
Inspired by:
https://github.com/microsoft/TypeScript/blob/2a0edf7949ad8427e3ef233a4c549ecbe32953e1/src/lib/es2015.promise.d.ts#L21
Co-authored-by: Eran Hirsch <eranhirsch@gmail.com>
i settled with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot that could go wrong with typing in the unhappy flows, let's add tests to cover as much cases as possible to we can assure users that the function can handle their needs.
}); | ||
}); | ||
|
||
describe('data-last', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dataLast should also test inference via pipe
, as those work a bit differently to simple dataLast invocations. Because dataLast is mainly used in pipes those are the most important cases to test.
ah, yes, the current types indeed aren't great: const fn = (x: number, y: number, z: number) => `${x}, ${y}, and ${z}`;
const data: Array<string> = [];
// this should error:
partialBindTo(data, fn); // (...rest: unknown[]) => string const fn = (...xs: Array<number>) => xs.join(',');
// this should not error:
partialBindTo([1, 2, 3], fn)(4, 5, 6); // error const fn = (x: number = 1) => x;
// this should not error:
partialBindTo([2], fn)(); // error any thoughts on this? |
We'll need to recursively consume the Prefix and Suffix to cover all cases. Check out my changes and the extra tests. I don't have enough time to follow the changes to the |
this still doesn't handle a similar example to the first one i gave above: playground link this ends up being the type inferred in cases like pipe(
[1],
partialBindTo(fn),
); |
ok how about this: playground link type RemovePrefix<
T extends IterableContainer,
Prefix extends IterableContainer,
> = Prefix['length'] extends 0
? T
: T['length'] extends 0
- ? RemedaTypeError<'Too many args provided to function'>
+ ? Prefix extends Array<any>
+ ? T
+ : RemedaTypeError<'Too many args provided to function'>
: T extends [infer THead, ...infer TRest]
? Prefix extends [infer PrefixHead, ...infer PrefixRest]
? PrefixHead extends THead
? RemovePrefix<TRest, PrefixRest>
: RemedaTypeError<'Argument of the wrong type provided to function'>
- : RemedaTypeError<1>
+ : Prefix extends Array<THead>
+ ? [THead?, ...RemovePrefix<TRest, Prefix>]
+ : RemedaTypeError<1>
: T extends [(infer THead)?, ...infer TRest]
? Prefix extends [infer PrefixHead, ...infer PrefixRest]
? PrefixHead extends THead
? RemovePrefix<TRest, PrefixRest>
: RemedaTypeError<'Argument of the wrong type provided to function'>
: Prefix extends TRest
? TRest
: RemedaTypeError<2>
: RemedaTypeError<3>; |
closes #458
adds
partial
,partialRight
functionsMake sure that you:
src/index.ts
mapping.md
We use semantic PR titles to automate the release process!
https://conventionalcommits.org
PRs should be titled following using the format:
< TYPE >(< scope >)?: description
Available Types:
feat
: new functions, and changes to a function's type that would impact users.fix
: changes to the runtime behavior of an existing function, or refinements to it's type that shouldn't impact most users.perf
: changes to function implementations that improve a functions runtime performance.refactor
: changes to function implementations that are neitherfix
norperf
test
: tests-only changes (transparent to users of the function).docs
: changes to the documentation of a function or the documentation site.build
,ci
,style
,chore
, andrevert
: are only relevant for the internals of the library.For scope put the name of the function you are working on (either new or
existing).