TypeScript Generics Workshop (6 exercises)
solution

Pass Type Arguments to a Reduce Function

The Type Signature for reduce

Before we jump into the solution, let's look at the type signature for reduce.

There are three definitions, which are called function overloads (which we'll explore more another time).

Loading solution

Transcript

Instructor: [0:00] To solve this problem, we're going to have to look quite closely at the reduce type signature. I've done a finding definition and let's look at it now. This is pretty intimidating to look at.
[0:10] There are three definitions, and these are what's called function overloads, which we will cover at some point. These reduce functions, you'll notice that this first one only covers the case where you don't pass a second argument.

[0:24] This second one covers one where we're inside the array type here, all the way at the top. This T is the things that are the members of the array. Heading back down to reduce, this is like a version where you pass in the initial value that's exactly the same shape as the members of the array.

[0:44] We want to create a different object out of those members of the array. We have to infer a U here. This U, it gets inferred from the initialValue. This means then, if I pass in the initialValue, I can see that array.reduce, array is capturing this object here, name: string, and reduce, it's capturing this little empty object there.

[1:13] You can see that generics are captured at different levels based on where they're called. Why is this being captured as an empty object? Well, that's what I'm passing into here. The thing that I'm supposed to be returning here, the accumulator, is this record string here.

[1:33] Objects, currently, is typed as an empty array because that's what the reduced signature returns. It returns the U. Whatever you pass into the initialValue, that's the type that the reduce is going to think that you're returning.

[1:47] We need to somehow tell this reduce function that we don't want this, we want Record<string, { name: string }>. Let's grab this here. The obvious way to do it is just to stick it in here. What this does is it turns the accumulator into this type here, record, blah, blah, blah.

[2:09] The accumulator now, because it's a record, we can freely assign to it, and that's the type that we end up with at the end of the function. That's the U in this situation.

[2:19] We can do this in a few other ways as well. We can force it to infer that this is the type that we want. We can do this. Empty object as record blah, blah, blah. Or we can assign it to the accumulator too.

[2:35] Again, because this is like a more specific type signature, TypeScript tends to pay attention to your annotations more than its own inferences because it's trying to be helpful like that. It's trying to listen.

[2:48] It manages to understand that what it's supposed to be returning is this record here. This is really interesting. Of the three, I would probably choose the type argument just because it's cleanest. Although, probably assigning it to the accumulator is also pretty good too.