Abstract: The understanding of functional programming.
- Author: Front Ash
- Original: in JS Introduction to the basic principles of functional programming
Fundebug authorized reprint, belongs to original author.
After a long time to learn and use object-oriented programming, let's step back and consider the complexity of the system.
After doing some research, I discovered the concept of functional programming, such as invariance and pure function. These concepts enable you to build function without side effects, and therefore easier to maintain system has other advantages.
In this article, we will pass a lot of code examples to explain in detail some of the relevant functional programming and important concepts.
What is functional programming
Functional Programming is a programming paradigm, a computer program to build the structure and style elements, it is considered to calculate the evaluation of mathematical functions and avoids state and a change in the variable data.
Pure function
When we want to understand functional programming, first you need to know basic concept is a pure function, but pure function is what the hell?
We know how pure function is a function of whether there is a very strict definition here?:
- Given the same parameters, the same result is returned (also referred to as deterministic ).
- It does not cause any side effects.
Given the same parameters, the same result is obtained
If given the same parameters, it returns the same result. Imagine that we want to implement a function to calculate the area of a circle.
Function do not pure, received radius
as a parameter, then calculates radius * radius * PI
:
let PI = 3.14;
const calculateArea = (radius) => radius * radius * PI;
calculateArea(10); // returns 314.0
Why is this an impure function? The reason is simple, because it uses a global object is not passed to the function as a parameter.
Now, imagine a number of mathematicians believe that the value of pi is actually 42
and modify the value of the global object.
Impure functions obtain 10 * 10 * 42 = 4200
. For the same parameters ( radius = 10
), we get a different result.
Fix it:
let PI = 3.14;
const calculateArea = (radius, pi) => radius * radius * pi;
calculateArea(10, PI); // returns 314.0
Now the PI
value passed to the function as an argument, so that no external object is introduced.
- For parameters
radius = 10
andPI = 3.14
always will get the same result:314.0
. - For
radius = 10
andPI = 42
always get the same result:4200
Read the file
The following function reads an external file, it is not a pure function, the contents of the file at any time may be different.
const charactersCounter = (text) => `Character count: ${text.length}`;
function analyzeFile(filename) {
let fileContent = open(filename);
return charactersCounter(fileContent);
}
Random Number Generator
Any function depends on the random number generator function can not be pure.
function yearEndEvaluation() {
if (Math.random() > 0.5) {
return "You get a raise!";
} else {
return "Better luck next year!";
}
}
No significant side effects
Pure function does not cause any side effects to be observed. Examples of side effects include visible objects or modify global parameter passed by reference.
Now, we want to achieve a function, and returns an integer that receives the integers plus 1
operation and returns.
let counter = 1;
function increaseCounter(value) {
counter = value + 1;
}
increaseCounter(counter);
console.log(counter); // 2
The impure function receives the value and reassigned counter
to increase its value 1
.
Functional Programming discourages variability. We modify the global object, but how to do to make it pure function? Just go back to increase 1
the value.
let counter = 1;
const increaseCounter = (value) => value + 1;
increaseCounter(counter); // 2
console.log(counter); // 1
Pure function increaseCounter
returns 2
, but the counter
value remains the same. Function returns the incremented value, without changing the value of the variable.
If we follow these two simple rules, it will be easier to understand our program. Now each function is isolated, it can not affect other parts of the system.
Pure function is stable, consistent and predictable. Given the same parameters, a pure function always returns the same result.
We need to consider the results of the same parameters have different circumstances, because it will never happen.
The benefits of pure function
Pure function is certainly easier to test the code, do not need to mock anything, so we can use pure function unit testing different context:
- Given a parameter
A
, a desired function return valueB
- Given a parameter
C
, a desired function return valueD
A simple example is receiving a set of numbers, and for adding the number of each 1
such sand sculpture operation.
let list = [1, 2, 3, 4, 5];
const incrementNumbers = (list) => list.map(number => number + 1);
Receiving numbers
array, use map
increments each digit, and returns a new incremental list of numbers.
incrementNumbers(list); // [2, 3, 4, 5, 6]
For input [1,2,3,4,5]
, expected output is the [2,3,4,5,6]
.
Immutability
Despite the time change or the same, pure function heavyweights are the same.
When data is immutable, its state can not be changed after it is created.
You can not change an immutable object, if you have to come hard, just need to deep copy a copy, then the copy operation.
In JS, we usually use for
cycle, for
each traversing i
a variable variable.
var values = [1, 2, 3, 4, 5];
var sumOfValues = 0;
for (var i = 0; i < values.length; i++) {
sumOfValues += values[i];
}
sumOfValues // 15
For each iteration, all the changes i
and sumOfValue
the state, but how do we deal with the variability in the traversal? The answer is to use recursion .
let list = [1, 2, 3, 4, 5];
let accumulator = 0;
function sum(list, accumulator) {
if (list.length == 0) {
return accumulator;
}
return sum(list.slice(1), accumulator + list[0]);
}
sum(list, accumulator); // 15
list; // [1, 2, 3, 4, 5]
accumulator; // 0
The above code has a sum
function that receives a vector value. Function calls itself, until it list
is empty exit recursion. For each "walk", we will add to the total value accumulator
of.
The use of recursion, remain variables constant. It does not change list
and accumulator
variable. It maintains the same value.
Observation: we can use reduce
to achieve this functionality. This discussion took content in higher-order functions in.
Construction of the final state of the object is also very common. Suppose we have a string, trying to convert this into a string url slug
.
Ruby in object-oriented programming, we can create a class UrlSlugify
, this class has a slugify
method to convert the input string url slug
.
class UrlSlugify
attr_reader :text
def initialize(text)
@text = text
end
def slugify!
text.downcase!
text.strip!
text.gsub!(' ', '-')
end
end
UrlSlugify.new(' I will be a url slug ').slugify! # "i-will-be-a-url-slug"
There imperative programming method used above, first of all we want to use lowercase letters in each slugify
to do the process, and then delete the unused spaces, replace the last remaining spaces with a hyphen.
In this way it changes the input state in the whole process is clearly inconsistent with the concept of pure function.
Here can be optimized by a combination of the function or the function chain. In other words, the result of the function as an input of the next function, without modifying the original input string.
const string = " I will be a url slug ";
const slugify = string =>
string
.toLowerCase()
.trim()
.split(" ")
.join("-");
slugify(string); // i-will-be-a-url-slug
The code is mainly to do these things:
toLowerCase
: Converts a string to all lowercase letters.- trim: Delete blank ends of the string.
split
Andjoin
: replace all instances matching the given string replacement
After the code is deployed may exist BUG can not know in real time, and afterwards in order to solve these BUG, we spent a lot of time debugging log, here for everyone to recommend a way BUG easy to use monitoring tools Fundebug .
Referential transparency
Then implement a square
function:
const square = (n) => n * n;
Set to the same input, this function will always have the same net output.
square(2); // 4
square(2); // 4
square(2); // 4
// ...
Will be 2
passed as a parameter square function always returns 4
. In this way we can put square(2)
into 4
our function is referentially transparent.
Basically, if a function always produces the same result for the same input, then it can be regarded as transparent.
With this concept, we can do a cool thing to remember is this function. Imagine a function
const sum = (a, b) => a + b;
These parameters are used to call it
sum(3, sum(5, 8));
sum(5, 8)
The total is equal 13
, so the show can do the operation:
sum(3, 13);
This expression is always obtained 16
, we can replace the entire expression with a constant value, and write it down.
Function is a citizen of JS
JS as a function of a citizen is the trend, the function can also be considered and used as a value to use the data.
- It quoted from the constant and variable.
- Which was passed as a parameter to other functions.
- As a result of other functions return it.
The idea is to function as the value and function as a data transfer. In this way, we can combine different functions to create a new function with the new behavior.
If we have a function, which two values are summed, then the value is doubled, as shown below:
const doubleSum = (a, b) => (a + b) * 2;
Differencing the corresponding two values, then the value is doubled:
const doubleSubtraction = (a, b) => (a - b) * 2;
These functions have similar logic, but with the difference that the operator functions. If we can be regarded as the function values and passes them as parameters, we can construct a receiver operator functions and functions that use it within the function.
const sum = (a, b) => a + b;
const subtraction = (a, b) => a - b;
const doubleOperator = (f, a, b) => f(a, b) * 2;
doubleOperator(sum, 3, 1); // 8
doubleOperator(subtraction, 3, 1); // 4
f
Parameters and to deal with it a
and b
, where transfer of sum
functions and subtraction
using doubleOperator
functions to combine and create a new behavior.
Higher-order functions
When we talk about higher-order functions, typically include the following:
- One or more parameters as a function of
- Return a result as a function of
To achieve the above doubleOperator
function is a function of a higher order, because it will function as a parameter an operator and use it.
We often use filter
, map
and reduce
are higher-order functions, Look see see.
Filter
For a given set, we want to filter by attributes. filter
A desired function true
or false
value to determine whether an element should be included in the result set.
If the callback expression is true, the filter function contains the elements in the result set, otherwise, it will not.
A simple example is when we have a set of integers, we only want to even.
Imperative
Use imperative way to get an array of all the even usually do:
- Create an empty array
evenNumbers
- Iterate
numbers
- To push the even
evenNumbers
array
var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var evenNumbers = [];
for (var i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 == 0) {
evenNumbers.push(numbers[i]);
}
}
console.log(evenNumbers); // (6) [0, 2, 4, 6, 8, 10]
We can also use filter
higher-order functions to receive even function and returns a list of even number:
const even = n => n % 2 == 0;
const listOfNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
listOfNumbers.filter(even); // [0, 2, 4, 6, 8, 10]
I [Hacker Rank FP] [2] An interesting problem to solve is [Filter Array problem] [3]. The problem is that a given filter array of integers, and outputs only less than a specified value X
of those values.
Imperative approach is usually like this:
var filterArray = function(x, coll) {
var resultArray = [];
for (var i = 0; i < coll.length; i++) {
if (coll[i] < x) {
resultArray.push(coll[i]);
}
}
return resultArray;
}
console.log(filterArray(3, [10, 9, 8, 2, 7, 5, 1, 3, 0])); // (3) [2, 1, 0]
Declarative way
For the above always, we want a more declarative approach to solve this problem as follows:
function smaller(number) {
return number < this;
}
function filterArray(x, listOfNumbers) {
return listOfNumbers.filter(smaller, x);
}
let numbers = [10, 9, 8, 2, 7, 5, 1, 3, 0];
filterArray(3, numbers); // [2, 1, 0]
In smaller
the function used this
, starting to look a bit strange, but it is easy to understand.
filter
Function of the second parameter above this
, i.e. x
values.
We can also use map
the method to do this. Imagine a set of information
let people = [
{ name: "TK", age: 26 },
{ name: "Kaio", age: 10 },
{ name: "Kazumi", age: 30 }
]
We want to filter age
than 21 years of age, by filter
the way
const olderThan21 = person => person.age > 21;
const overAge = people => people.filter(olderThan21);
overAge(people); // [{ name: 'TK', age: 26 }, { name: 'Kazumi', age: 30 }]
map
map
The main function is to convert a collection of ideas.
map
The method by which the function is applied to all of the elements and building a new set of set based on the value returned by the conversion.
If we do not want to filter people older than 21, we want to do is show like this:TK is 26 years old.
Use imperative, we usually do:
var people = [
{ name: "TK", age: 26 },
{ name: "Kaio", age: 10 },
{ name: "Kazumi", age: 30 }
];
var peopleSentences = [];
for (var i = 0; i < people.length; i++) {
var sentence = people[i].name + " is " + people[i].age + " years old";
peopleSentences.push(sentence);
}
console.log(peopleSentences); // ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']
Declarative do:
const makeSentence = (person) => `${person.name} is ${person.age} years old`;
const peopleSentences = (people) => people.map(makeSentence);
peopleSentences(people);
// ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']
The whole idea is to convert a given array into a new array.
Another interesting question is HackerRank [update the list of questions] [3]. We want to use the absolute value of an array to update its value.
For example, the input [1,2,3,- 4,5]
required output [1,2,3,4,5]
, -4
the absolute value 4
.
A simple solution is to place each updated set of values, a very dangerous practice
var values = [1, 2, 3, -4, 5];
for (var i = 0; i < values.length; i++) {
values[i] = Math.abs(values[i]);
}
console.log(values); // [1, 2, 3, 4, 5]
We use Math.abs function converts the value of its absolute value and in-place update.
This is not the best way to do solution.
First of all, we learned the front invariance, know immutability make more consistent and predictable function, our idea was to create a new collection has all absolute.
Secondly, why not here to use map
all the data to "convert"
My first idea was to test Math.abs
the function handles only one value.
Math.abs(-1); // 1
Math.abs(1); // 1
Math.abs(-2); // 2
Math.abs(2); // 2
We want to convert each value to a positive value (absolute value).
Now know how to perform an absolute value operation on a value, this function may be used as a parameter passed to map
a function.
Remember the higher-order functions can receive and use it as a parameter to a function? Yes, map
the function can do this
let values = [1, 2, 3, -4, 5];
const updateListMap = (values) => values.map(Math.abs);
updateListMap(values); // [1, 2, 3, 4, 5]
Reduce
reduce
The idea of this is to receive a function and a set, and returns the value created by the combination of these items.
A common example is the total amount of acquired orders.
Suppose you are in a shopping site, we have 1, 2 product, product and product 3 4 Add to cart product (order) in. Now, we have to calculate the total number of the shopping cart:
To imperative way is a convenient list of orders and the amount of each product and the total amount of the sum.
var orders = [
{ productTitle: "Product 1", amount: 10 },
{ productTitle: "Product 2", amount: 30 },
{ productTitle: "Product 3", amount: 20 },
{ productTitle: "Product 4", amount: 60 }
];
var totalAmount = 0;
for (var i = 0; i < orders.length; i++) {
totalAmount += orders[i].amount;
}
console.log(totalAmount); // 120
Use reduce
, we can construct a function to calculate the amount of processing sum
and passed as a parameter to reduce
a function.
let shoppingCart = [
{ productTitle: "Product 1", amount: 10 },
{ productTitle: "Product 2", amount: 30 },
{ productTitle: "Product 3", amount: 20 },
{ productTitle: "Product 4", amount: 60 }
];
const sumAmount = (currentTotalAmount, order) => currentTotalAmount + order.amount;
const getTotalAmount = (shoppingCart) => shoppingCart.reduce(sumAmount, 0);
getTotalAmount(shoppingCart); // 120
Here shoppingCart
, the current reception currentTotalAmount
function sumAmount
, and summing them to order
objects.
It may also be used map
to shoppingCart
convert a amount
set, and then use the reduce
functions and sumAmount
function.
const getAmount = (order) => order.amount;
const sumAmount = (acc, amount) => acc + amount;
function getTotalAmount(shoppingCart) {
return shoppingCart
.map(getAmount)
.reduce(sumAmount, 0);
}
getTotalAmount(shoppingCart); // 120
getAmount
Receiving product
object and only the return amount
value, i.e. [10,30,20,60]
, then, reduce
by adding all the items together.
Examples of three functions
Read the works of each higher-order functions. Here is an example to show you, explain how to combine these three functions in a simple example.
When it comes to shopping carts, suppose we have this product in order list
let shoppingCart = [
{ productTitle: "Functional Programming", type: "books", amount: 10 },
{ productTitle: "Kindle", type: "eletronics", amount: 30 },
{ productTitle: "Shoes", type: "fashion", amount: 20 },
{ productTitle: "Clean Code", type: "books", amount: 60 }
]
If you want to shop with the car type books
the total, usually do:
- Type of filter for the books
- Use
map
to convert the shopping cart toamount
the collection. - With
reduce
all the items together.
let shoppingCart = [
{ productTitle: "Functional Programming", type: "books", amount: 10 },
{ productTitle: "Kindle", type: "eletronics", amount: 30 },
{ productTitle: "Shoes", type: "fashion", amount: 20 },
{ productTitle: "Clean Code", type: "books", amount: 60 }
]
const byBooks = (order) => order.type == "books";
const getAmount = (order) => order.amount;
const sumAmount = (acc, amount) => acc + amount;
function getTotalAmount(shoppingCart) {
return shoppingCart
.filter(byBooks)
.map(getAmount)
.reduce(sumAmount, 0);
}
getTotalAmount(shoppingCart); // 70
After the code is deployed may exist BUG can not know in real time, and afterwards in order to solve these BUG, we spent a lot of time debugging log, here for everyone to recommend a way BUG easy to use monitoring tools Fundebug .
About Fundebug
Fundebug focus on JavaScript, applets micro-channel, micro-channel games, Alipay small program, React Native, Node.js and Java applications in real-time online monitoring BUG. Since 2016, two-eleven formally launched, Fundebug handled a total of 2 billion + error event, paying customers have Sunshine Insurance, walnut programming, lychee FM, head of the 1-to-1, micro pulse, the Youth League and many other community brands. Welcome to Free Trial!