Generic Functions in Flow

Matthew Garcia

One of the best concepts Flow borrows from Object-Oriented programs is the concept of generics. There are many cases where generics are essential to refining type checks.

Where are Generics Useful?

Let’s say you have a function memoize. With Flow types, it might look something like this:

function memoize(func: (key: any) => any): (key: any) => any {
  const registry = new Map();
  return function(key: any): any {
    let value = registry.get(key);
    if (typeof value === 'undefined') {
      value = func(key);
      registry.set(key, value);
    }
    return value;
  };
}

The problem is that it will swallow the specifics of func:

// Type is (val: number) => boolean
function isPrime(val: number): boolean {
  // Implementation...
}
// Type is (key: any) => any
const memoizedIsPrime = memoize(isPrime);
// This gives an error, since `Sentinel` is not a number.
isPrime('Sentinel');
// This does not give an error, since 'Sentinel' is any.
memoizedIsPrime('Sentinel');

Making it Generic

It’s as simple as declaring types in chevrons before the parameters and using those as types:

// We're using `K` and `V` here for convention, but you can name them pretty much anything.
export default function memoize<K, V>(func: (key: K) => V): (key: K) => V {
  const registry = new Map();
  return function(key: K): V {
    let value = registry.get(key);
    if (typeof value === 'undefined') {
      value = func(key);
      registry.set(key, value);
    }
    return value;
  };
}

Flow will infer the rest:

// Type is (val: number) => boolean
function isPrime(val: number): boolean {
  // Implementation...
}
// Type is (key: K) => V.  Flow infers that `K` is number and `V` is boolean.
const memoizedIsPrime = memoize(isPrime);
// This gives an error, since `Sentinel` is not a number.
isPrime('Sentinel');
// This gives an error, since 'Sentinel' is a 'K' (number).
memoizedIsPrime('Sentinel');
  Tweet It
✖ Clear

πŸ•΅ Search Results

πŸ”Ž Searching...