Coding Concepts – Understanding Generics

What are Generics, and why should we use them?

Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters. This approach, pioneered by ML in 1973,[1][2] permits writing common functions or types that differ only in the set of types on which they operate when used, thus reducing duplication. Such software entities are known as generics in AdaC#DelphiEiffelF#JavaRustSwiftTypeScript and Visual Basic .NET. They are known as parametric polymorphism in MLScalaHaskell (the Haskell community also uses the term “generic” for a related but somewhat different concept) and Juliatemplates in C++ and D; and parameterized types in the influential 1994 book Design Patterns.[3] The authors of Design Patterns note that this technique, especially when combined with delegation, is very powerful, however, Dynamic, highly parameterized software is harder to understand than more static software.

So that’s a bit of a long winded, and pretty non descriptive definition sourced from Wikipedia. I’ve been fascinated with Generics for a while, they can be quite hard to grasp, understanding why and where they should be used. The main motivation for the use of generics is to provide meaningful type constraints between members, Generics or Parametric Polymorphism,  are used in most programming languages, and although they can be harder to understand, there are 5 main benefits to using them.

Benefits

  • Stronger type checks at compile time.
  • Fixing compile-time errors is easier than fixing runtime errors
  • Elimination of casts. Which in turn is quicker.
  • Enabling coders to implement generic solutions, which can be reused for multiple purposes.
  • Future proofed for the datatypes of tomorrow.

Using a generic type is much more powerful than using standard types and allows us to create some form of encapsulation and well defined consistent API’s. It doesn’t matter how you use these, but it’s a good to understand how to pass that type of information between different members, and that you can use them externally with return types which will give a more descriptive view of whats happening in your code.

In essence Generics just mean to can assign a Type to a Class. Hence the “T” we will see throughout this example.

Why use Generics? To abstract out data types, allowing you to reuse code and improve maintainability.

So let’s run through a simple TypeScript example to show what I mean.

A Standard Class Example

We’ll start with a non Generic List and switch it to a Generic List!

class Stack
{
private stack: any[];
  pushItem(item){
  this.stack.push(item);
  }
}

This example above is a basic class which contains an Array called stack. Anything can be added to this array! Let’s add a String, a number and a new person object.

 

var newStack = Stack();
var aString = "A String";
var aNumber = 100;
var aPerson = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"};
newStack.addItem(aString);
newStack.addItem(aNumber);
newStack.addItem(aPerson);

This will work, and maybe you want an array that can hold a mishmash of objects. However in most cases this will cause you a number of problems when iterating over the array, sorting, or filtering the values within. Worst of all, you won’t know about these errors until runtime. These will not be found until the code executes, and therefore may not be found during testing.

Whereas with a Generic list will not let you add types to the Stack that it cannot deal with.

To use the generic container, you must assign the type of the container by specifying it at instantiation using the angle bracket notation.

It’s actually pretty simple, let’s look at the example from above but this time lets make 2 separate instances of our array, one that can hold numbers, and one that can hold strings. Firstly we’ll need to create the Generic class.

The Generic Way

Image result for cup type t
Cup of Type Tea!
 
class GenericStack<T>;
{
  private stack: T[]; 
  function pushItem(item: T) { 
  this.stack.push(item); 
  }
}

As you can see the code is pretty much identical to our example above! So what’s all the fuss about? What benefit does this bring us? Well let’s look at this example when initialising this new class.

var numberStack = GenericStack<Number>(); 
var stringStack = GenericStack<String>(); 
var aString = "A String"; 
var aNumber = 100; 
var aPerson = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"};

// These will pass the typescript compiler
stringStack.addItem(aString); 
numberStack.addItem(aNumber);

// But these would all fail.
numberStack.addItem(aPerson);
numberStack.addItem(aString);
stringStack.addItem(aPerson);
stringStack.addItem(aNumber);

So what does this all mean? Well in essence we are have only created a single class, but have changed the behaviour of it depending on the Type referenced.  It’s essentially a contract between the class and the type. Yes, we could have just created 2 separate classes, but then we’d be duplicating code.

Imagine if we’d create a Person class, rather than just a JObject, we could of created a GenericStack<Person>()

Now this is quite a trivial example, but type safety is quite a big deal, especially in JavaScript. As JavaScript is not a compiled language Typescript offers us the benefits of type safety and pre compilation to catch such errors.

As previously mentioned, without the Generic class, errors would only surface during run time.

A major part of development is building reusable, well defined components, If this class was to contain more functions, it could be re-used by teams across an organisation with minimum effort, allowing them to reuse their own types.

But does this not limit what you can do with Generics?

Well when you work with primitive types such as Strings and Numbers and even Arrays you will be familiar with certain methods available to you such as: .ToString() or .length() or .size() or .replace()

These require the compiler to know the type of the variables, unfortunately when using generics this will mean they cannot be used. The number type doesn’t contain a replace() so you wouldn’t be able to use it. And the T Type doesn’t contain any of those listed above! A lot of people will try and implement Generics into their code just to say that they use them. The thing to make sure is that there is a use case for them. Generics come into play when you start moving away from using primitive data types (the basic data types available: numbers, string, etc.) and working with custom objects and classes.

Refactoring

Generics are  useful when refactoring your code, can you see instances in your code where you can abstract out the data type from the data structure?

If the answer is yes then you should be considering Generics!

There’s a lot more to generics, and I’m not going to try and explain it all here, if you’re interested in reading more about Generic, I’ve linked additional resources below which should paint a clearer picture.

Have I missed something useful? Do you have anything extra to add? Have you used Generics in an interesting way? If so share below! 

Thanks for reading.

Chris

Additional Reading

Official TypeScript Documentation – Generics

Dzone – Understanding the use cases of Generics

Git Books – Generics

Code.tutsplus. – Tutorials – Typescript-for-beginners

 

Leave a Reply

Your email address will not be published. Required fields are marked *