Polymorphism and why use it?
Tags: programming, oop, typescript, tutorial
Published: 2021-02-22 12:03:05

# Intro

In this article, I want to introduce you to different types of polymorphism. I'll try to answer questions about what it is, why, and how to use it with different examples. I wrote this article, to sum up, and structure my knowledge after reading [On understanding types, data abstraction, and polymorphism](https://web.cse.ohio-state.edu/~soundarajan.1/courses/788/cardelli85understanding.pdf) paper. I'll use PHP, Go, and Typescript for code examples. 

# What is polymorphism?

Simply put, polymorphism allows your functions to have values of multiple types. It's opposed to **monomorphism**, where functions only work on one type.

For example, consider the following function:

```go
func length(list []int) int {}
```

This function is **monomorphic**. It allows you to pass `list` only as only type `[]int`.

On the other hand:

```go
func length(list []interface) int {}
```

is polymorphic. Your list can be `[]int`, `[]string`, and etc.

# How many types of polymorphism are there?

According to the paper, there are four. We will discuss only two of them in this article, which the paper names as "true polymorphism".

![types of polymorphism](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ylxhoi8zvmmfif6yspq.png)

## Inclusion

Let's start with the one that we all know about. Inclusion polymorphism says that your types can have subtypes, and your function will work with any subtype as well as with the supertype.

For example:

```typescript
interface Writer {
    write(value: string): void;
}

class ConsoleWriter implements Writer {
    public write(value: string): void {
        console.log(value);
    }
}

class FileWriter implements Writer {
    public write(value: string): void {
        // write to file here
    }
}

function write(writer: Writer, value: string): void {
    writer.write(value);
}
```

Although this is a bad example for real-world programming (composition over inheritance), it's good for demonstrating the idea. 

You can pass any object of type `Writer`, and its subtypes to the `write` function. What does it give to us? It simply allows you to program by contract and not to care about the implementation. You always know that the `$writer` object has a defined set of methods that you can run. 

Subclasses and interfaces are not the only way to achieve this type of polymorphism. There's also a concept that's called **duck typing** which says that if an object has a set of methods that are defined in type `A`, then it's type `A`. If it behaves like a duck, then it's a duck.

A common example is Go:

```go
type Writer interface {
	Write(p []byte) (n int, err error)
}

type FileWriter struct {}

func (w FileWriter) Write(p []byte) (n int, err error) {}

func write(w Writer, data []byte) {}
```

For those who are not familiar with Go, the code above defines an interface `Writer` and the struct(think about this as a class) `FileWriter` with the method `Write`. Here, the `FileWriter`  is a subtype of `Writer`, although it doesn't implement any interface or anything.

In Go, any type is a subtype of the type named `interface` which is an **inclusion** kind of polymorphism, but when generics are released, the language will support **parametric** as well. Let's jump to this one.

## Parametric

Parametric polymorphism allows you to define **generic** functions that will work with any type. A common technique that allows you to achieve this is **generic programming**. Consider this method in Typescript:

```tsx
function length<T>(list: T) int {}
```

Users of your function can pass a list of any type here.

As I wrote above, Go doesn't not support this yet. You still can create a function like this:

```go
func length(list []interface) {}
```

But this is **inclusion** polymorphism because any type in the Go is a subtype of the type `interface`.

I won't dive into the details about why generics are better, but you can watch this [video](notion://www.notion.so/%5B%3Chttps://www.youtube.com/watch?v=WzgLqE-3IhY%3E%5D(%3Chttps://www.youtube.com/watch?v=WzgLqE-3IhY&feature=emb_title%3E)) if you're interested.

# What does it give to us?

## One type, many implementations

Polymorphism gives you IoC(Inversion Of Control) power. To simplify, you can create some complicated function that accepts two values:

```go
type Writer interface {
	func Write(data string) string
}

func transformAndWriteData(w Writer, data string) string {
	// transform data
	w.Write(data);

	return data
}
```

Inside that function, you can do whatever you want and then call `w.Write`. You don't care how `Write` behaves. It could write to some remote file, disk, or stdout.

### Unit testing

This concept actually allows you to unit test your program. This is a common technique when you test only one part of the system at a time. It would be hard and unnecessary to use the implementation of `FileWriter` when you want to test `transformAndWriteData`. So you can create a mock:

```go
package main

import (
	"testing"
	"reflect"
	"strings"
)

type Writer interface {
	Write(data string)
}

func transformAndWriteData(w Writer, data string) string {
	data = strings.ToUpper(data) // let's pretend this is useful
	w.Write(data);

	return data
}

type MockWriter struct {
	Data string
}

func (w *MockWriter) Write(data string) {
	w.Data = data
}

func TestTransformAndWrite(t *testing.T) {
	data := "abc"
	writer := &MockWriter{}
	
	expected := "ABC"
	actual := transformAndWriteData(writer, data);

	if !reflect.DeepEqual(actual, expected) {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}
```

## Generic programming

What about parametric polymorphism? It also gives you the power of IoC. For example, you want to implement your own `map` function in TypeScript:

```jsx
function map<T, K>(list: Array<T>, f: ((_: T) => K)): Array<K> {}
```

Or, you want to build a library that provides some sort of authentication:

```tsx
interface User {
  id: int;
  username: string;
}

function authenticate<T extends User>(user: T) {
  //...
}
```

Now, programmers who use your tiny auth library can create their own implementation of user and then use the `authenticate` method. This is a super-simplified example, but you get the idea. 

# Summary

We've defined two types of polymorphism: **inclusion** and **parametric**. Both could be used separately, or together, and allow you to define beautiful abstractions which give you the power of IoC. And both make more sense in statically typed languages, where types could be checked on compile, which will save you time debugging errors during runtime.