Imagine if looking at your business logic read as simple as a story from a children's book. That's exactly what we'll cover by learning how to compose small pieces of business logic and also set them up for further composition in the future.
We're showing a list of fictional tweets in a trivial React component created with create-react-app, however the example is not React specific and can be applied to any framework/environment.
Our example will refactor out business logic from a non-composable function to small composable pieces. To accomplish this we'll be using flow, filter and map from lodash/fp, which is included in the regular lodash npm package.
This approach works well in any environment where you're not relying on object references to your original data, as our functional approach creates new collection objects instead of mutating them.
Guide to lodash/fp: https://github.com/lodash/lodash/wiki/FP-Guide
We've got this getTweetsFromMainPage function where we'll be doing all of our work. If we look at our mock data, we see that some tweets have a deleted flag, and some tweets have an inappropriate flag. Let's start by refactoring out our logic which filters out inappropriate tweets.
We're going to be using lodash/fp for this, so let's install it. I'm going to go yon@lodash, and we're done. We still want to be filtering, so let's import filter from lodash fp. Now we can declare a function which removes inappropriate tweets.
I'm going to call this removeInappropriate. That's going to be a call to filter. Our iteratee function is going to be the same anonymous function which we've already declared.
Lodash/fp's version of filter is similar to lodash's regular filter function, except that it's iteratee first and carried. This let's us pass our iteratee function before we pass the collection we're operating on. If we wanted to pass the collection, we would just do another call with our array, but we don't want to do that yet.
Now, let's factor out our remaining logic. I'm going to type const removeDeleted=filter. Once again, we'll reuse our anonymous function. Finally, let's take care of this map call. I'll call this getPropsForDisplay, which will be a call to map. Once again, I'll reuse my anonymous function.
We'll also have to import map from lodash/fp. Now we've describe all our business logic without have to explicitly say which data we're going to operate on. With that out of the way, let's create a function to express the combination of all three. Let's call this tweetsFromMainPage.
If we were to solve this with normal function composition, I would go getPropsForDisplay, which would call removeDeleted, which would call removeInappropriate, which would get all tweets.
However, by doing this, we're preventing any additional composition. We also have to define which data we're operating on here. To solve these problems, we can instead compose these functions together with flow from lodash.
Let's import it. I'm going to go import flow from lodash/fp/flow. Then we have to do it with our three functions as arguments. Let's put these on separate lines for readability.
Now we have a nice, flat, sequential structure, which almost reads like a story, with the business logic hidden away behind these simple functions. What flow does is pass the output from one function as input to the next.
The output of removeInappropriate would get passed as the input to removeDeleted, and the output of removeDeleted would get passed as the input to getPropsForDisplay.
This is very similar to compose. We could use that instead. However, compose works in reverse. It would start with getPropsForDisplay and pass its output to removeDeleted. RemoveDeleted would then pass its output to removeInappropriate, and so on. I find flow more natural, so let's keep using that.
You can see in our composed example here, we don't mention any arguments at all. This is what's known as point-free or tacit code. Apart from being cleaner, this lets you focus on transformations, not what happens to specific elements.
This style is a nice result of building up functionality through carrying and higher-order functions. The real strength is that we can keep composing these individual functions together to form new combinations.
Let's say we want a function which handles both inappropriate and deleted tweets. Let's call this removesHidden. That's going to be a simple call to flow with removeInappropriate and removeDeleted as arguments.
If we wanted to, we could use removesHidden instead of our other two, and that would still work. In our case, I like the explicitness we had, so I'm going to undo that.
We're not actually using our tweetsFromMainPage function yet. Let's comment out our old function, and lets use the same name for a new one. That's just going to be a call to tweetsFromMainPage with tweets as the arguments.
If we save this, we can see tweetsList is still working. It's still removing inappropriate and deleted tweets, but we now have these much smaller composable pieces to keep working with.