-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Smarter type inference for functional programming (RxJS, Ramda, etc) #15680
Comments
There's no "b" in ramda |
I think TS cannot infer generic types only. If you pass concrete types to compose (functions that are not generic), TS will infer types properly. But it lacks inference when arguments are generic themselves. That problem was discussed a little bit here #10247. @gcnew even made a prototype where a generic parameters can be introduced by the arguments in such compositions. |
I do think that @Igorbek's issue is the same as this one. We may choose to consolidate on the two. I have been thinking about this problem for a while and would really like for us to improve here. |
I've updated the example above a little bit, but the spirit is the same. This is a feature RxJS dearly needs in order to have solid TypeScript support with the direction we'd like to go. A direction the Angular team has expressed interest in us going as well. |
@DanielRosenwasser ... is it safe to say improvements around this are on your roadmap? Because it will affect long-term decisions that the RxJS team makes. |
Yeah, most user-reported issues there (and a bunch of failing tests) boil down to this one bug. Minimum repro is |
@benlesh in your example I think maybe you need: const testSet = new SetOf<number>();` Was just preparing to file basically the same issue, but as usual you are exactly 16 days ahead of me. |
Using {
function compose<A, B, C>(
f: (x: A) => B,
g: (x: B) => C
): (x: A) => C {
return x => g(f(x));
}
const result = [1, 2, 3, 4].map(
compose(
x => x + '!!!',
x => parseInt(x)
)
);
console.log(result); // [2, 4, 6, 8]
} However, the definition of {
function compose<A, B, C>(
g: (x: B) => C,
f: (x: A) => B,
): (x: A) => C {
return x => g(f(x));
}
const result = [1, 2, 3, 4].map(
compose(
x => parseInt(x), // error: Argument of type '{}' is not assignable to parameter of type 'string'.
x => x + '!!!',
)
);
console.log(result); // [2, 4, 6, 8]
} Is there any way we can fix this? |
@OliverJAsh: thanks for mentioning that; I guess it got sorta overlooked in my links above. |
@OliverJAsh This is a consequence of inference working left-to-right for contextually typed arguments. To solve it in the general case would require some form of unification, but that might in turn uncover other issues as has been @gcnew's experience. I should add that in my experience APIs where information flows left-to-right are much more intuitive and ergonomic. It becomes evident when you look at the experience someone would have with the "backwards" form of const result = [1, 2, 3, 4].map(
compose(
x => x. With the original form of |
Thanks for the detailed explanation! |
EDIT: updated the types in example to match what works perfectly in Flow type
TypeScript Version: 2.2.1 / nightly (2.2.0-dev.201xxxxx)
TypeScript is unable to infer types through higher-order functions.
Code
Given some imaginary class
SetOf
, which is just some set of values we want to compose operations over... We'll try the following:Libraries Where This Problem Will Exist
RxJS
We want to move RxJS over to using "lettable operators". That means operators that are built in a similar fashion to what you see in the example able. The current prototype augmentation is untenable for large applications, and makes tree-shaking hard if not impossible with regards to Rx.
Ramda
A widely popular functional programming library will undoubtedly have the same issues. Especially give that (I think) the
compose
function shown above exists (probably in a better form) within Ramdba.Redux
combineReducers
in redux is liable to have this same problem, at least when dealing with typed reducers. I don't think the reducer's types can be inferred inside of that call. However, I'm not sure it's a common use case for Redux to have inline reducers in acombineReducers
call.Plain JavaScript
This problem would exist for any higher-order function used in this manner within JavaScript:
cc/ @rkirov @IgorMinar @david-driscoll @alexeagle
The text was updated successfully, but these errors were encountered: