The beauty in Partial Application, Currying, and Function Composition.

July 18, 2017 0 Comments

The beauty in Partial Application, Currying, and Function Composition.

 

 

Today, I had a quirky function that needed writing. It involved far too many inputs and a lot more math (for a single function) than I would have liked. Even though the math itself is actually just basic trigonometry. After writing a few lines code, it became quite obvious that this function would not be maintainable.

I was reminded of something I recently heard and fell in love with:

A Programming language is not for the computer, it is for people.

Another along the same lines:

If the computer doesn’t run it, it’s broken. If people can’t read it, it will be broken. Soon.

Anyway, back to the function.

I had set my mind towards refactoring this thing. It was a typical refactor, break out some responsibilities, sprinkle in a little partial application, add some currying and a wrap it all up with function composition.

What I was left with was some very elegant and easily maintainable code. I reflected upon how rare it is for code to be written this way, which is why I am sharing today.

Disclaimer: A single article is nowhere near enough to cover all the functional programming techniques within. And we won’t be deep diving into any of them. My hope is that this high level overview will pique your curiosity with FP and interest you into starting your own journey.

So what am I doing anyway?

I am trying to calculate the probability of an event occurring. I know that as the velocity of this event increases so does the probability of the event occurring.

If the velocity is below 1,000, then the probability of occurrence is always 50%. If the velocity is above 9,000, the probability jumps to a maximum of 85%. It is easier to visualize with a chart, so here’s a chart.

And I want this all encapsulated into a single function.

This could be solved with a single line trigonometry, but it would look like a regex or machine language and today, we are writing code for people.

First I created a calibration object to transform the X’s to the Y’s.

const calibration = {
x: { min: 1000, max: 9000 },
y: { min: 50, max: 75 },
}

Then I almost wrote this code to return the min Y if the value is below the threshold and the max Y if it is above.

But I really don’t like if statements. If you find yourself curious as to why, then check out the article about it…

And there’s a better way of doing this using Math.min and Math.max. Since I need to use both I plan on just use Ramda’s clamp method, which does exactly this.

Feature scaling, also known as normalizing, is a machine learning technique that I think will apply well here.

The gist of feature scaling is to scale the input (the X) to a value between [0, 1]. Then I will do the reverse and de-scale the number into the Y scale.

You should ignore the majority of the code below, just take note of normalize, specifically how it calls two functions, scale and R.clamp. denormalize does the same, just in reverse.

Function composition is one of my favorite FP tools. It’s a technique to create new functions using existing functions.

normalize and denormalize are great candidates for function composition and we can use Ramda’s pipe method to transform them into this:

Not vastly different, but we’re only half way there. We’ll reduce this more in the Currying & Partial Application section.

To continue learning more about Function Composition, check out my previous article on Function Composition.

Currying is the art of breaking up a multi-argument function into smaller functions that each take one argument.

Currying allows us to easily partially apply arguments to scale and R.clamp.

While I didn’t follow the 1 argument rule

Building the function is now super easy. This is because we have written all of the logic already. All that is left is to compose normalize and denormalize like this:

That’s it! Super simple.

All together it will look like this:

Observations:

Every function is an expression and not a block. The functions immediately return their value.

Every function is idempotent, meaning given the same inputs it will always produce the same output.

Every function is a pure function. The output it calculated from only the inputs.

The majority our functions do not contain any new code, they are simply composed of existing functions.

The simplicity is more than just cosmetic. The transformation I am working with is linear, but actually it’s probably closer to easeOutSine or easeOutCubic pictured below.

Because we have broken the code out into small pieces, it would be unbelievably simple to enhance this function. We simply have to add our transition into the R.pipe pipeline. Here’s a few ways this could be done:

I do understand that “easier” is subjective and a lot of people who are unfamiliar with a functional programming style are probably having their minds melted right now.

You do have to know how to program functionally before you will also feel this way. Though once you do, this kind of style becomes the obvious and correct choice. The key here being once you know.

This is also not meant to be a tutorial. It is just a small lens into the world of functional javascript.

Hey, you made it all the way to the end! Thanks for listening to my rantings. I truly mean that.

Today I have demonstrated how to take functions that have been broken down into their single responsibilities can be easily composed into new functions.

I know I didn’t cover nowhere near enough, but I do have a lot of other functional programming articles that go into more details.

I know it’s a small thing, but it makes my day when I get those follow notifications on Medium and Twitter (@joelnet). Or if you think I’m full of shit, tell me in the comments below.

Cheers!


Tag cloud