Reputation: 7681
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
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:
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]
}
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