I'm using Flow and have a use case for generic types but don’t know how to use them.
I have a class that takes an array of events of one type. All the event types share some properties (like startTime
) but differ on others. The return value for class functions should be of the same type as the events passed in the constructor array.
To make that more concrete, in the code below I would like to stipulate that if you create an instance of EventUtility
with an array of PartyEvent
s, then when you ask for EventUtility.earliestEvent()
you can be sure you will get a PartyEvent
back. (The array passed to the constructor cannot consist of a mixture of objects of different types.)
Thanks.
export class EventUtility {
// pick item based on JSON-friendly conditional notation
events: Array<PartyEvent | AppointmentEvent | MealEvent>
constructor(events: Array<PartyEvent | AppointmentEvent | MealEvent>) {
this.events = events;
}
earliestEvent(): PartyEvent | AppointmentEvent | MealEvent {
// determine earliest event in array and return it
return event;
}
}
What you're looking for is bounded generics. They're explained in the Flow documentation here.
First, make your whole class generic on some type T
, not just your individual methods. Then attach a bound (a type annotation) to the type T
where it's declared at the top of the class, like this:
class EventUtility<T: PartyEvent | AppointmentEvent | MealEvent> {
Now your class will accept some type T
, but only if T
matches the bound you've given it. You can only do things to a value of type T
internally that are allowed for a value whose type is the type of the bound, and whatever type T
is used to create an instance of the class is the same type that will be used for return values.
The second challenge is how to make sure that only one kind of event is stored in your class at the same time. This is challenging, because the type T
is a union type, meaning it could be any of the three. Flow will let you instantiate the class with an array of PartyEvent
objects, in which case T
will have the type PartyEvent
, but it will also let you instantiate it with an array of MealEvent
and AppointmentEvent
objects, in which case T
will have the type MealEvent | AppointmentEvent
. You can get around this by providing an explicit type annotation where you use your EventUtility
, stating which type you want it to contain. Even though Flow will let you instantiate EventUtility
with a combination of types, it will error if you instantiate it with a larger type than you declare in an explicit annotation.
Here's the solution to your example in the Flow playground. Try creating the class with different bounds for T
and uncommenting some of the calls to see what errors pop up. Using { startTime: Date }
as the bound for T
should give you the same result.