Polymorphism in Typescript

FP and OOP teaming up to fight code redundancy

Hugo Nteifeh
5 min readSep 17, 2020
Photo by Robert Euro Djojoseputro on Unsplash

It’s not uncommon for people to think of Object-Oriented Programming and Functional Programming as mutually exclusive, and understandably so; any discussion involving the two turns quite often into a debate where rivalry is the central theme. But as it turns out, in some contexts, we can utilize both paradigms to get the best of two worlds. Ad-hoc Polymorphism in Typescript is one such context.

The scenario

Consider the case where we’re developing an inline-styling library. The library has different types that correspond to different CSS data types, such as:

  • RGB
  • RGBA
  • HSL
  • HSLA
  • HEX
  • Length
  • Percentage
  • Decibel
  • Time
  • Transformation

For the sake of simplicity, our interface for these inline-style objects will only support one property: The color property.

As demonstrated in the photo above, the value of the color property in our interface has a tagged-union type, which consists of a stringLiteral type and five other custom types represented as Javascript objects and modeled after the nominal-typing model described in my previous article.

Now, within the boundaries of the library’s code, these types are well understood, and their values can be manipulated accordingly, but outside these boundaries — say in React, for example — they bare no meaning. For React to utilize these values for styling, we need to serialize them into either string or number values.

The problem

Based on our scenario, we need a function that serializes a color value into a string value. One way to do it can be this:

Nothing fancy here. We create small functions, each responsible for serializing a single subtype (a subtype of color) and then creating a function that checks the type of the passed argument (using type guards) and calls the function associated with that type. This approach results in excess code and an increased number of imports(in this case, we uses five imports).

In Haskell, you’d be using ad-hoc polymorphism to deal with this scenario. You’d have different implementations under the same name associated with different types, and Haskell will automatically pick the appropriate implementation for you depending on the type of the argument. If Typescript had it, it would “kinda” look something like this (without the error, of course):

As you can see, TS’s compiler is complaining about “duplicate function implementation”. This error shows up because you can have multiple signatures under the same name, but only one implementation is allowed. This implementation is responsible for finding the appropriate function to call. So to fix the error, we need to do the following:

Absolutely worthless! We’ve ended up in a similar situation as before, only worse, with more code. Typescript calls this function overloading. It can be handy in many cases, but not in this one.

In Typescript, you can have many signatures under the same name, but only one implementation is allowed to associate with that name

Function overloading in TS didn’t work as expected, you feel sad and betrayed, the voice inside your head is urging you to reach out to the dark side, where all the poor objects scream in pain caused by mutations and method binding. But then, just right before you make your first step into the OOP world, I — your guardian angel — show up and stop you, offering you a middle ground, a safe place where your functions can thrive together with your objects in a pure atmosphere.

A glimpse into the OOP world
A glimpse into the OOP world

The solution

My go-to approach to this problem is to keep a reference of the function inside the “typed” object, stolen right from the OOP world. Don’t panic, there is no state involved in this approach, and the function is still referentially transparent. This reference is merely there to give easy and quick access to the appropriate function and eliminate code redundancy. We can do that by adding a property whose value is a reference of the function, something like this:

You can ignore the properties type and data. Thergb function is just a factory function that creates values of type RGB. The resulting object will have the property serialize that points to the function serializeRGB. Following this pattern with our other custom types, we can now refactor our serializeColor function to the following form:

Each type is responsible for pointing to its own implementation of the serialization behavior, no need for imports, or manual type checking.

I hope you agree with me that this is way neater than the previous version. If you don’t, that’s fine. We all have the right to have our own opinions, just leave a comment, and I’ll let Liam Neeson find you and convince you of mine.

We can keep references to functions inside objects to associate a particular type with an overloaded behavior without breaking the purity of our functional code.

Recap

  • It’s possible to bring some disciplines from OOP into our functional code without breaking its purity.
  • Using references is an excellent way to implement ad-hoc-like polymorphism in TS.

The scenario that I’ve used in this article is actually a simplified version of one that I found myself dealing with in my project, Rosebox, a styling library I’ve written in Typescript. Check it out.

--

--