4

I have the following scenario Playground

I want to curry twice basically and make sure TS infers the types:

type Picks = Record<
  string,
  {
    a: () => any
    b: () => any
  }
>

const picks = {
  first: {
    a: () => 'something',
    b: () => 'something else'
  },
    second: {
    a: () => 'something',
    b: () => 'something else'
  }
}


const pickSomething = <T extends Picks>(picks: T) => 
  <K extends keyof T>(key: K): ReturnType<T[K]['b']> => {
  const { a, b } = picks[key]
  return a() + b()
}


const works = pickSomething(picks)


// but i would like to do a but more currying:

const someOtherPickFunction = 
  <T extends ReturnType<typeof pickSomething>>(fn: T) => 
  <K extends Parameters<T>[0]>(key: K) => {
  const result = fn(key) as ReturnType<typeof fn<K>> // i would like to do ReturnType<T<K>> but that is not supported. is there a way to get around it?
  return result
}

// here the result is any. 
const mainFn = someOtherPickFunction(pickSomething(picks))('first')

It would work I guess if T<K> was possible to do. Is there a way to go around this?

Martin
  • 99
  • 5
  • I sincerely doubt this is possible. The support for higher order function inference from generic functions in [ms/TS#30215](https://github.com/Microsoft/TypeScript/pull/30215) doesn't happen to give this to you even if you refactor, and there's no all-purpose higher kinded types as requested in [ms/TS#1213](https://github.com/microsoft/TypeScript/issues/1213), so you're probably stuck. But: – jcalz Oct 10 '22 at 17:28
  • I don't quite understand why you need all this currying, or the particular example here. If I were doing this I'd write [this code](https://tsplay.dev/WPpZkW); could you [edit] to show a use case where such refactoring is insufficient? What specific type are you looking for that's better than `string` here? – jcalz Oct 10 '22 at 17:28
  • Or maybe [this](https://tsplay.dev/WPpZkW) is sufficient for your needs? If so I could write up an answer; if not, let me know what I'm missing. – jcalz Oct 10 '22 at 17:30
  • it's actually working :) thanks! I want the currying to make the functions more independent and testable. i have one more question. if i do the following: `const mainFn = (picks: T) => someOtherPickFunction(pickSomething(picks))` is there a way to maintain the types? [playground](http://shorturl.at/acKU7) – Martin Oct 10 '22 at 23:35
  • You can write up an answer for this one if you want, added a new question regarding the comment above here: https://stackoverflow.com/questions/74021666/ts-losing-typesafety – Martin Oct 10 '22 at 23:47

1 Answers1

2

For the specific example as written here, I'd give someOtherPickFunction() the following "simple" typing:

const someOtherPickFunction =
  <K, R>(fn: (key: K) => R) =>
    (key: K) => {
      const result = fn(key)
      return result
    }

which behaves as desired:

const mainFn = someOtherPickFunction(pickSomething(picks))('first');
// const mainFn: string

As you noted, the manipulation you were trying to do with ReturnType<typeof fn<K>> doesn't work; apparently an instantiation expression where the generic function fn is itself of a generic type T gets widened to its constraint before being evaluated. The constraint T is ReturnType<typeof pickSomething>, equivalent to <K extends string>(key: K) => any, and thus typeof fn<K> is (key: K) => any, and ReturnType<typeof fn<K>> is any.

TypeScript unfortunately lacks true higher-kinded types of the sort requested in microsoft/TypeScript#1213, so there's no way to say that a generic type parameter T is itself a generic type that takes a type parameter. There's no T<K> to speak of at the type level.

While instantiation expressions give you some support for this sort of manipulation at the value level (that is, you need an actual value v before you can start talking about typeof v<K>), it's not powerful enough to address your use case. TypeScript has a few features like this, such as higher order type inference from generic functions, but they also can't be pressed into service here.


So the general version of what you're looking for is probably unattainable for now. There are various workarounds in microsoft/TypeScript#1213 mentioned, but they are out of scope for the question as asked, and in my opinion they are not likely to be easy enough to use to be worth it. See Higher-order type functions in TypeScript? for more information.

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360