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.
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-else
statement 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.
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-else
statement 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.
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:
Code snippet by the author.
Non-functional approach
You can execute the function line by line. For example, you initialize anemployee
object with anid
. Then a series of functions will update theemployee
object and return the updatedemployee
object.
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 mutatingemployee
object. Imagine if you need to add a new function. You could add a new line to call the function and pass theemployee
object 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 theemployee
argument 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.
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 thepersonFunctionsArray
array. You don’t need to reassign the returned value toemployee
and 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 aCalculator
class that contains the calculation operations as methods within that class, which areadd
andsubtract
. 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.
Code snippet by the author. Pass the operation function as an argument to the calculate function.
5. Conversion Calculator
Problem
Convertmeter
into the imperial unitsmile
,yard
,foot
, andinch
. These unit conversion functions take the valuex
inmeter
and return the converted value in an imperial unit formatted with its corresponding symbol.
Non-functional approach
Write a function for each conversion.
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 valuev
and symbols
arguments. It returns a function that takes anx
argument, which is the value that you want to convert.
const createConverter = (v: number, s: string) => (x: number) => `${x * v} ${s}`
Reuse thecreateConverter
function 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.
6. Validate Function Arguments
Problem
You have a function that performs a subtraction operation, given aminuend
and asubtrahend
as its arguments, and returns thedifference
. The returned value should be non-negative numbers only. Therefore, you will need to validate your arguments such that theminuend
should 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.
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.
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.
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.
Functional approach
Usereduce
with thewordCounter
hashmap as the accumulator andword
as the element to process.
Use a spread operator to extract the key and value of thewordCounter
hashmap.
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.
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-infilter
andreduce
functions. Repurpose the samecountWords
function 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 thefilter
function to filter words that have the same letters and length as yourinput
value.
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:
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:
ThehasMinCharacters
andhasValidEmail
functions check the input and return an error message.
Use a combination ofreduce
andmap
to accumulate the error messages returned by testing each input against its corresponding validation criteria. Usefilter
to exclude empty strings.
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 like
map
,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: