Generics

What exactly are generics and why should we use them?

Generics aren’t daunting because of what they are but because of how complicated they get. Breaking them into concrete implementations and renaming type variables can help.

What are they?

A generic type is one that allows developers to build general functionality not bound to a specific type. The generic defines what other types can be used with it. Here is an example of a simple collection generic:

// <T> signals that this type is generic. It's called the type variable.
class Collection<T> {

  private items: Array<T> = [];

  constructor() {}

  getAt(index: number): T {
    return this.items[index];
  }
  add(item: T): void {
    this.items.push(item);
  }
  removeAt(index: number): void {
    this.items = this.items.splice(index, 0);
  }
  count(): number {
    return this.items.length;
  }
}

Why are they hard?

I think the difficulty in generics comes from bad type variable naming or overly complex types. You usually see type variables names T or U (like in my example above).

class Collection<TCollectable> {
  // Remaining code left out for brevity

  private items: Array<TCollectable> = [];

  getAt(index: number): TCollectable {
    return this.items[index];
  }
  add(item: TCollectable): void {
    this.items.push(item);
  }
}

TCollectable is more explicit. It adds context to the T type variable: something collectable.

Here is how we use of the above type with the TCollectable as string:

const stringCollection = new Collection<string>();

Here is a more complex generic:

type Action<T1, T2, T3, T4, T5, T6> = (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6) => void;

const func: Action<string, number, string, number, string, number> = (s1: string, n1: number, s2: string, n2: number, s3: string, n3: number) => {
  console.log(s1, n1, s2, n2, s3, n3);
};

A real example in C#. This represents a function with 6 parameters that are different types. The naming is poor as each param is just Tn. Better naming might help (e.g. TFirstParamter, TSecondParamter) at least to understand what’s happening. In this example refactoring to a type with more context makes sense:

type Action<TOptions> = (options: TOptions) => void;

type ManyOptions = {
  FirstArg: string;
  SecondArg: number;
  // ... etc
};

const func: Action<ManyOptions> = (options: ManyOptions) => {
  console.log(options);
};

I find it easier to understand a single type of ManyOptions vs six different, unrelated, types.

Why use generics?

Mostly to save code. You don’t need to change the collection code when you change the type of TCollectable. A string-only collection looks like:

class Collection {

  private items: Array<string> = [];

  constructor() {}

  getAt(index: number): string {
    return this.items[index];
  }
  add(item: string): void {
    this.items.push(item);
  }
  removeAt(index: number): void {
    this.items = this.items.splice(index, 0);
  }
  count(): number {
    return this.items.length;
  }
}

It’s clear that code isn’t specific to strings. Generics can share code without re-implementing anything.

const stringCollection = new Collection<string>();
const numberCollection = new Collection<number>();
const objectCollection = new Collection<Object>();

Here is an example on repl.it.

What can I learn next?

More Posts