Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TS - Higher order generics/inference

Tags:

typescript

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?

like image 301
Martin Avatar asked Oct 15 '25 21:10

Martin


1 Answers

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

like image 180
jcalz Avatar answered Oct 17 '25 12:10

jcalz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!