Closed
Description
// #-------------- This works
type Repeat = (n: number) => (s: string) => string;
export const repeat: Repeat = n => s => s.repeat(n); // Sweet! s and n are both inferred!
// -------------------------#
// #----- Problems begin here
type Repeat2 = (n: number) => <T extends { repeat(n: number): T }>(repeatable: T) => T;
// Only n is inferred as number. Won't work with noImplicitAny enabled
export const repeat2Broken: Repeat2 = n => r => r.repeat(n);
// Workaround is to redeclare everything from the type signature
export const repeat2Ugly: Repeat2 =
n => <T extends { repeat(n: number): T }>(r: T) => r.repeat(n);
// -------------------------#
// #----------- It gets worse
// Type inference breaks for all args following the generic one, so even if you use
// the workaround, the next args won't regain inference
type Repeat3 =
(n: number)
=> <T extends { repeat(n: number): T }>(repeatable: T)
=> (reverse: boolean)
=> T;
// Even if you explicitly provide the boilerplate for the generic arg, the boolean arg
// won't be inferred. So the following won't work with noImplicitAny enabled
export const repeat3UglyAndBroken: Repeat3 =
n => <T extends { repeat(n: number): T }>(r: T) => b => { ... }
// You have to add typing for the last arg
export const repeat3Uglier: Repeat3 =
n => <T extends { repeat(n: number): T }>(r: T) => (b: boolean) => { ... }
// -------------------------#
I'd really appreciate better support for good inference in this kind of scenario, whether it is solved by supporting partial generic application or addition of participation of return-types in inference or whatever.
JS libraries that use a functional programming paradigm often end up loosely or any-ly typed, because you find yourself fighting the type system and adding repetitive type noise all over your code to derive any safety. A couple of examples where better support for FP would be useful:
- Libraries like React or Apollo make use of curried higher-order functions for solving cross-cutting concerns. In-fact, my original use case comes from React higher-order components
- Popular FP libraries like Ramda become tedious to use in a
noImplicitAny
project because any generic functions passed or received from the library tend to quickly devolve into{}
unless you repeatedly hand-hold the type system
Here is my original use-case, if interested:
export type GetState<TState> = () => TState;
export type SetState<TState> = (newState: TState) => void;
export type StatelessComponent<TProp> = (prop: TProp) => React.ReactElement<TProp>
export type StatefulComponent<TProp, TState> =
(stateAccess: { getState: GetState<TState>, setState: SetState<TState> })
=> StatelessComponent<TProp>;
type ScrollableHOC =
(options: ScrollableOptions)
=> <T extends ScrollableProps>(Child: ReactComponent<T>)
=> StatefulComponent<T, ScrollableState>;
// The introduction of the Child arg breaks type inference for getState, setState, props
const scrollable: ScrollableHOC = opts => Child => ({ getState, setState }) => props => {
// ...
}