-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Compose: implement compose natively instead of reexporting from Lodash #32734
Conversation
I looked into typing this after conversation at #32709. It's an interesting problem 🙂 In my findings, the best solution seems to be a bunch of overrides: export function compose<A, B, C>( ...fs: [Fn<B, C>, Fn<A, B>] ): (input: A) => C;
export function compose<A, B, C, D>( ...fs: [Fn<C, D>, Fn<B, C>, Fn<A, B>] ): (input: A) => D;
export function compose<A, B, C, D, E>( ...fs: [Fn<D, E>, Fn<C, D>, Fn<B, C>, Fn<A, B>] ): (input: A) => E;
// …as far as you want to go…
export function compose(...fs: [AnyFn, ...AnyFn[], AnyFn]) {
return (input: FirstArg<typeof fs>): LastReturn<typeof fs> =>
fs.reduceRight((val, f) => f(val), input);
} It has good user experience: Although type errors are a bit funny with right-to-left application. Here, the first-applied function (2nd param) receives a string and returns a number, while the second-applied (1st param) function accepts a string: Dealing with a well-typed arbitrary number of functions did not seem to be possible providing satisfactory user experience (although I'd love to be proved wrong). The closest I was able to get was a My submission including work including alternate types// For testing and comparison
import _ from "lodash";
const { flowRight } = _;
export { flowRight };
// Some testing functions with simple types
export const stringToNumber = (x: string): number => {
if (typeof x !== "string") {
throw new TypeError(`Expected a number, got ${JSON.stringify(x)}`);
}
return x.length;
};
export const numberToString = (x: number): string => {
if (typeof x !== "number") {
throw new TypeError(`Expected a number, got ${JSON.stringify(x)}`);
}
return x.toExponential();
};
export const arrayWrap = <T>(x: T): [T] => (console.log([x]), [x]);
export const arrayUnwrap = <T>([x]: [T]): T => (console.log(x), x);
// A couple of utilities
type Fn<A, B> = (arg: A) => B;
type AnyFn = Fn<any, any>;
//
// A big complex implementation for arbitrary number of inputs
//
type NextFn<In, Fs extends [...AnyFn[], Fn<In, any>]> = Fs extends [
Fn<In, infer Out>
]
? Out
: Fs extends [...infer Rest, Fn<In, infer Out>]
? Rest extends [...any, Fn<Out, any>]
? NextFn<Out, Rest>
: never
: never;
type Compose<Fs extends [AnyFn, ...AnyFn[], AnyFn]> = Fs extends [
...infer Rest,
Fn<infer Input, infer NextIn>
]
? Rest extends [...any, Fn<NextIn, any>]
? NextFn<NextIn, Rest> extends never
? never
: Fn<Input, NextFn<NextIn, Rest>>
: never
: never;
type FirstArg<Fs extends [...AnyFn[], AnyFn]> = Fs extends [
...any,
Fn<infer A, any>
]
? A
: never;
type LastReturn<Fs extends [AnyFn, ...AnyFn[]]> = Fs extends [
Fn<any, infer A>,
...any
]
? A
: never;
export const composeAlt = <Fs extends [AnyFn, ...AnyFn[], AnyFn]>(...fs: Fs) =>
((initial: FirstArg<Fs>) =>
fs.reduceRight((val, f) => f(val), initial)) as Compose<Fs>;
const fAlt = composeAlt(stringToNumber, numberToString, stringToNumber);
const gAlt = composeAlt(stringToNumber, stringToNumber);
//
// A much simpler implementation for a set number of inputs that must be manually typed out.
// I recommend this approach.
//
export function compose<A, B, C>(...fs: [Fn<B, C>, Fn<A, B>]): (input: A) => C;
export function compose<A, B, C, D>(
...fs: [Fn<C, D>, Fn<B, C>, Fn<A, B>]
): (input: A) => D;
export function compose<A, B, C, D, E>(
...fs: [Fn<D, E>, Fn<C, D>, Fn<B, C>, Fn<A, B>]
): (input: A) => E;
// …as far as you want to go…
export function compose(...fs: [AnyFn, ...AnyFn[], AnyFn]) {
return (input: FirstArg<typeof fs>): LastReturn<typeof fs> =>
fs.reduceRight((val, f) => f(val), input);
}
const f = compose(stringToNumber, numberToString, stringToNumber);
const g = compose(stringToNumber, stringToNumber); |
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 could solve the issues with buggy (?) types for Lodash that @sarayourfriend is dealing with in #32709.
Currently just a draft, it needs at least JSDoc and/or TypeScript types before it's ready.