typed-typings / npm-ramda

TypeScript's type definitions for Ramda
MIT License
384 stars 64 forks source link

Question: Partial does not know how many arguments are available? #412

Open lukasbash opened 6 years ago

lukasbash commented 6 years ago

Given the following super simple snippet:

function foo(x: string) { 
  // some logic
  return something;
}

const y = partial(x, ["bar"])

I would expect that y is a function that takes no parameter because one is already baked in. Of course same goes for other parameters, e.g. if the function takes 3 args and I partial 2, it should come out with 1 arg after the partial.

Any information about this? As far as I could see in the types, there is no overload for defining the parameter types as well. Is it possible to infer those?

ikatyang commented 6 years ago

I tried a bit but it seems it's easy to get Excessive stack depth comparing types and tsc crash, though it did show the correct type in VSCode.

(require TS 3.0)

// from https://github.com/Microsoft/TypeScript/pull/24897#issuecomment-400989548

type Tail<L extends any[]> = ((...x: L) => void) extends ((
  h: any,
  ...rest: infer T
) => void)
  ? T
  : never;

// R.partial

type Shift<T extends any[], U extends any[]> = {
  0: T;
  1: Shift<Tail<T>, Tail<U>>;
  2: never; //=> how to throw type error?
}[U extends [] ? 0 : T[0] extends U[0] ? 1 : 2];

declare function partial<T extends any[], U extends any[], R>(
  fn: (...args: T) => R,
  args: U
): (...args: Shift<T, U>) => R;

// test

type ShiftTest = Shift<[1, 2, 3], [1, 2]>; //=> [3]

declare function tuple<T extends any[]>(...args: T): T;
declare function foo(x: null, y: boolean, z: number): string;

const a = partial(foo, tuple()); //=> (null, boolean, number) => string
const b = partial(foo, tuple(null)); //=> (boolean, number) => string
const c = partial(foo, tuple(null, false)); //=> (number) => string
const d = partial(foo, tuple(null, false, 0)); //=> () => string

const invalid = partial(foo, tuple(1)); //=> (...never) => string

cc @tycho01

KiaraGrouwstra commented 6 years ago

My Playground crashes once I paste in partial, and there are still some outstanding issues about recursive types crashing.

how to throw type error?

Maybe with some infer voodoo, otherwise I'm not so sure we can.

It's great to see tuple manipulation has suddenly started going mainstream too -- progress! :) The tuple trick is new to me. It's unfortunate we need hacks for granular inference, but this is definitely an improvement over regular casts -- pretty cool!

It'd be cool if we could otherwise constrain U input to tuples too though. I fiddled for a bit along the lines of declare function tuplesOnly<T>(x: { length: number extends infer T ? T : never });, but no success. I'm really bad at this infer voodoo magic, so that was to be expected.

Another attempt:

type ActualNumber<T, U = number extends T ? never : T> = U;
let a: ActualNumber<1>; // 1
let b: ActualNumber<number>; // never

I tried making a tuple type based on a length involving ActualNumber, but no luck so far. Cheap man's version :clown_face::

type SomeNatural = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
type Tpl = any[] & { length: SomeNatural };

Alas though, this wasn't as useful as I hoped: it didn't provide free casts as a solution to tuple().

declare function foo(x: Tpl);
foo([1]); // error: number[] not assignable to Tpl

In this particular case though, couldn't we just change partial's U extends any[] to U extends T?