Skip to content

Instantly share code, notes, and snippets.

@rpivo
Last active December 8, 2020 02:59
Show Gist options
  • Save rpivo/325e8287f4351b3733ab3d05ffa4d170 to your computer and use it in GitHub Desktop.
Save rpivo/325e8287f4351b3733ab3d05ffa4d170 to your computer and use it in GitHub Desktop.
Comparing Generics in Flow & TypeScript

Comparing Generics in Flow & TypeScript

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

Adding Types to Generics in Flow

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]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment