ChaseMoskal
ChaseMoskal

Reputation: 7681

typescript trouble in tuple town: preserving argument types

i'm having a tough time figuring out how to make my and function here: it has a simple job. it must accept a bunch of items, and concatenate them into an array that starts with "and"

// the items 'and' may accept
type Item = false
  | {type: "alpha", a: boolean}
  | {type: "bravo", b: number}

// 'and' function adds items to an array
function and<Items extends Item[]>(
    ...items: Items
  ): ["and", ...Items] {

  return ["and", ...items]
}

// calling 'and' with a few items
const [op, item1, item2, item3] = and(
  false,
  {type: "alpha", a: true},
  {type: "bravo", b: 3.14},
)

// verifying the types are preserved
op    //> ✔ type "and"
item1 //> ✘ type Item, expected false
item2 //> ✘ type Item, expected {type: "alpha", a: true}
item3 //> ✘ type Item, expected {type: "bravo", b: 3.14}

the problem is that item1, item2, and item3 are all losing their original specific type information, thus my and function is actually a destructive operation on types — instead, i need the types preserved

is there a different approach? thanks!

Upvotes: 3

Views: 53

Answers (1)

Guilhermevrs
Guilhermevrs

Reputation: 2344

Indeed, typescript is not very fit for this kind of operations (not that I am aware of, anyway).

Problem seems to be that there are cases where the compiler won't be able to correctly type those guys

const whatsMyType: Item = false

// calling 'and' with a few items
const [op, item1] = and(
  whatsMyType
)

I would go for either of the below two approaches:

Overloading

Pros: Have the correct typing without much effort

Cons: Not very much scalable

(obs: it's the approach chosen by RxJS pipe for example)

function and<A extends Item>(a: A): ['and', A];
function and<A extends Item, B extends Item>(a: A, b: B): ['and', A, B];
function and<A extends Item, B extends Item, C extends Item>(a: A, b: B, c: C): ['and', A, B, C];
function and<Items extends Item[]>(
    ...items: Items
  ): ["and", ...Items] {

  return ["and", ...items]
}

Type predicate

Pros: Extensible

Cons: Extra logic in the code

const isFalseItem = (a: Item): a is false => {return a === false};
const isAlphaItem = (a: Item): a is ({type: "alpha", a: boolean}) => { return !isFalseItem(a) && a.type === 'alpha'}
const isBravoItem = (a: Item): a is ({type: "bravo", b: number}) => { return !isFalseItem(a) && a.type === 'bravo'}

const [op, item1, item2, item3] = and(
  false,
  {type: "alpha", a: true},
  {type: "bravo", b: 3.14},
)

if (isAlphaItem(item1)) {
 // do something alpha
} else if (isBravoItem(item1)) {
 // do something bravo
}

Upvotes: 1

Related Questions