Reputation: 3144
I'm trying to get typings to work with a generic array reduce function which basically merges two objects. The following snippet is a dumped down version of the real code. Why is fl
's type {}
and not IFoo & IBar
?
(I'm aware that this particular example can easily be replaced with a single Object.assign()
call.)
const flatten = <K, T>(prev: K, x: T): K & T => {
return Object.assign(prev, x);
};
interface IFoo {
foo: true;
}
interface IBar {
bar: true;
}
const fooRes: IFoo = { foo: true };
const barRes: IBar = { bar: true };
const fl = [fooRes, barRes].reduce(flatten, {});
console.log(fl); // here, fl : {}
Upvotes: 3
Views: 5676
Reputation: 44634
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U
(T
is the type parameter of the array itself.) So, faced with the code
[fooRes, barRes].reduce(flatten, {})
the type checker's job is to figure out what U
is. Let's walk through its reasoning:
fooRes : IFoo
and barRes : IBar
, so [fooRes, barRes] : (IFoo | IBar)[]
T ~ IFoo | IBar
flatten
is being called with its T
parameter set to IFoo | IBar
flatten
's return type (K & T
) is therefore K & (IFoo | IBar)
flatten
's return type must be assignable to U
, that gives us the constraint U >= (U & (IFoo | IBar))
, which simplifies to U >= (IFoo | IBar)
initialValue
parameter which has a type of {}
U >= {}
{}
. So the type checker infers U ~ {}
.Why doesn't it realise that the return type is IFoo & IBar
? The type checker doesn't reason about the runtime behaviour of your code - that flatten
's parameter takes on a variety of different types throughout the reduction. An array of type (IFoo | IBar)[]
is not guaranteed to have both IFoo
s and IBar
s in it - it could just be an array of IFoo
s. Deducing that flatten
ing an heterogeneous list squashes its constitutent types down would require quite a sophisticated proof, and it doesn't seem reasonable to expect a machine to be able to write such a proof for you.
Upvotes: 11