The use of generics in Flow and TypeScript is pretty similar, except for a few things that are mentioned at the end. The following examples focus on TypeScript.
This simple example uses generics to specify a function that takes in three arguments wherein the last two must be of the same type.
function createTuple<A, B>(a: A, b: B, c: B): [A, B, B] {
return [a, b, c]
}
createTuple('foo', 'bar', 'baz') // good
createTuple(1, 2, 3) // good
createTuple(true, false, true) // good
createTuple(1, 'foo', 'bar') // good
createTuple(1, 'foo', 2) // Argument of type 'number' is not assignable to parameter of type 'string'.
In the zip
function below, we take in two arguments: keys
, an array of Indexable
(string | number
) values, and values
, an array containing values of any type Value
-- this can contain numbers, booleans, objects, arrays, Errors, Dates, anything.
We want to return an AnyObject
type, which is just a Record
that's a zipped copy of the two provided arrays.
type Indexable = string | number
type AnyObject<Value> = Record<Indexable, Value>
function zip<Value>(keys: Array<Indexable>, values: Array<Value>): AnyObject<Value> {
const result: AnyObject<Value> = {}
for (const [index, item] of keys.entries()) result[item] = values[index]
return result
}
zip([1, 2, 3], ['a', 'b', 'c'])
/* returns
{
"1": "a",
"2": "b",
"3": "c",
}
*/
zip(['a', 'b', 'c'], ['a', 'b', 'c'])
/* returns
{
"a": "a",
"b": "b",
"c": "c",
}
*/
zip([1, 'b', 'c'], [false, true, new Error()])
/* returns
{
"1": false,
"b": true,
"c": {},
}
*/
zip([1, 'b', true], ['a', 'b', 'c']) // Type 'boolean' is not assignable to type 'Indexable'.
The type for the values
argument looks more restrictive than it actually is:
values: Array<Value>
At first glance, this looks like Value
can only be of one type, and values
would have to be an array containing only numbers, or only strings, or only Errors, etc., but that's actually not the case.
For the third zip()
call where the passed-in values
array is [false, true, new Error()]
, the Value
type would look like this if we manually declared it:
type Value = boolean | Error
Generic types work similarly in Flow.
In Flow, you can actually give a type to a generic type. This seems somewhat antithetical to the idea of the type being generic, but it is nevertheless possible, and is a way to tighten the genericness of the generic.
/* @flow */
function createTuple<NumberOrString: number | string>(
a: NumberOrString,
b: NumberOrString
): [NumberOrString, NumberOrString] {
return [a, b]
}
createTuple('foo', 2)
createTuple(2, 2)
createTuple(2, 'foo')
createTuple('foo', 'foo')
createTuple('foo', false) // error
This feature doesn't stop you from doing something like this, which would completely eliminate the need for the generic in the first place.
/* @flow */
function createTuple<JustANumber: number>(
a: JustANumber,
b: JustANumber
): [JustANumber, JustANumber] {
return [a, b]
}
This is equivalent to:
/* @flow */
function createTuple(a: number, b: number): [number, number] {
return [a, b]
}