Happy New Year, dear reader. This is the year we plan to do our best work, simply because. We’re doing it not for money — although that would be nice — or fame (because that which is bestowed upon us by the public can be taken away by the public) but simply because it’s just a very neat thing to do.
As part of my plans to become a better software engineer, I’ve been reading about different kinds of programming languages, the justification for their existence, their strengths and weaknesses and — the most important of them all — the philosophy powering their entire design.
I’d like to tell you all of this is very interesting (and it sometimes is), but it’s often ponderous stuff. There’s a lot of first principle thinking, academic considerations and trivia that I cannot even unravel because I don’t even have the shared cognitive baseline (a CS degree, I presume) to parse all that dense information.
Plus, what the hell, I’m not a professor. I’m just a builder.
Ben Kuhn, a software engineer I respect very much, once wrote that you should be able to understand computers at any arbitrary level. It’s apparently not an idealistic statement, either: you can understand computers at any level of abstraction as long as you’re willing to do the heavy lifting.
Well, we’re going to attempt to do just that, aren’t we?
Today I’m starting at the high-level, reading a textbook (titled, charmingly, ‘Learn you a Haskell for Great Good’), where I’m being introduced to Functional Programming Concepts.
It may be interesting for you to see how I arrived at this decision. Or maybe not. I’m going to create a header so you can skip right past this section if you want.
Why Haskell?
My first introduction to Haskell wasn’t through a desire to learn Functional Programming (since, in classic JavaScript arrogance, I believed my multi-paradigm language was enough scope coverage, thank you very much), but because I got into the Cardano Plutus Program. If you didn’t know, Plutus is Cardano’s programming language, which is entirely adapted from Haskell.
It was inscrutable.
No fault of the language itself, and more from my own fear of doing new things. Then I had my near-death experience (botched surgery that nearly killed me, long story), and I dropped out of the program.
That, as it turned out, was enough to whet my appetite. Before Plutus, I’d done a Functional Programming course on Frontend Masters where I hilariously missed the point (‘currying? That just sounds like painful args parsing. WTH??!’). I was, as the kids say, ngmi as far as grokking this intriguing programming paradigm.
My guardian angel, stubborn as ever, was determined to win me over, and so as I wrote more Solidity and contributed to Ethereum projects, more and more people kept using the word ‘composable’. Composable smart contracts, composable NFTs. Heck, even composable blockchains. The metaverse came along and the world was a-buzzing with the idea of composable reality!
I even wrote a piece that explored the idea of composable NFTs, did a skinny-dip into the UNIX philosophy (huge on composability - are you tired of that word yet?)
I became quickly haunted by the idea of composability.
One day I was watching a video from the Grace Hopper Academy Youtube Channel on ImmutableJS when I read this doozy of a quote from the Eric J. Elliot:
It led me to his blog (where I read the DAO of immutability, probably one of the most beautiful software-related pieces I’ve ever read), and to the book that he wrote, titled “Composing Software” .
So there it was: I’d come full circle: back to composability, and back to functional programming with JavaScript. I was now ready to return to Haskell.
Haskell: “And where did that bring you? Back to me.”
Baby’s First Composition
In a nutshell, with functional programming, the problems you solve are broken down into small problems that are solved by writing small functions that you compose — haha — into a larger function that solves the original, larger problem.
For the benefit of the reader who doesn’t know what composability means, I offer this lossy definition: breaking things down into components, that can then be coupled together, reorganized or reassembled in various ways depending on what you need? Thats composability.
If you are a UI developer, you’ve probably learned to think along Brad Frost’s Atomic Design principles, which is really to think of your website and apps as composed (heh) of composable units.
Composability: Atoms, Molecules, Organisms.
Let me attempt to bridge that understanding from the world of JavaScript.
Let’s assume that the problem we want to solve is a problem of raising numbers to arbitrary power units, then multiplying them by a number.
The intuitive code for this can be written as:
function raiseAndMultiply(number, power, multiplier) {
return (number ** power) * multiplier
};
raiseAndMultiply(3, 2, 5); // returns 45
All in a day’s work, but this isn’t very composable by our definition, is it? Imagine if we wanted to *only* raise a number to the power of x. Why, this function wouldn’t be very helpful at all. We’d need to write another, specific function to do what we’re already doing in this double-action function.
This looks like a trivial problem to have at this level - and it is - but on a large-scale project, you’ll suddenly find yourself overwhelmed by a million utility functions, each doing slight variations of what the other does.
'FUNCTIONS SHOULD DO ONE THING. THEY SHOULD DO IT WELL. THEY SHOULD DO IT ONLY.'
— Robert C. Martin
So let’s make that function a little more composable. Our broad problem is that we want to raise a number to an arbitrary power, then multiply that number by an arbitrary number. We can break that problem into smaller problems, like so:
Raise a number to the power of x
Multiply the result with y
This neatly dove-tails into a crucial element of composability: the output of one function goes into the input of another. This seems simple enough, but things can get technical when you get into discussions about types (functions need to be ready to accept arbitrary data structures from other functions without breaking, which will lead us into discussions about functors, but perhaps that’s a discussion for later.)
Now let’s write our smaller functions:
Raise a number to the power of x
const raise = power => number => number ** power;
Multiply the result with y
const multiply = number => multiplier => number * multiplier;
And now the stage is set.
What can we do with these two functions. Well, the formula for composition is something like this:
f(g(x))
WOT DIS MEAN? It means apply f after g has been invoked on x. It’s short hand for saying that you pass the results of one function into the execution context of the function it’s composed under.
Practically speaking, let’s create our `raiseAndMultiply` function:
// Now we can have specialized functions, like raise2, which only raises to the power of two, and multi5, which only multiplies numbers by 5
const raise2 = raise(2);
const multi5 = multiply(5);
// Compose them together to solve the main problem, which is raising by x and then multiplying by y
// Now let's compose
const raiseThree = raise2(3);
const compose = multi5(raiseThree);
console.log(compose) // 45
If you’ve never done this before, this looks like such complicated code, and it can either make you exasperated or intimidated. Don’t worry - let’s distill this to its essentials.
Currying
What we’re doing here is currying both our raise and multiply functions so we can make them more specialized forms of themselves. There’s a Wikipedia definiton for Currying, but for the impatient, to curry a function is to pass it multiple arguments one argument — and one function at a time, like so:
const raise = power => number => number ** power;
The way to read this is the function named `raise` takes in a “power” argument and returns an anonymous function which takes “number” as its argument which returns number raised to the power supplied.
That bit of syntax is what allowed us create a function that will only raise a number by the power of 20, for example.
const byThePowerOfTwenty = raise(20);
This is called a partially executed function. When we called “raise(20)”, we triggered the first step in the raise function, and passed the remaining, unexecuted parts of the function into byThePowerOfTwenty, like so:
// byThePowerOfTwenty == (x) => x ** 20;
We can verify this on a console:
As a result, we can now do this:
const fiveToThePowerOfTwenty = byThePowerOfTwenty(5);
console.log(fiveToThePowerOfTwenty); // 95367431640625
Neat.
Composing
If you noticed, currying is a form of function composition — we passed arguments and return values from one function to another until all values were collected and we returned a final value.
Because of this feature, we can create different partially executed functions that are derived from (and thus composed on) one base function. We can have byThePowerOfTwenty, byThePowerOfFour, byThePowerOfZeroPointFive, specialized functions for calculating the power of numbers.
We did something similar with the multiply function.
We then composed these functions together to solve the bigger problem (raise to the power of x, then multiply by y).
If we want more generic functions, we can skip the partial execution of the curried function and just compose the base functions directly. This way we’ve kept our logic loosely-coupled for easy reuse and adaptation within our application.
Take-home assignment (LOL)
Chidi (who writes a mean technical blog) adds this, in the comments:
a fun exercise for someone learning about this to try could be implementing a function to compose a variable number of function arguments.
Like:
compose(square, cube, squareRoot)(2) => 8
compose(add, toString)(1, 2) => 'three'
It’s worth it, attempting to write your own function composition solution to the problems above.
Here’s a small hint: consider that a square is a partially-executed, curried function that takes the arguments 2 (for the power) and x (for the number to be squared). Cubed takes 3 (for the power) and x (the number to be raised to the power of three) and squareRoot takes 0.5 as the power and x.
That’s all folks.
If you found this interesting, please share. If you were confused, please leave a comment or respond to this email. I’m still learning, and questions that challenge my thinking are the best catalysts for me.
Really cool. A fun exercise for someone learning about this to try could be implementing a function to compose a variable number of function arguments.
Like:
compose(square, cube, squareRoot)(2) => 8
compose(add, toString)(1, 2) => 'three'
What's the best programming language a beginner should start with?