Home > database >  Correct typing for the first element of array
Correct typing for the first element of array

Time:10-19

For typing practice purpose, I'm trying to apply the following typing to get the first element of an array :

type First<T extends unknown[]> = T extends [infer P, ...unknown[]] ? P : never

I have the following implementation :

const getFirstOrThrow = <T extends unknown[]>(arr: T): First<T> => {

  if(arr.length === 0) {
    throw new Error("Empty array")
  }

  return arr[0]; // Type 'unknown' is not assignable to type 'First<T>'.(2322)


}

For some reason typescript is not able to understand that the array should have at least 1 element.

I tried with the following type too :

type First<T extends unknown[]> = T["length"] extends 0 ? never : T[0];
type A = First<[]> // never 
type B = First<[1]> // 1 
type C = First<[1,2]> // 1

Is something like this possible with typescript ?

This typing seems ok to me :

declare function getFirst <T extends unknown[]>(arr: T): First<T>;

const A = getFirst([]) // never  
const B = getFirst([1]) // number 
const C = getFirst([1, 2]) //number

CodePudding user response:

Just as an alternative to Matthieu's good answer, a couple of other options for you (well, perhaps a couple of variations on another option):

  1. You can make the type of the element generic instead of the type of the array, which avoids the need for an extends in the type parameter entirely:

    type First<T extends unknown[]> = T["length"] extends 0 ? never : T[0];
    
    const getFirstOrThrow = <ElementType>(arr: ElementType[]): First<ElementType[]> => {
    // −−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^−−−−−−−^^^^^^^^^^^^^−−−−−−−−−^^^^^^^^^^^^^
        if (arr.length === 0) {
            throw new Error("Empty array");
        }
    
        return arr[0];
    };
    

    Playground example

    I think it's fundamentally the same thing, though, since the generic type parameter is unconstrained (and thus effectively extends any).

  2. (This is obsoleted by your edit to the question saying you were explicitly trying to use First, but I'll leave it here for future readers.) And if you use the element type as the type parameter rather than an array type, you don't need First anymore, you can just use ElementType directly (or even leave it off; it'll be inferred from the return):

    const getFirstOrThrow = <ElementType>(arr: ElementType[]): ElementType => {
    // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^
        if (arr.length === 0) {
            throw new Error("Empty array");
        }
    
        return arr[0];
    };
    

    or (inferring from return):

    const getFirstOrThrow = <ElementType>(arr: ElementType[]) => {
    // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^
        if (arr.length === 0) {
            throw new Error("Empty array");
        }
    
        return arr[0];
    };
    

    I'd include it explicitly so that if I messed up the code inside the function, TypeScript would catch me, but I've seen it argued well both ways. :-)

    Playground link

CodePudding user response:

Make it T extends any:

const getFirstOrThrow = <T extends any[]>(arr: T): First<T> => {

  if (arr.length === 0) {
    throw new Error("Empty array")
  }

  return arr[0]; // ok
}
  • Related