Functional Programming in ES6
Bài đăng này đã không được cập nhật trong 8 năm
Functional Programming
Functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.
Why this paradigm change
Functional languages use a different paradigm than imperative and object oriented languages. They use side effect free functions as a basic building block in the language. This enables lots of things and makes a lot of things more difficult (or in most cases different from what people are used to)
One of the biggest advantages with functional programming is that the order of execution of side effect free functions is not important. For example in erlang this is used to enable concurrency in a very transparent way. And because functions in functional languages behave very similar to mathematical functions it's easy to translate those into functional languages. In some cases this can make code more readable.
Traditionally one of the big disadvantages of functional programming was also the lack of side effects. It's very difficult to write useful software without IO, but IO is hard to implement without side-effects in functions. So most people never got more out of functional programming than calculating a single output from a single input. In modern mixed paradigm languages like F# or scala this is easier.
Lots of modern languages have elements from functional programming languages. C# 3.0 has a lot functional programming features and you can do functional programming in python too. I think the reasons for the popularity of functional programming is mostly because of two reasons. Concurrency is getting a real problem in normal programming because we're getting more and more multiprocessor computers. And the languages are getting more accessible.
Before you read further more...
Watch this Intro to Functional Programming video by Anjana Vakil She explains everything in a much better way than my blog, which is obviously very helpful for beginners. After you watch the talk, you continue the examples provided here.
How to write functional code
-
Avoid side-effects, Use pure functions : Make sure your functions does not do more than what its supposed to do. Strictly follow the 'single responisbility' principle. As an example, getEvens() function's only responsibility should be filter and return the even numbers, not printing them on the screen. Functions that follow the SRP are known as pure functions.
-
Stop manual iteration : Rather than manually iterating over an array, use the provided hihger order functions 'Map, Reduce, Filter, Some, Every'
-
Avoid mutability : Stop mutating your state. Your state should be immutable
Higher Order Functions
We know functions in JavaScript are nothing but objects. Does this mean
- functions can be passed around as arguments to other functions? And
- can one function return another function?
The answer to both questions is Yes.
Such functions which taking in other functions are arguments and/or return functions are typically called higher order functions.
In the example below, we will define a higher order function forEach. This function takes in two arguments: a list and an action. The first argument list is an array containing containing objects and the second argument action is a function that will be invoked for each item in list.
let forEach = (list, action) => {
for (let i = 0; i < list.length; i++) {
action(list[i]);
}
};
let logItem = item => console.log(item);
let listOfThings = ["soap", "candle", "deer", "wine", "bread"];
let anotherListOfThings = ["grapes", "apples", "beer", "pizza"];
forEach(listOfThings, logItem);
forEach(
anotherListOfThings,
(item => console.log(`${item}'s leangth is ${item.length}'`) )
);
Map
Map works exactly how it worked on other languages. It creates a new array with the results of calling a provided function on every element in this array.
Lets discuss it with a simple example, 'double the array numbers'. With our imperative programming minds, we would've written it step by step, like this:
var numbers = [1, 2, 3, 4];
var newNumbers = [];
for(var i = 0; i < numbers.length; i++) {
newNumbers[i] = numbers[i] * 2;
}
let numbers = [1, 2, 3, 4]; // let works similar to var
let newNumbers = numbers.map( x => x*2 );
It makes life so much simpler. Rather than telling the computer what to do, you ask it What you want. How the computer processes the data should not be our concern as long as you are getting the correct response (also, it works as fast as imperatively written code).
Now lets imagine, we have another array starks
let starks = [
{
name: "Edard",
isAlive: false,
companion: null,
army: 10000
},
{
name: "Rob",
isAlive: false,
companion: {name: "Grey Wind", isAlive: false, breed: "Dire Wolf"},
army: 30000
},
{
name: "Jon",
isAlive: true,
companion: {name: "Ghost", isAlive: true, breed: "Dire Wolf"},
army: 1000
},
{
name: "Bran",
isAlive: true,
companion: {name: "Summer", isAlive: false, breed: "Dire Wolf"},
army: 2
},
{
name: "Arya",
isAlive: true,
companion: {name: "Nymeria", isAlive: false, breed: "Alpha Dire Wolf"},
army: 0
},
{
name: "Tony",
isAlive: true,
companion: {name: "Jarvis", isAlive: false},
army: 5
}
];
So, if we need the name of all the Starks, we'd write
let names = starks.map( x => x.names);
Easy as that.
One thing to remember, map (and other functions we're about to discuss) DOES NOT mutate the initial array.
Parameters
[...].map(function(element, index, array) {
...
}, thisArg);
index: index in each traversal, moving from left to right
array: original array invoking the method
thisArg: (Optional) object that will be referred to as this in callback
Filter
Like map() method, filter works on each element of the array and returns a new array as output only containing the elements that passed the evaluation.
How it works: filter() calls a provided callback function once for each element in an array, and constructs a new array of all the values for which callback returns a value that coerces to true. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values. Array elements which do not pass the callback test are simply skipped, and are not included in the new array.
Use it when: You want to remove unwanted elements based on a condition.
Example:
Return the alive Stark members:
starks.filter( x => x.isAlive);
All the starks with alive companion.
starks.filter(x => x.companion && !x.companion.isAlive)
Like map(), filter does not mutate the array. And has a similar structure
[...].filter(function(element, index, array) {
...
}, thisArg);
Reduce
Reduce is another useful higher order function , which generally is used to flatten the array it is called from. reduce() applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.
Example, Counmbine all the men fought under stark banner
starks.reduce((armies, stark) => (armies+stark.army), 0);
How reduce works
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, currentIndex, array) {
return previousValue + currentValue;
});
Another example (from fun fun function, we have a file containing customer information in form of tab-separated.
Josim Khan toast-biscuit 80 2
Josim Khan blender 200 1
Josim Khan knife 10 4
Sodrul Pasha salted-biscuit 80 20
Sodrul Pasha blender 200 1
Sodrul Pasha heater 1000 2
We need to convert this tsv to a json object.
{
'Josim Khan': [
{'name': 'toast-biscuit', 'price': '80', 'quantity': '2'},
{'name': 'blender', 'price': '200', 'quantity': '1'},
{'name': 'knife', 'price': '10', 'quantity': '4'},
],
'Sodrul Pasha': [
{'name': 'salted-biscuit', 'price': '80', 'quantity': '20'},
{'name': 'blender', 'price': '200', 'quantity': '1'},
{'name': 'heater', 'price': '1000', 'quantity': '2'},
]
}
We can do this by
import fs from 'fs';
let output = fs.readFileSync('data.txt', 'utf8')
.trim()
.split('\n')
.map(line => line.split(`\t`))
.reduce(
(customers, line) => {
customers[line[0]] = customers[line[0]] || [];
customers[line[0]].push({
name: line[1],
price: line[2],
quantity: line[3]
})
}
)
Some
When called on an array, some() will return a boolean value as output. It runs the provided function on each elements, and will return true if it finds any of the element passing the evaluation.
Parameters
[...].some(function(element, index, array) {
...
}, thisArg);
So, if we need to find if any of the starks are alive, we need to called
starks.some( x => x.isAlive )
Every
This is the exact opposite of how the some() function works. It returns a truthy output as long as all the element passes the evaluation.
Parameters
[...].every(function(element, index, array) {
...
}, thisArg);
Example, Are the starks dead yet?
starks.every( x => x.isAlive)
Study Materials
Fun Fun Function's awesome tutorial on Functional Programming
All rights reserved