Home > Enterprise >  Map to return `(Wrap<A>|Wrap<B>)[]` instead of `Wrap<A|B>[]`
Map to return `(Wrap<A>|Wrap<B>)[]` instead of `Wrap<A|B>[]`

Time:01-10

I am trying to solve code:

type Wrap<T> = {
    value: T
}
function wrapIt<T>(t: T): Wrap<T> {
    return { value: t}
}
const arr: Array<string|number> = ["a", 1]
const arr2: Array<Wrap<string|number>> = arr.map(wrapIt)
const arr3: Array<Wrap<string>|Wrap<number>> = arr.map(wrapIt) // <--- Doesn't compile

The compiler says:

Type 'Wrap<string | number>[]' is not assignable to type '(Wrap<string> | Wrap<number>)[]'.
  Type 'Wrap<string | number>' is not assignable to type 'Wrap<string> | Wrap<number>'.
    Type 'Wrap<string | number>' is not assignable to type 'Wrap<string>'.
      Type 'string | number' is not assignable to type 'string'.
        Type 'number' is not assignable to type 'string'.

arr3 is what I need for being compatible with some other code I dont own. Also, conceptually, it seems more correct.

Is there some typescript black magic to make it behave the way I want? For now I will type cast it using as.

EDIT: Array<string|number> was just used as an example, I am using this pattern quite often so I would like the solution to work for Array<T> where T is arbitrary and can be a union like string|number but not just.

CodePudding user response:

You can change the definition of the function to distribute over T when returning the type. So this means that when mapping a union (such as number | string) you will get a union of instantiations of Wrapped for each union constituent (so Wrapped<number> | Wrapped<string>) not an instantiation of Wrapped for the union (so not Wrapped<string | number>)

type DistraibutiveWrapped<T> = T extends T ? Wrap<T> : Wrap<T>
function wrapIt<T>(t: T): DistraibutiveWrapped<T> {
    return { value: t} as DistraibutiveWrapped<T>
}

Playground Link

CodePudding user response:

Using explicit type checking?

type Wrap<T> = {
  value: T
}
function wrapIt<T>(t: T): Wrap<T> {
  return { value: t }
}
const arr: Array<string | number> = ["a", 1]
const arr2: Array<Wrap<string | number>> = arr.map(wrapIt)
const arr3: Array<Wrap<string> | Wrap<number>> = arr.map(x => {

  switch (typeof x) {
    case 'string':
      return wrapIt<string>(x);
    case 'number':
      return wrapIt<number>(x);
    default:
      return wrapIt(x);
  }
});

CodePudding user response:

You can create extra function and overload it:

type Wrap<T> = {
    value: T
}
function wrapIt<T>(t: T): Wrap<T> {
    return { value: t }
}
const arr: Array<string | number> = ["a", 1]

function map<Item,>(arr: Item[]): Array<Wrap<string> | Wrap<number>>
function map<Item,>(arr: Item[]) {
    return arr.map(wrapIt)
}

// (Wrap<string> | Wrap<number>)[] which is equivalent to Array<Wrap<string> | Wrap<number>>, just different syntax
const result = map(['hello', 42])

Playground

  •  Tags:  
  • Related