It All Adds Up: An Exploration of Generic Programming
As a student, I remember learning a lot of programming concepts for the first time using Java. Things like classes and objects, inheritance, polymorphism - the list goes on.
In particular, I remember having a hard time understanding exactly what an object was.
When we were first learning, we used an IDE called BlueJ as an intro to Java. While it had its limitations, it taught me what an object was, and how they relate to classes.
In the second semester, we moved on from BlueJ to Eclipse and IntelliJ, and the programming concepts we covered continued. Things like lambdas, interfaces, abstract classes, and data structures.
One thing we covered that I had a really hard time with was generics - I just couldn’t get it.
It wasn’t until I started dabbling with functional programming that generics finally clicked.
So I’ve written this article to explain generic programming in simple terms, and to show that not only is generic programming not scary, it’s incredibly useful!
Table of Contents
Generic Programming Defined#
Let’s start at the beginning - what do I mean by generic programming?
In a sentence, generic programming is the technique of writing code (usually functions, methods, or classes) which can operate on lots of different types of data.
Purr-fect Cookies#
Analogy time!
Let’s imagine we have some cat-shaped cookie-cutters.

You can use any of your cat cookie-cutters to make different flavoured cookies (chocolate-chip, oatmeal, or gingerbread) using the same cutting mechanism.
The cookie-cutter itself represents generic code - it defines the structure (or shape) of cookies it will cut, but doesn’t specify the type of cookie dough it works on.
The cookie dough (chocolate-chip, oatmeal, gingerbread, etc.) represents the specific data types (integers, strings, custom data types) the generic code will operate on.
The resulting cookies represent the instantiated code - the actual versions of the function or class tailored for a specific type.
Make sense? Great!
It All Adds Up#
To get a feel for generics, let’s change gears a little, and write a math library in Haskell (if you aren’t familiar with Haskell, consider having a quick read through my Haskell intro).
In our library, we’ll provide some helpful functions for our users. One of our helpful functions will be add, for adding two numbers:
add :: Int -> Int -> Int
add x y = x + y
Our add function takes two Ints, x and y, and returns the result of adding them together. Pretty straightforward.
But what if our users don’t want to add two Ints together? What if they want to add an Int and a Float?
Since Haskell is a statically typed language, if a user tries to use our add function to add an Int and a Float, their program won’t compile. Haskell will complain that the program has a type error.
The problem is add expects two Int arguments. Passing an Int and Float violates the type of the function.
How can we fix this?
Well why don’t we define another add function for adding Ints to Floats?
addInts :: Int -> Int -> Int
addInts x y = x + y
addIntToFloat :: Int -> Float -> Float
addIntToFloat x y = x + y
I’ve renamed our original add function to addInts to signify that it’s only used for adding Ints, and our new function addIntToFloat is used to add Ints to Floats.
Cool, our users can now use our functions like so:
addInts 3 12 -- 15
addIntToFloat 10 3.14 -- 13.14
Silly question, but what if a user wants the arguments the other way round - in other words, they want to add a Float to an Int?
…ok, let’s capture that requirement in another function addFloatToInt.
addInts :: Int -> Int -> Int
addInts x y = x + y
addIntToFloat :: Int -> Float -> Float
addIntToFloat x y = x + y
addFloatToInt :: Float -> Int -> Float
addFloatToInt x y = x + y
Functions from our little library can be called like so:
addInts 3 12 -- 15
addIntToFloat 10 3.14 -- 13.14
addFloatToInt 3.14 10 -- 13.14
But wait, a user has emailed us and said they need to add two Floats together!
Fine, let’s add yet another function addFloats:
addInts :: Int -> Int -> Int
addInts x y = x + y
addIntToFloat :: Int -> Float -> Float
addIntToFloat x y = x + y
addFloatToInt :: Float -> Int -> Float
addFloatToInt x y = x + y
addFloats :: Float -> Float -> Float
addFloats x y = x + y
Can you see any problems with this approach?
-
First of all, that’s a lot of repetition - and we’re not even done. What if users wanted to add
Doubles? We’d need 5 more functions just to handle that! -
Our code is brittle and will almost always break. As users create their own custom types, they might want them to be
addable, but we won’t haveaddfunctions defined to handle those types. -
Adding it all together (pun intended), our math library is unmaintainable. We’ll be spending all our time copying and pasting the same code but with different argument types to satisfy our users.
There must be a better way.
Spot The Difference?#
Let’s take a closer look at our add functions.
addInts :: Int -> Int -> Int
addInts x y = x + y
addFloats :: Float -> Float -> Float
addFloats x y = x + y
addIntToFloat :: Int -> Float -> Float
addIntToFloat x y = x + y
addFloatToInt :: Float -> Int -> Float
addFloatToInt x y = x + y
Can you see a pattern?
With each re-implementation of our function:
-
The function’s type signature changes according to the kinds of number we want our
addfunction to handle. -
The actual implementation of each
addfunction is the same. If we abstract it out, it’s of the formaddXtoY x y = x + y.
A good rule of thumb in software engineering is “if you notice a recurring pattern, abstract it out”. So with that let’s abstract this a little.
The key issue is we’re forced to re-implement our functions for each new data type combination.
Since the implementations are identical for each data type, it would be nice to have a function which we could define once, and have it automatically work for all possible number types.
Using generic programming, we can!
One Size Fits (Almost) All#
Since we want a single add function to cover all numeric types, let’s remove all our flavours of add from earlier, and go back to a single add function, but we won’t specify the type-signature yet.
add x y = x + y
We want to enable our function to work across a wide-range of types. To do that, instead of specifying a particular kind of number (such as Int, Float, or Double) let’s put in a placeholder a:
add :: a -> a -> a
add x y = x + y
Just as we use placeholders for numbers in algebra (think a + b = c, for any two numbers a and b which sum to give c), we’ve used a placeholder for types.
We’ve written our function generically: we specify the basic logic without specifying the exact types the function can work with.
Think back to the cat cookie-cutters. The cutter itself doesn’t specify the kinds of cookie dough it can cut - it can cut any kind.
Our generic add function works the same way: it doesn’t specify what it can add, just how to add.
With that in mind, users of our math library are free to add anything to anything - they are happy it works, and we are happy we only had to define our function once.
Constraining Things#
Unfortunately, Haskell won’t accept our new generic add function as it is.
To understand why, let me make a small detour.
Imagine we specified the type signature as a -> b -> c, which means “take an argument of some type a and another of some type b and return something of type c.” This is more generic than our a -> a -> a type signature (which requires that all the types are the same), but it comes with a problem.
Because the function is so generic, it allows adding anything to anything. For example you could write:
add 5 True -- huh?
That doesn’t make sense - how do you add an Int to a Bool?
Our type-signature a -> a -> a is slightly more constrained in that we can only add things of the same type - we can’t add Int and Bool, but we can add Bool to Bool, which still makes no sense:
add :: a -> a -> a
add x y = x + y
add True False -- huh?
Going back to our cookie-cutter analogy, this is like trying to cut cookies with a rolling pin.
To prevent such garbage, we need to provide some type constraints - rules for the kinds of things our function can be used for.
The approach for this varies between languages, but the basic idea is the same: we want to specify the rough shape our code will work for - cookie-cutters should only cut cookies, nothing else!
In Haskell this is done through typeclasses, which we won’t delve into here, but will in another post.
In a sentence, type classes are a way of specifying data which has a certain behaviour or characteristic. They’re a little like interfaces in Java, but better.
For our add function, we want to specify that it will only work for numeric data.
Haskell has a typeclass for that! It’s called Num.
Using the Num typeclass, we would rewrite the add function as follows:
add :: (Num a) => a -> a -> a
add x y = x + y
We’ve specified that our generic type-placeholder a must be a number of some kind.1
If someone comes along and tries to do something crazy like add an Int to a Bool, they’d be thrown in jail the compiler will raise a type error.
Wrapping Up#
Generic programming lets us write less code that does more. Instead of endlessly repeating ourselves for every possible combination of types, we write a single, general version that adapts to different contexts. It’s DRY (Don’t Repeat Yourself) in its purest form.
And this is where functional programming really shines. Languages like Haskell don’t just encourage generic programming — they bake it into your code. You’re not fighting the type system, you’re collaborating with it.
We’ve explored the foundations of generics and generic programming, but there’s so much more to this story (generic classes and methods anyone?). The beauty of generic programming is that you can make your code flexible while using the strength of the type system to provide guarantees.
So whether you’re a Java dev, a Pythonista, or someone who’s tired of duplication, there’s a lot to gain from thinking generically.
And who knows? Maybe next time you reach for a cookie-cutter solution, it’ll be in the best possible sense.
-
This is like specifying a class which
extendstheNumberabstract class in Java. ↩︎