-
Notifications
You must be signed in to change notification settings - Fork 4
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
Various small fixes #1
Conversation
@@ -18,6 +18,7 @@ const it = (name: string, fn: () => void) => { | |||
describe('pipe', () => { | |||
it('works when first function has single param', () => { | |||
// One test for each overload | |||
assert.strictEqual(pipe(singleParamFnAdd1)(1), 2); |
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.
Something for the future: test the type output
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.
Might be something we could take a stab at as @WrocTypeScript/organisers for the purpose of doing WrocTypeScript/talks#19.
I'm sure many have the same problem.
"devDependencies": { | ||
"@types/node": "^12.0.10", |
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.
Only used by tests, so safe to move here
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.
Precisely. Reference tweet for anyone interested:
@@ -55,6 +55,10 @@ const result: number = pipeWith(1, add1, times2); | |||
assert.strictEqual(result, 4); | |||
``` | |||
|
|||
## Note about type safety | |||
|
|||
`pipe` and `pipeWith` currently support up to 9 functions. If more than 9 functions are passed, type safety will be lost. If need be, we are open to adding more overloads to avoid this. |
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 why I initially skipped adding an overload for "any number of functions", because it loses type safety. 🤔
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.
Which is the correct way to go. My concern was not about the upper limit (9+) but the lower one.
pipe
and pipeWith
should be allowed to accept parameters dynamically, and as such, they should work with an empty list of arguments or with a single function passed as a tuple.
The original motivating example:
declare const functions: [add1] | [add1, multiplyBy2] | [add1, multiplyBy2, toString];
pipeWith(
0,
...functions, // Unexpected compile-time error
)
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.
We could also suggest adding overloads locally in the README if one really needs them to keep the core small and maintainable.
declare module 'pipe-ts' {
export function pipeWith<A, B, C, D, E, F, G, H, I, J, K>( /* ... */);
}
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.
My concern was not about the upper limit (9+) but the lower one.
Are you saying we don't need the 10+ overload? I think we do, to support the array use case?
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.
We could run a BigTSQuery to see how popular this pattern is (strongly typed tuples) or see how many overloads others libraries are offering and do the same.
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.
compose
from Redux bails out after 4 functions:
Sanctuary can manage 5 functions:
Ramda does 10 functions:
I think what 10 should be enough + we need a bailout strategy just like Redux does with compose
(although they make poor use of type parameters here).
export function compose<R>(
f1: (b: any) => R,
...funcs: Function[]
): (...args: any[]) => R
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.
My worry about having a "bailout" array overload is that type safety will silently disappear once you get past a certain threshold, e.g. I have a pipe
with 9 functions, I add another one, and now suddenly I'm into any
/unknown
territory. I guess that's OK as long as we use unknown
and not any
(as I currently am in this PR)?
Or would it be better to do away with this bailout overload? If we did that, would the use cases you're concerned about (tuples up to 9 items in length) still work?
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.
I guess that's OK as long as we use unknown and not any (as I currently am in this PR)?
Exactly!
Or would it be better to do away with this bailout overload?
We need to draw the line somewhere.
- If we decide not to add the bailout one at all, it won't compile for people using more than 10. They will need to assert both their arguments (because there will be no matching overload) and the final return type.
- If we do add it, the consumers will need to assert the return type (because they will be given
unknown
).
This might look stupid, but if we are to allow the operator functions to be passed dynamically, then these two would be justified:
No functions
export declare function pipeWith<A>(a: A): A;
A lot of functions
export declare function pipeWith(a: any, ...functions: Function[]): unknown;
With these in place, it works like this:
const add = (x: number) =>
(y: number) =>
x + y;
const add5 = add(5);
const zero = [] as const;
const one = [add5] as const;
const five = [add5, add5, add5, add5, add5] as const;
const ten = [add5, add5, add5, add5, add5, add5, add5, add5, add5, add5] as const;
const twelve = [add5, add5, add5, add5, add5, add5, add5, add5, add5, add5, add5, add5] as const;
pipeWith(0, ...zero); // $ExpectType 0
pipeWith(0, ...one); // $ExpectType number
pipeWith(0, ...five); // $ExpectType number
pipeWith(0, ...ten); // $ExpectType unknown
pipeWith(0, ...twelve); // $ExpectType unknown
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.
- If we decide not to add the bailout one at all, it won't compile for people using more than 10. They will need to assert both their arguments (because there will be no matching overload) and the final return type.
Or:
- Add an overload locally
- Send a PR to add an overload upstream
- Nest pipes! -
pipe(...8Fns, pipe(...another9Fns))
😆
Also worth mentioning that I've never had a tuple of functions longer than 9 before, and I've used pipe
for a long time :-P
FWIW I had to remove the bailout/array overload due to a TS bug: #3. So we can continue this discussion but keep in mind that right now it's a non-option!
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.
Also worth mentioning that I've never had a tuple of functions longer than 9 before, and I've used pipe for a long time :-P
Exactly! I don't think it's much of an issue, and if it is, then the users will tell us about it.
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.
👍
Re. https://github.com/unsplash/unsplash-web/pull/3814#issuecomment-507207611