DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Refactor a function to be more functional
PaweΕ‚ Kowalski
PaweΕ‚ Kowalski

Posted on

Refactor a function to be more functional

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));
Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
t0nyba11 profile image
Tony B • Edited on

The trouble with attempting to write 'functional' code in javascript, is that it is EXTREMELY slow to create all those extra copies and iterate them all. The language just isn't designed to optimize away any of it.

Regular code like this would run ten times as fast, use far less memory, and to me, is just as readable. :)

const countUniqueCharacters = (s, counter = 0) => {
  const track = [];
  for (let i = 0, len = s.length; i < len; i++) { 
    const c = s.charCodeAt(i) | 32;               // convert to lower case
    if (c >= 97 && c <= 122 && !track[c])         // if a-z and not seen yet
      track[c] = ++counter                        // set seen, increment counter
  }
  return counter;
};
Enter fullscreen mode Exit fullscreen mode

πŸ€” Did you know?

🌚 You can turn on dark mode in Settings