# Abstractions, composition, and modularity

A lot of the time, code is about sharing ideas, in addition to solving problems. In code, ideas are most often expressed as abstractions. And solving a problem is usually building up a solution using several small ideas put together. Interested?

In my last post, I wrote about a solution to the Leap problem on Exercism. A reader and I had a discussion around what predicates are, sparking the idea for another post. This time, we'll look at abstractions, composition, and modularity. If you don't want to visit the aforementioned links, and still want to be able to follow, here's a quick recap:

## Leap

Leap is a problem where we want to find if the year passed in is a leap year or not. There are simple rules provided as a direction:

A leap year (in the Gregorian calendar) occurs:

* In every year that is evenly divisible by 4.

* Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400.

Some examples:

* 1997 was not a leap year as it's not divisible by 4.

* 1900 was not a leap year as it's not divisible by 400.

* 2000 was a leap year!

Your task is to determine whether a given year is a leap year.

## Abstractions

One way to think about problems and their solutions is to break down the problem into smaller problems, and then solve those, and combine those to create a solution for the larger problem. It's when we break things down and build them up that abstractions become useful.

What's an abstraction? Anything that represents an idea and doesn't go into details about its implementation. In `TypeScript`

, a `type`

is an abstraction, at its very basic level. It serves to refer to a simple idea of something that has a specific structure. They're reusable, and generally, composable. We can use a type in another type definition, that's what makes it composable. In fact, in `TypeScript`

, we can use a type in its own definition, a special case called recursive types!

In general, it's a good exercise to try to learn things and express things with type definitions and usages in `TypeScript`

, and it's something we'll dabble with in our post. By extension, functions (and classes) are also abstractions - we usually refer to them and not often reach for their definitions when we're building things with them.

## Functions

As an example, let's define a type for a function. A function can have any number of arguments, and its arguments can be of any type. But it always has one return type. So, to define a type for a function, we need to specify the type of its arguments, and the return type. We can write:

We basically have used 2 generic arguments (`Args`

and `Result`

) to define a `Fun`

type, which represents a function. There's an additional constraint on the generic argument type `Arg`

that it has to be a tuple type (a heterogenous array, as JavaScript allows) - we do that by saying it `extends any[]`

. An `extends`

clause on a type argument is called a type constraint. Together, the definition for the type makes it clear that it refers to any function that takes arguments of type `Args`

(which needs to be an array) and a return type `Result`

.

## Predicates

We definitely can see that the solving the leap problem involves a bunch of conditional logic. A predicate is just a function that returns `true`

or `false`

. Given we have an abstraction (type) for a function, it's easy to define a type for a predicate:

We have now defined a predicate type using a single generic type argument `Args`

. There's a type constraint on `Args`

that it *has* to be an array. But for whatever `Args`

is, `Predicate<Args>`

is just a `Fun<Args, boolean>`

which means a predicate is *just a function that returns a boolean*. It can take any number and any types of arguments, just like a function.

This is an example where we use an idea to build up another. Here, we used the idea of a function that takes arguments and returns a value of a specific type, to build up the idea of a predicate that is just a function that returns a boolean. It's simple to see and understand just from the types. As we get better at TypeScript, we can describe a lot of ideas with types. This is one of the ways we can harvest the power of abstractions. Watch out for further abstractions we build!

What makes this abstraction useful? We know that testing whether a given year is a leap year or not is itself a predicate that takes a single number (year) as the argument. What would it's predicate type be?

We can also add a name for the argument using a named tuple type:

It's not necessary but useful to know.

To check whether a year is a leap year though, we need to check several conditions. Each of those can be a predicate, which when combined (in the right way and order), results in us being able to test whether a given year is a predicate. Can we model it likewise? It looks like we need operations like `and`

, `or`

, and `not`

to combine different predicates together. They are usually boolean operators. Maybe we can build them up?

## Boolean logic

Given we're on the lookout for effective composition, let's try to build up predicate logic from boolean logic. We'll define 3 simple functions `and`

, `or`

, and `not`

, as they're the basic logical operators:

`and`

and `or`

both can take any number of `boolean`

arguments, and return a single `boolean`

. We collect the arguments with the `...`

(spread operator) into an array, and then do an `Array.prototype.reduce`

that simply uses the logical operators. These are both generic functions, defined with a generic type argument `Args`

, with a type constraint that it should be an array of `boolean`

s. This means that `and`

and `or`

are only defined for an input type `Args`

with some conditions on the type of `Args`

.

However, there's a problem: `and`

and `or`

now take `0`

or `1`

arguments, which shouldn't be defined. How can we solve that? We basically want to say that `and`

and `or`

take 2 or more arguments.

We can explain it supports 2 or more arguments by simply making the first 2 arguments explicit:

Here, we collect the rest of the arguments into an array, and then allocate another array where we prepend the first 2 boolean arguments. Notice that we don't need a default accumulator value anymore for the `reduce`

, as the array always has at least 2 elements.

We can still do better. Here's a way that makes use of a type constraint we can place on the arguments type `Args`

:

`Array`

s in `JavaScript`

also support a short-circuiting version (which means it only does minimal work and exits as soon as possible) to do minimum work given a list or arguments (`reduce`

runs through all the elements and doesn't short-circuit):

Given that the arguments always have at least 2 elements, it makes sense to not have to think about a default initial argument like we had to for the version with `reduce`

.

That was simple enough, now how do we build up predicate logic?

## Function application

If we're intent on composing predicates, we definitely need function application. Function application is simply applying a function to a value, getting its result.

In addition, we also want to `compose`

functions. Function composition is defined by `f . g`

where if `g`

is a function that takes parameter `x`

, `f . g`

is `f(g(x))`

. Let's write it out, and also use a `pipe`

which is just compose but in the opposite order (`g(f(x))`

):

Notice that we also introduced additional types and functions. We have a unary function, which takes a single argument, and a binary function that takes any 2 arguments, and they both are simply functions. We also added a simple `flip`

function that was used to build up `pipe`

using `compose`

itself. Because we used generic types to define both `compose`

and `flip`

, we get the type information of `pipe`

for free. If we hover over it in an IDE we can easily inspect its type:

It should now be clear what `pipe`

does simply from its signature - I hope you're used to reading types by now. It takes as its first argument a function that takes any arguments and returns a value of type `Intermediate`

, and as its second argument a function that takes a single argument of type `Intermediate`

and returns a value of type `Result`

, and returns a function that takes arguments of type `Args`

and returning a value of type `Result`

. We can see that there's only one way to implement that function that makes the types agree - pass the arguments to the first function, and pass the resulting value of type `Intermediate`

into the second function to get a return value of type `Intermediate`

. Wasn't that simple?

## Predicate logic

Now that we have all the ingredients, writing predicate logic using the boolean logic should be simple. Given we have several imports and we need to disambiguate, let's use an alias for our imports. We don't want it to be too verbose since we may be using the imports a lot:

Notice how we use `map`

and `reduce`

to define our `and`

and `or`

. And we have no conflicts with similar functions in our `boolean`

import because we used a `B`

namespace for the imports. Also note that we use `pipe`

to implement our `not`

for the predicate logic.

### Performance

While our composition of `map`

, `reduce`

, and the boolean operators `and`

and `or`

are neat, it's not necessarily the most performant. We can create short-circuiting versions by using in-built methods. It's useful to know but not necessary to worry about as we're probably not using too many predicates as arguments to `and`

or `or`

to cause significant concern.

### Predicate creation

A simple predicate to construct will be useful, so let's add one:

There are a lot more predicate constructors we can think of, like `isLessThan`

and `isGreaterThan`

and the like: I'm sure we'll know when we need them.

## Leap

Now that we have predicate logic, let's try to implement our solution. It can also be built up. I'd encourage you to have a look at our ordering of arguments so far, and whether they've been useful:

It's interesting that when we have functions like `remainder`

with arguments in a certain order, it becomes very easy to compose things together. We are also trying to disambiguate between a boolean `and`

and `or`

by using a `P`

namespace for predicate imports, even in the absence of `TypeScript`

language server help with the right type hints.

## Conclusion

While by no means this is the only way to solve the problem or even approach it, it was helpful to discuss ideas like abstraction, composition, and modularity. Hopefully that helped you appreciate an idea or two about the power of composition, modularity, and overall: abstractions.

It's very helpful to build up an idea from small pieces that work well independently but also better together. We represent ideas in code as abstractions. But as I've mentioned before, abstractions always come with a cost. Despite it, there may be places where an explicit identification and expression of abstractions helps more than it hurts. Hopefully we create useful abstractions when required!

Fun Fact: A similar approach is discussed as "overkill" in the Exercism video on different approaches to solve Leap