Refactor a function to be more functional
Paweł Kowalski
Posted on December 1, 2019
Functional paradigm is kind of mystical knowledge for me, as it involves a lot of hard words and concepts from math. But once in a while I read or watch materials about it hoping that I will understand more. This has been going for years now.
Some concepts are easy, but without a good, iterative example it is still hard to incorporate into everyday developer's life. Last night, I think I found a good example, that would help me a lot with understanding some of the basics of composition if someone showed me something like I'm about to show you. I hope you find it hopeful in your journey to writing good and easy to maintain code.
Function that will serve me as an example will take a string and return a number of unique letters in that string.
Prepare the test case
I always do that first, because I prototype in RunJS. I find it the easiest and quickest that way. There is also Scratchpad in Firefox, but RunJS has live code evaluation.
const input = 'Hi, my name is Pawel!';
const expected = 11;
const count = (string) => '';
console.log(expected === count(input));
Make it work
Now let's implement the first version that will return correct result.
const count = string => {
const array = Array.from(string);
const onlyLetters = array.filter(char => char.match(/[a-zA-Z]/));
const lowercase = onlyLetters.map(char => char.toLowerCase());
const unique = new Set(lowercase);
const output = unique.size;
return output;
}
It is pretty verbose, line by line it is pretty easy to understand what is going on. Probably the biggest downside is that it uses a lot of assignments.
Note: Im using Set
to make array values unique.
Make it better
Let me walk you through some of the variants I came up with when trying to find the optimal solution.
A little bit of chaining
const count = string => {
const array = Array.from(string)
.filter(char => char.match(/[a-zA-Z]/))
.map(char => char.toLowerCase());
return new Set(array).size;
}
Now we used less constants and used the fact that Array
can chain methods like filter
, and map
. This is a first step to what is coming next.
"The Oneliner"
const count = string => {
return new Set(
Array.from(string)
.filter(char => char.match(/[a-zA-Z]/))
.map(char => char.toLowerCase())
).size;
}
In general I consider chaining a very nice way of making things prettier. But when your goal is only to make code shorter, usually readability hurts, like in this case. I wouldn't consider this a improvement compared to the previous version.
But its fun to know it could be done, and shows how important indentation is in those cases where you decide to go with it.
One big chain
const count = string => {
return [string]
.map(string => Array.from(string))
.map(array => array.filter(char => char.match(/[a-zA-Z]/)))
.map(array => array.map(char => char.toLowerCase()))
.map(array => new Set(array))
.map(set => set.size)[0]
}
This stage takes advantage of the same chaining property of Array
from second version, but this time it takes things to the next level, literally. It puts input immediately into an array and uses map
for composition to do the necessary operations.
More composition
const onlySmallLetters = string => {
return Array.from(string)
.filter(char => char.match(/[a-zA-Z]/))
.map(char => char.toLowerCase())
}
const count = string => {
return [string]
.map(onlySmallLetters)
.map(array => new Set(array))
.map(set => set.size)[0]
}
Lastly, not the most condensed version, but this implementation adds another dimension.
You might want to reuse onlySmallLetters
function somewhere else - this would be called composition - compose functions from smaller functions. Those smaller functions are easier to test, understand and debug.
And this is where I landed at the end of my journey with this challenge that I found when learning basics of python.
Mixing types, accepting a string and returning an array might not be predictable, thats why, as I understand, functional programming has specific constructs to make it easier and more predictable for everybody knowing the paradigm.
Dive deeper into those mystical parts of functional programming in JS by watching "Professor Frisby Introduces Composable Functional JavaScript" by Brian Lonsdorf.
Posted on December 1, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.