Skip to content

Generics explained

Daisho Komiyama edited this page Apr 3, 2022 · 1 revision

Today, while working on TypeScript Fundamentals V3, I understood what Generics really is.

listToDict function

Here's a function listToDict. It converts a list (array of objects) into a dictionary (object of objects).

interface PhoneInfo {
  customerId: string
  areaCode: string
  num: string
}

function listToDict(
  list: PhoneInfo[],
  idGen: (arg: PhoneInfo) => string
): { [k: string]: PhoneInfo } {
  const dict: { [k: string]: PhoneInfo } = {}

  list.forEach(element => {
    const dictKey = idGen(element)
    dict[dictKey] = element
  })

  return dict
}

const list = [
  {customerId: 'quebec3', areaCode: '514', num: '775-9110'},
  {customerId: 'japan10', areaCode: '0123', num: '22-0387'},
]

listToDict(list, item => item.customerId)
// output
// {
//   "quebec3": {
//     "customerId": "quebec3",
//     "areaCode": "514",
//     "num": "775-9110"
//   },
//   "japan10": {
//     "customerId": "japan10",
//     "areaCode": "0123",
//     "num": "22-0387"
//   }
// }

Nicely done. It successfully converted the list to a dictionary. But here's a problem. We want to use listToDict function for other types of lists. At this point, the function creates a tight coupling with the interface (PhoneInfo) and because of that listToDict cannot be used other than PhoneInfo type list. That sucks😩

const list2 = [
  { serialNumber: 11, make: 'GE' },
  { serialNumber: 23, make: 'Bombardier' }
]
listToDict(list2, item => item.customerId)
// Error (2345)
// Type '{ serialNumber: number; make: string; }' is missing the following properties from type 'PhoneInfo': customerId, areaCode, num

Use any

Ok so let's loosen the type so that we can use the function for any lists.

function listToDict(
  list: any[],
  idGen: (arg: any) => string
): { [k: string]: any } {
  const dict: { [k: string]: any } = {}

  list.forEach((element) => {
    const dictKey = idGen(element)
    dict[dictKey] = element
  })

  return dict
}

listToDict(
  [{ name: "Mike" }, { name: "Mark" }],
  (item) => item.name
)
// {
//   "Mike": {
//     "name": "Mike"
//   },
//   "Mark": {
//     "name": "Mark"
//   }
// }

This works! listToDist function takes a different type of list, then converted it to a dictionary and returned it. However, there are almost no type checks. It provides a JavaScript-level type safety which makes no sense of using TypeScript.

So you can even return homeId which doesn't exist in the given list. Since there's practically no type check, you'll never be caught for this.

listToDict(
  [{ name: "Mike" }, { name: "Mark" }],
  (item) => item.homeId // it's wrong but the compiler won't care 
)

Generics: gain flexibility without giving up all of our type information

function listToDict<T>(
  list: T[],
  idGen: (arg: T) => string
): { [k: string]: T } {
  const dict: { [k: string]: T } = {}

  list.forEach(element => {
    const dictKey = idGen(element)
    dict[dictKey] = element
  })

  return dict
}

// usage 1
listToDict(
  [{ serialNumber: 11, make: 'GE' }, { serialNumber: 23, make: 'Bombardier' }],
  item => item.make
)
// works!
// {
//   "GE": {
//     "serialNumber": 11,
//     "make": "GE"
//   },
//   "Bombardier": {
//     "serialNumber": 23,
//     "make": "Bombardier"
//   }
// }

// usage 2: different list
listToDict(
  [{ camp: 'Chitose', infantry: '11 mechanized regiment' }, { camp: 'Hirosaki', infantry: '39 regiment' }],
  item => item.camp
)
// this works too!
// {
//   "Chitose": {
//     "camp": "Chitose",
//     "infantry": "11 mechanized regiment"
//   },
//   "Hirosaki": {
//     "camp": "Hirosaki",
//     "infantry": "39 regiment"
//   }
// }

Good. Now we can use the function for any time of list while being covered by the type check. Let's get caught to see if the compiler does the job.

// caught by the type check
listToDict(
  [{ serialNumber: 11, make: 'GE' }, { serialNumber: 23, make: 'Bombardier' }],
  item => item.fakeName // Good! The compiler is pissed 😠
)

By using Generics, we can create a generic function with type safety! Yatta 🥳

Clone this wiki locally