Jonathan Woolf
Jonathan Woolf

Reputation: 11

Typescript Curry Function with generic functions

Below is a typescript curry function definition from a medium.com article that's pretty neat. I was wondering if it would be at all possible to create a typescript curry function like this that allows for a function with generics. Or at least a way to create a curry function where you can more or less specify a custom return type.

E.g.

const example_curry = curry(<T>(name: string, int: number): Promise<T> => {...});
const named_curry = example_curry('name');
const curry_result = named_curry<string>(5);
/* eslint-disable @typescript-eslint/no-explicit-any */
// curry utilty type from https://medium.com/codex/currying-in-typescript-ca5226c85b85

type PartialTuple<
  TUPLE extends any[],
  EXTRACTED extends any[] = []
> = TUPLE extends [infer NEXT_PARAM, ...(infer REMAINING)]
  ? PartialTuple<REMAINING, [...EXTRACTED, NEXT_PARAM?]>
  : [...EXTRACTED, ...TUPLE];

type PartialParameters<FN extends (...args: any[]) => any> = PartialTuple<
  Parameters<FN>
>;

type RemainingParameters<
  PROVIDED extends any[],
  EXPECTED extends any[]
> = EXPECTED extends [infer E1, ...(infer EX)]
  ? PROVIDED extends [infer P1, ...(infer PX)]
    ? P1 extends E1
      ? RemainingParameters<PX, EX>
      : never
    : EXPECTED
  : [];

type CurriedFunction<
  PROVIDED extends any[],
  FN extends (...args: any[]) => any
> = <
  NEW_ARGS extends PartialTuple<RemainingParameters<PROVIDED, Parameters<FN>>>
>(
  ...args: NEW_ARGS
) => CurriedFunctionOrReturnValue<[...PROVIDED, ...NEW_ARGS], FN>;

type CurriedFunctionOrReturnValue<
  PROVIDED extends any[],
  FN extends (...args: any[]) => any
> = RemainingParameters<PROVIDED, Parameters<FN>> extends [any, ...any[]]
  ? CurriedFunction<PROVIDED, FN>
  : ReturnType<FN>;

export default function curry<
  FN extends (...args: any[]) => any,
  STARTING_ARGS extends PartialParameters<FN>
>(
  targetFn: FN,
  ...existingArgs: STARTING_ARGS
): CurriedFunction<STARTING_ARGS, FN> {
  return function(...args) {
    const totalArgs = [...existingArgs, ...args];
    if (totalArgs.length >= targetFn.length) {
      return targetFn(...totalArgs);
    }
    return curry(targetFn, ...(totalArgs as PartialParameters<FN>));
  };
}

I've tried rewrites of the definition with a generic value for the return, or having curry return something like function<T>, but in the end I was just guessing and checking as I don't really know enough about the underlying TS machinery to more intelligently tackle this. Thanks for your time!

Upvotes: 0

Views: 42

Answers (1)

Janek Eilts
Janek Eilts

Reputation: 572

To be honest I did not completely read the question but I guess you want to curry a function and preserve its generics.

Here is what you can do:

type CurryOverload = {
  <A, R>(cb: (p1: A) => R): (p1: A) => R;
  <A, B, R>(cb: (p1: A, p2: B) => R): (p1: A) => (p2: B) => R;
  <A, B, C, R>(cb: (p1: A, p2: B, p3: C) => R): (p1: A) => (p2: B) => (p3: C) => R;
  // todo: add more later
};

const curry: CurryOverload = (() => {}) as any;

const result = curry(<N>(name: N, age: number) => name); // => const result: <N>(p1: N) => (p2: number) => N

If you want a custom return type then you can introduce a generic to the overload.

Upvotes: 0

Related Questions