4

I was hoping that if I use the built-in map function on a tuple of length N in TypeScript then the return type would also be a tuple of length N (perhaps with different type for the elements depending on the function being passed to map). Instead, the return type is just a standard, variable-length array of whatever type the callback function returns. The tuple's length is lost. I wrote a custom function that does what I want, but I'm wondering if there's a better way that is eluding me. I'm trying to improve my understanding of TypeScript. I included a TypeScript Playground link below the code (with the same code). Thanks for your help!

const nums = [1, 2, 3] as const;

// result1 type is string[]
const result1 = nums.map((num) => num.toString());

// so this won't compile
const result2: [string, string, string] = nums.map((num) => num.toString());

// a type assertion would work, but I'd rather not use one...
const result3 = nums.map((num) => num.toString()) as [string, string, string];

// ...because this also compiles yet the type of result4 doesn't match its value
const result4 = nums.map((num) => num.toString()) as [string, boolean, number, symbol, string, number];

// result5 type is [string, string, string]
const result5 = map(nums, (num) => num.toString());

// custom map function that yields the correct return type when used on a tuple
function map<A extends readonly [...any[]], B>(values: A, func: (value: A[number]) => B): { [K in keyof A]: B } {
    return values.map(func) as unknown as { [K in keyof A]: B };
}

See on: TypeScript Playground

2

1 Answer 1

2

Please try next:


type A = readonly [1, 2, 3]

const nums: A = [1, 2, 3];
const toStr = <T extends number>(num: T) => num.toString() as `${T}`

const result2 = nums.map(toStr); // ("3" | "1" | "2")[]

I believe this is not the best solution, because you still have ('3'|'2'|'1')[] and not ['1','2','3'], but this is some kind of step forward

I will happy to see other solutions here. Very interesting problem )

Works only with T.S 4.1

UPDATE

There is a possibility to create type what you want with some help of overloading and variadic tuple types

type Json =
    | string
    | number
    | boolean
    | { [prop: string]: Json }
    | Array<Json>

type CustomMap<T extends ReadonlyArray<Json>> = {
    [Index in keyof T]: { elem: T[Index] }
}

function map<Elem extends Json, List extends Elem[], Return>(list: [...List], callback: (value: Elem) => Return): CustomMap<List>
function map<Elem extends Json>(list: Elem[], callback: (elem: Elem) => unknown) {
    return list.map(callback)
}

// const result2: [{
//     elem: 1;
// }, {
//     elem: 2;
// }, {
//     elem: 3;
// }]
const result2 = map([1, 2, 3], elem => ({ elem }));

Playground

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.