Composition is better than inheritance. Why?

Photo by Damon Lam on Unsplash

Intuitively we all know what composition is. Just take a look at a painting and different pieces are combined in a way that produces one whole thing. Start listening to music and hear how harmony, melody, and rhythm makes one sound.

What is inheritance?

I’m pretty sure that you already know what inheritance is. But just to be sure that we have the same thing in mind I will assume that inheritance is a way of reusing code.

The gist of it is that as a developer you must define a class. This will serve as a blueprint for creating instances, objects, of this class. Each of this class can define some properties and behaviors. If there is a need for another class with similar behaviors the developer might create a new class that will be inheriting from the base class. To give a very common example just think of an animal kingdom.

In the realm of UIKit there is a similar structure. Just open Cocoa Fundamental Guide and find something like this:

UIKit class inheritance

You can clearly see that UIButton inherits from UIControl, in turn, UIControl inherits from UIView… you could trace this chain back to the root which is NSObject. All of this means that anything deeper has all the methods and properties of that what is before. Or to put it in other words everything is an NSObject. So imagine that you have to add some new things to this hierarchy. Where would you put it?

Also on top of this hierarchy there should be only properties and behaviors that we want on all other objects/classes. So here comes true world that will test if conditions that were true during the creation of this hierarchy are still true. Do all of the components need to know if the user is logged in or not? What is the contents of the basket? Can you tap it? And what if it has to do both but there is no common parent?

Bad luck. This system is working. It has a lot of clients that are consuming this API and you cannot just kick those people in the ass. Maybe this hierarchy was fine when it was developed by now times have changed. The requirements did change. So need something that will allow us to couple things loosely.

Composition you already use

Leaving behind paintings and music in our IT world composition related to programming is what interests us the most. And I dare to say that the most common one is aggregation.

Gist of it is making bigger pieces from smaller ones like so:

struct User {
    let name: String
    let age: Int
}

I know it’s trivial but that’s the point. Here User aggregates two simpler types (Int, String). But now this new piece can be used to construct something else:

struct ContactInfo {
    let user: User
    let address: String
}

Exactly the same technique is reused when creating fancy UIView-s. Just aggregate by addSubview other views that together will create a desired outcome. And again this new view can be added as a subview of another view. Fun with views never stops.

That is only aggregation of data. When I start to add behaviour thru methods I will slowly get back from where I have started. What should I do when I need a same data but behaviour is slightly different? Should I change this structure to a class and introduce inheritance? As you can see the temptation to inherit is big here.

What we can do is to take a look to a realm od functional programming languages such as Haskell. What we will do is we will have plain data without any behaviours. You might come across a saying dumb data and smart functions.

Function Composition

We are in luck. Why? Well in Swift you can’t inherit over functions so we must compose.

To make those functions as compassable as possible we need to follow some rules. One functions need to be as simple as possibles, meaning they will do only one thing. The #2 will be that they will be pure. That is they will not modify anything outside of their scope. Value in and value out, no mutation.

When we have this then any other function can be build from those smaller pieces . Just like with a example of User and ConcatInfo.

First steps in Composition

So we need something that we can compose. Let’s start slow and define two functions that later will be used to build more complex structures.

func  incr(_ x: Int) -> Int { x + 1 }
func multi(_ x: Int) -> Int { x * x }

One functions knows how to increment a number and another one knows how to square it (multiply it by itself). Having what we have let’s try to represent some simple equations:

// x + 1
let x = 1
incr(x) // 2

Let’s break it apart. Equation x + 1 can be represented as let x, that is my X and call to increment like so incr(x) which is the + 1 part. Ok another simple example but bear with me it’s going somewhere.

// x * x
let x = 2
multi(x) // 4

Nothin out of the ordinary. Just a normal day to day task of a developer calling functions. But what happens when those equations became more complicated?

// (x^2) + 1
let x = 2
incr(multi(2)) // 5
 
// ((x + 1)^2)^2 + 1 or (x+1)^4 + 1
let x = 0
incr(multi(multi(incr(0)))) // 2

Last example is on the edge of readability. And this is just a simple mathematical equation not a complicated business logic where those function names could be more vague.

But we can take a look at this problem from a different side. There is something that is repeating here. And if something repeats then you can hide it behind an abstraction that you can name! Or if you prefer a more common name, you have a design pattern.

So what’s the pattern? Easy, calling a function to get a result that is used to call another function.

func compose<A,B,C>(
    _ f: @escaping (A) -> B,
    _ g: @escaping (B) -> C
    ) -> (A) -> C {
    return { a in
        let b = f(a)
        let c = g(b)
        return c
    }
}

A bit verbose but that is the literal implementation of compose function. It could be just g(f(a)) but readability is more important than cleaver code. And after all we are learning here!

How it works? It returns a block, a function without a name an anonymous function. This block takes one argument (instance) of type A. That is exactly the right type that can be used as input to the first argument of compose function that is called f (typical name for the function). This f-function is run to produce an instance of type B. This, in turn, is fed as input to g-function that produces an instance of type C. This is the returned value of the whole function.

Having this new toy now it’s easy to compose an equation: (x + 2)^ + 3. We don’t have all the building blocks that we need. But we can easily create them and reuse them later. So let’s start with nothing… I mean zero:

let zero = 0

From now on I have an abstraction in code that symbolizes the idea of zero. I can use in anywhere in code that I need it. Composition enables the creation of new things out of old ones. This can look something like this:

let addTwo   = compose(incr, incr)   // +2
let addThree = compose(addTwo, incr) // +3
let tetra    = compose(multi, multi) // ^4
 
let addTwoAndTetra = compose(addTwo, tetra)   // (x + 2)^4
let final = compose(addTwoAndTetra, addThree) // (x + 2)^4 + 3
 
final(-1)   // 4
final(zero) // 19
final(1)    // 84

Notice that every other piece was created using something that already exists. That is code reuse. And what’s cooler those new parts were used to create even newer parts. This is a very powerful idea.

Bigger thing can be decomposed into its smaller components, analyzed, and put back together. Do those parts need to be unit tested? Let’s say we have tests for initial components. Also we have tests for the compose function. So this code is reusing code that is tested and works. It has this algebraic property that a working program combined with a working program produces a working program. Would this unit test add any value? There is something to think about.

Let’s refactor this code a bit. It’s not that it’s bad but there is something hiding when it’s written like this. So I will replace each defined smaller part by that whats stands at the right side of = operator.

// (x + 2)^4 + 3
let composed =
compose(           // <-- plumbing🚰 (x + 2)^4 + 3
    compose(       // <-- plumbing🚰 (x + 2)^4
        compose(   // <-- plumbing🚰  x + 2
            incr,  // <-- basic block 🧱 +1
            incr   // <-- basic block 🧱 +1
        ),
        compose(   // <-- plumbing🚰 ^4
            multi, // <-- basic block 🧱 ^2
            multi  // <-- basic block 🧱 ^2
        )
    ),
    compose(      // <-- plumbing🚰 +3
        compose(  // <-- plumbing🚰 +2
            incr, // <-- basic block 🧱 +1
            incr  // <-- basic block 🧱 +1
        ),
        incr      // <-- basic block 🧱 +1
    )
)
 
composed(-1)   == final(-1)   // true
composed(zero) == final(zero) // true
composed(1)    == final(1)    // true

Plumbing is clearly visible as our basic building blocks. It’s nice how this shows code reuse. Don’t be fooled you can’t run away from the plumbing. But the idea is to hide it so the intent is clear and passing of values don’t stay in the way. In the first example pieces are smaller and I think that’s the way I would use this in a real code. The second one shows this plumbing.

There is one more thing that compose function allows. We can compose anonymous functions to make more complicated behaviours.

// (x + 3)^3 + 10
let composed =
compose(
    compose(
        { (x: Int) in x + 3 },
        { (x: Int) in x * x * x }
    ),
    { (x: Int) in x + 10 }
)
 
composed(-3)   // 10
composed(zero) // 37

Reuse is swapped for expressivity. Just the right functions were defined as ad-hock. But as always this new thing can be stored and serve as a building block in other places. What if those anonymous parts are needed somewhere else? Well… you have to copy them or make them less anonymous.

So why composition is better?

It allows you to change your mind. Components are loosely coupled so It’s easy to break it apart, add and or remove something and glue things together again. Small easy to understand and tested pieces are reused to build bigger functionalities.

Summary

All of this is applicable not only in Swift. Those concepts are more general than any programming language and this is a good thing. Theologies come and go so it’s better to learn something more universal.

To keep you on your those here a some links to materials that got me started and are a big inspiration for talking about it. So let’s go:

  • Pointfree.co – awesome resource on functional programming in Swift. You should check them out. Some content is free for rest you have to pay but it’s totally worth it!
  • Programming with Categories – one could say that category theory is the science of composition. So if you are not afraid of a lot of math the check this youtube playlist.
  • Overture – is a cool Swift framework that has defined compose function inside and many many other useful functions. There are nice examples and you can peek at implementations to get some intuition about how things should work.
  • F# for fun and profit – this time not a Swift resource but a one having a lot of examples with practical use of functional programming concepts. Besides videos you can download the whole site as a pdf or get in kindle format.

That’s it for now and thanks for reading this 🙂

1 thought on “Composition is better than inheritance. Why?”

  1. Pingback: Less Noisy Composition with Operators - I do IT

Leave a Reply

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