Contents

9 Useful Functional Programming Solutions You Can Learn (in TypeScript and Node)

9 Useful Functional Programming Solutions You Can Learn (in TypeScript and Node)

Coding examples that are easy for web developers to understand.

https://cdn-images-1.medium.com/max/800/1*n-051M-qWyqkiIRUVK1dEw.jpeg

Source: Unsplash

For Portuguese readers: Thanks to Alison Miazaki who translated my story into Portuguese: 9 Soluções práticas em programação funcional para você saber (em TypeScript e Node).

We’ve all been there as software developers: We’re writing or reviewing a snippet of code. Then out of nowhere, our intuition tells us that we could further simplify our code. Or, if we are familiar with imperative programming, there seems to be a “functional” way to solve the problem. The “functional” solution is at our fingertips, but we somehow can’t write it down.

I wrote this article to help unblock myself from this type of situation. After all, we can get inspiration from anywhere. It’s a matter of finding the right hints to get to that solution. The closer the hint is to the solution we are looking for, the better.

We’ll go through a set of common coding tasks ordered by the simplicity of the problem. I used TypeScript with Node 10 to test each solution.


1. Mocking in Unit Tests

Problem

When testing an async function, you want to use mock data when running your tests locally.

Non-functional approach

Use anif-elsestatement to determine which function to run, whether async or mocked.

Functional approach

Assign the async and mocked function to their variables. Use a ternary operator to determine which function to use based on the current environment.

View Code on GitHub Gist

Code snippet by the author.


2. A/B Testing or Experimentation

Problem

You want to show two or more different variants of your web application: variants A and B.

Non-functional approach

Use anif-elsestatement to determine which variant to show.

Functional approach

Assign your display functions to their variables. Create an object to map the function variables to their corresponding variant. Use the variant as the object key and function variables as the object value. Display the variant using the object hashmap.

View Code on GitHub Gist

Code snippet by the author.


3. Execute a Series of Functions to a Piece of Data

Problem

You have many functions that take an object, execute a series of updates to that object, and return an updated or transformed object:

View Code on GitHub Gist

Code snippet by the author.

Non-functional approach

You can execute the function line by line. For example, you initialize anemployeeobject with anid. Then a series of functions will update theemployeeobject and return the updatedemployeeobject.

View Code on GitHub Gist

Code snippet by the author. The code executes the functions starting line 17.

The code above gets the job done. You are applying each function line by line to the mutatingemployeeobject. Imagine if you need to add a new function. You could add a new line to call the function and pass theemployeeobject as an argument. Not bad, but we can do better.

Functional approach

The functional way is to assign the functions in an array, iterate through that array usingforEach, and apply each function to the argument. Note that theemployeeargument to pass after applying the first function in the array will be the returned value of the previous function. You use the functions in the same order in which they are positioned in the array.

View Code on GitHub Gist

Code snippet by the author. Line 17 assigns the functions in an array.

If you need to add another function to execute, add another entry in thepersonFunctionsArrayarray. You don’t need to reassign the returned value toemployeeand pass it as an argument to the next function.


4. Simple Calculator

Problem

You want to implement a simple calculator that accepts two numbers.

Non-functional approach

The object-oriented approach is to create aCalculatorclass that contains the calculation operations as methods within that class, which areaddandsubtract. If you want to add more operations, you will update the class to add each operation’s corresponding method. Another way is to write an individual function for each operation. For instance, implement thecalculateSum()andcalculateDifference()functions.

Functional approach

Create acalculate()function that accepts an operation function (e.g.add()) as an argument that will perform the calculation you want. The function that calculates is also self-contained and you can use it without it being passed as an argument.

View Code on GitHub Gist

Code snippet by the author. Pass the operation function as an argument to the calculate function.


5. Conversion Calculator

Problem

Convertmeterinto the imperial unitsmile,yard,foot, andinch. These unit conversion functions take the valuexinmeterand return the converted value in an imperial unit formatted with its corresponding symbol.

Non-functional approach

Write a function for each conversion.

View Code on GitHub Gist

Code snippet by the author. Conversion starts on line 6.

The duplicated code in the conversion functions is evident in the example above. Multiplication and string formatting in each function is repetitive.

Functional approach

Try to solve this by creating a function that will handle the different conversions while maximizing code reuse between functions with slightly different behaviors.

ThecreateConverter()function below accepts the conversion valuevand symbolsarguments. It returns a function that takes anxargument, which is the value that you want to convert.

const createConverter = (v: number, s: string) => (x: number) => `${x * v} ${s}`

Reuse thecreateConverterfunction above to form the conversion functions.

const meterToMile = createConverter(0.0006213689, 'mi')
console.log(meterToMile(1000))

You are maximizing reuse while keeping your code maintainable.

View Code on GitHub Gist


6. Validate Function Arguments

Problem

You have a function that performs a subtraction operation, given aminuendand asubtrahendas its arguments, and returns thedifference. The returned value should be non-negative numbers only. Therefore, you will need to validate your arguments such that theminuendshould always be greater than or equal to thesubtrahend:minuend >= subtrahend.

const minus = (minuend: number, subtrahend: number): number => minuend - subtrahend

According toClass Ace:

  • minuend— This is the biggest number, or the whole, from which a part will be taken away.

-subtrahend— This is the part that is taken away from theminuend.

  • Difference — This is the part that is left over after subtraction.”

Non-functional approach

The typical way to validate the arguments would be to add a conditional early return or exit.

View Code on GitHub Gist

The code above works. However, the function is doing more than one thing. Instead of only returning the difference, it also validates the arguments. It violates the single-responsibility principle inSOLID.

Functional approach

First, create a separate function for validating the arguments.

View Code on GitHub Gist

const minus = (minuend: number, subtrahend: number): number => minuend - subtrahend

Create theminusPositiveDifference()function that calls theminuendGreaterThanSubtrahend()validation function with theminus()function as its argument. ThisminusPositiveDifference()will then be called to ensure that your arguments are validated first before performing the subtraction.

const minusPositiveDifference = minuendGreaterThanSubtrahend(minus)

Each function is doing one thing while solving the problem.

View Code on GitHub Gist

Code snippet by the author. A functional approach to validate arguments while keeping the code compliant with the single-responsibility principle.


7. Word Counter

Problem

You have a string of words or a poem assigned to a variable. You want to count the number of times a word has occurred in the string. This problem is similar to having an array and wanting to count the number of times certain elements appear in the array.

const poemInput: string = `Hold fast to dreamsFor if dreams dieLife is a broken-winged birdThat cannot flyHold fast to dreamsFor when dreams goLife is a barren fieldFrozen with snow`.replace(/(\r\n|\n|\r)/gm, ' ').toLowerCase()

To simplify the string, replace the line breaks with spaces and transform them to lowercase.

Non-functional approach

Use a hashmap to keep track of the number of occurrences of the words.

View Code on GitHub Gist

Functional approach

Usereducewith thewordCounterhashmap as the accumulator andwordas the element to process.

Use a spread operator to extract the key and value of thewordCounterhashmap.

Use a ternary operator to check if the word is already in the hashmap. If it is in the hashmap, increment the value. Otherwise, assign1, which means that the word has appeared for the first time.

View Code on GitHub Gist

Code snippet by the author. The functional solution is more compact and easier to read if you are already familiar with functional programming.


8: Finding Anagrams

Problem

“An anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.” —Wikipedia

Given a word, you want to find its anagram(s) from an array of words provided.

For example, you have this array of words with its corresponding anagram in the comment.

const words: Array<string> = [ 'rat', // tar 'car', // arc 'below', // elbow 'taste', // state 'cried', // cider 'study', // dusty 'thing', // night 'chin', // inch 'grab', // brag 'act', // cat 'robed', // bored 'vase', // save 'glean', // angel 'desserts', // stressed]

The input can be one of the possible anagrams.

const input: string = 'save'

Non-functional approach

There are many examples of non-functional solutions to this problem online, so I will not cover them here.

Functional approach

You can solve this with a combination of the JavaScript built-infilterandreducefunctions. Repurpose the samecountWordsfunction in example #7 that usesreduce, but instead of counting words, you are now counting letters in a word.

const countLetters = (word: Array<string>) => word.reduce(
    (letterCounter: Map<string, number>, letter: string) => ({
        ...letterCounter,
        [letter]: letterCounter[letter] ? letterCounter[letter] + 1 : 1,
    }),
    new Map<string, number>()
);

Use thefilterfunction to filter words that have the same letters and length as yourinputvalue.

const findAnagrams = (word: string, words: Array<string>): Array<string> => {
    return words
        .filter(entry => hasSameLetterCount(word, entry))
        .filter(anagram => anagram !== word);
}

The final solution will be the following:

View Code on GitHub Gist

Code snippet by the author. The solution uses a combination of filter() and reduce() functions.


9. Form Validation

Problem

You have a form that accepts input from the user, and you need to validate the input. Some simple validation criteria can include a username that is at least three characters long or the user needing to enter a valid email address.

const currentInputValues: Object = { username: 'ar', email: 'me@',}

Non-functional approach

There are many examples of non-functional solutions to this problem online, so I will not cover them here.

Functional approach

Start with a hashmap of input criteria where the key is the field and the values are arrays of input criteria. For example:

View Code on GitHub Gist

ThehasMinCharactersandhasValidEmailfunctions check the input and return an error message.

View Code on GitHub Gist

Use a combination ofreduceandmapto accumulate the error messages returned by testing each input against its corresponding validation criteria. Usefilterto exclude empty strings.

View Code on GitHub Gist

Code snippet by the author. The solution uses reduce, map and filter.


Conclusion

To recap:

  • Examples 1, 2, and 3 are good examples of how you can treat functions as data.
  • Example 4 shows that you can use functions as arguments.
  • Example 5 shows that you can use functions as return values.
  • Example 6 shows how you can use higher-order functions.
  • Examples 7–9 use a combination of the previous concepts plus built-in functions likemap,reduce, andfilter.

I’m not suggesting that you should always use the functional solutions outlined above when you encounter the same problems. Sometimes it’s a matter of preference or coding style. It will also help if you consider other factors before writing down your solution, like your existing code base, team members, and coding guidelines.

I hope your journey with functional programming has been fun so far!

You can find the code examples used in this article below: