Introduction to web Front-end to Practice: Introduction to JavaScrip Functional Programming Principles

Keywords: Javascript Programming less REST Ruby

After learning and using object-oriented programming for a long time, let's take a step back and consider the complexity of the system.

After doing some research, I discovered the concepts of functional programming, such as invariance and pure functions. These concepts allow you to build functions without side effects, making it easier to maintain systems with other advantages.

In this article, we will introduce functional programming and some important concepts in detail through a large number of code examples.

What is functional programming?

Functional programming is a programming paradigm, a style of building computer program structure and elements. It regards calculation as the evaluation of mathematical functions, avoiding the change of state and data.

Pure function

When we want to understand functional programming, the first basic concept we need to know is pure function, but what the hell is pure function?

How do we know if a function is a pure function? Here is a very strict definition:

  • Given the same parameters, the same result (also known as deterministicity) is returned.
  • It does not cause any side effects.

If the same parameters are given, the same results are obtained.

If the same parameter is given, it returns the same result. Imagine that we want to implement a function that calculates the area of a circle.

Instead of a pure function, it takes radius as a parameter and calculates radius radius PI:

let PI = 3.14;

const calculateArea = (radius) => radius * radius * PI;

calculateArea(10); // returns 314.0
web Front-end development learning Q-q-u-n:  784783012 ,Sharing Development Tools, Zero Foundation, Advanced Video Tutorials, I hope novice less detours

Why is this an impure function? The reason is simple because it uses a global object that is not passed to the function as a parameter.

Now, imagine that some mathematicians think that the value of the circumference is actually 42 and modify the value of the global object.

The impurity function yields 10 10442 = 4200. For the same parameter (radius = 10), we get different results.

Fix it:

let PI = 3.14;

const calculateArea = (radius, pi) => radius * radius * pi;

calculateArea(10, PI); // returns 314.0

Now pass the PI value as a parameter to the function so that no external object is introduced.

  • For parameters radius = 10 and PI = 3.14, the same result is always obtained: 314.0.
  • For radius = 10 and PI = 42, the same result is always obtained: 4200

read file

The following function reads the external file. It is not a pure function. The content of the file may vary at any time.

const charactersCounter = (text) => `Character count: ${text.length}`;

function analyzeFile(filename) {
  let fileContent = open(filename);
  return charactersCounter(fileContent);
}

generation of random number

Any function that depends on a random number generator cannot be a pure function.

function yearEndEvaluation() {
  if (Math.random() > 0.5) {
    return "You get a raise!";
  } else {
    return "Better luck next year!";
  }
}

No obvious side effects.

Pure functions do not cause any observable side effects. Examples of visible side effects include modifying global objects or passing parameters by reference.

Now, let's implement a function that accepts an integer and returns the integer by adding 1 and returning.

let counter = 1;

function increaseCounter(value) {
  counter = value + 1;
}

increaseCounter(counter);
console.log(counter); // 2

The impure function receives the value and redistributes counter to increase its value by 1.

Functional programming does not encourage variability. We modify global objects, but what can we do to make them pure functions? Just return the value added by 1.

let counter = 1;

const increaseCounter = (value) => value + 1;

increaseCounter(counter); // 2
console.log(counter); // 1

The pure function increaseCounter returns 2, but the counter value is still the same. The function returns an incremental value without changing the value of the variable.

If we follow these two simple rules, it will be easier to understand our procedures. Now each function is isolated and cannot affect the rest of the system.

Pure functions are stable, consistent and predictable. Given the same parameters, pure functions always return the same results.

We don't need to consider that the same parameter has different results, because it will never happen.

The Benefits of Pure Functions

Pure function code is certainly easier to test and does not require mock anything, so we can unit test pure functions in different contexts:

  • Given a parameter A, the expected function return value B
  • Given a parameter C, the expected function return value D

A simple example is receiving a set of numbers and adding 1 to each number.

let list = [1, 2, 3, 4, 5];

const incrementNumbers = (list) => list.map(number => number + 1);

Receive the numbers array, increment each number with map, and return a new list of incremental numbers.

incrementNumbers(list); // [2, 3, 4, 5, 6]

For input [1, 2, 3, 4, 5], the expected output is [2, 3, 4, 5, 6].

If you are interested in programming, web front-end, want to know about learning, and want to know more about friends in this industry, you can add our front-end learning button qun: 784783012. Whether you are a student or a friend who wants to change careers, I welcome to share dry goods from time to time. A new web front-end learning material of 2019 and a basic introductory course of 0 will be shared with you: We are serious about learning the front-end.

Immutability

Although time changes or does not change, pure function bosses are invariant.

When data is immutable, its state cannot be changed after creation.

We can't change immutable objects. If we want to be hard, we just need to make a deep copy and then operate on it.

In JS, we usually use for loops, where i is a variable for each traversal.

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 traversal, the i and sumOfValue states are changed, but how do we handle variability in 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 code above has a sum function that receives a numerical vector. The function calls itself until the list is empty and exits recursion. For each traversal, we will add the value to the total accumulator.

Using recursion, let's keep the variables unchanged. The list and accumulator variables are not changed. It keeps the same value.

Observation: We can use reduce to achieve this function. This is discussed in the next section on higher-order functions.

It is also common to build the final state of an object. Suppose we have a string that we want to convert into url slug.

In Ruby's object-oriented programming, we can create a class UrlSlugify, which has a slugify method to convert string input to 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"

The command programming method used above is that we first use lowercase letters to indicate what we want to do in each slugify process, then delete useless spaces, and finally replace the remaining spaces with hyphens.

This method changes the input state in the whole process, which obviously does not conform to the concept of pure function.

This side can be optimized by function combination or function chain. In other words, the result of the function will be used as input to 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
web Front-end development learning Q-q-u-n:  784783012 ,Sharing Development Tools, Zero Foundation, Advanced Video Tutorials, I hope novice less detours

The above code mainly does these things:

  • toLowerCase: Converts strings to all lowercase letters.
  • trim: Delete the blanks at both ends of the string.
  • split and join: Replace all matching instances with substitutions in a given string

Reference transparency

Then we implement a square function:

const square = (n) => n * n;

Given the same input, this pure function always has the same output.

square(2); // 4
square(2); // 4
square(2); // 4
// ...

Passing 2 as a parameter to the square function always returns 4. So we can replace square(2) with 4, and our function is transparent.

Basically, if a function always produces the same result for the same input, it can be considered transparent.

With this concept, one of the cool things we can do is remember this function. Suppose there is a function like this.

const sum = (a, b) => a + b;

Call it with these parameters

sum(3, sum(5, 8));

Summ (5, 8) is equal to 13, so you can do something about it:

sum(3, 13);

This expression always gets 16. We can replace the whole expression with a numerical constant and write it down.

Functions are first-class citizens in JS

Functions are very coquettish as first-class citizens in JS. Functions can also be regarded as values and used as data.

  • Refer to it from constants and variables.
  • Pass it as a parameter to other functions.
  • Returns it as a result of other functions.

The idea is to treat functions as values and transfer functions as data. In this way, we can combine different functions to create new functions with new behavior.

If we have a function, it sums up two values and then doubles them, as follows:

const doubleSum = (a, b) => (a + b) * 2;

The difference between the two values is calculated, and then the value is doubled.

const doubleSubtraction = (a, b) => (a - b) * 2;

These functions have similar logic, but the difference lies in the function of operators. If we can treat functions as values and pass them as parameters, we can construct a function that receives operators and uses them inside 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

The f parameter is used to handle a and b, where the sum function and subtraction are passed, and the doubleOperator function is used to combine and create new behavior.

Higher order function

When we discuss higher-order functions, we usually include the following points:

  • Take one or more functions as parameters
  • Returns a function as a result

The doubleOperator function implemented above is a higher order function because it takes an operator function as a parameter and uses it.

The filter s, map s and reduce s we often use are higher-order functions, Look see.

Filter

For a given set, we want to filter based on attributes. The filter function expects a 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 will contain elements in the result set, otherwise it will not.

A simple example is that when we have a set of integers, we just want even numbers.

Imperative form

Use imperative methods to get all even numbers in an array, usually by:

  • Create an empty array evenNumbers
  • Traversal array numbers
  • push even numbers into the 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]
web Front-end development learning Q-q-u-n:  784783012 ,Sharing Development Tools, Zero Foundation, Advanced Video Tutorials, I hope novice less detours

We can also use filter higher-order functions to receive even functions and return an even list:

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]

Commandative practices are usually as follows:

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 approach

For all of the above, we would prefer a more declarative solution to 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]

Using this in smaller's functions may seem strange at first, but it's easy to understand.

The second parameter in the filter function represents the above this, which is the x value.

We can also do this with map method. Imagine that there is a set of information.

let people = [
  { name: "TK", age: 26 },
  { name: "Kaio", age: 10 },
  { name: "Kazumi", age: 30 }
]

We want to filter people over the age of 21, using the filter method

const olderThan21 = person => person.age > 21;
const overAge = people => people.filter(olderThan21);
overAge(people); // [{ name: 'TK', age: 26 }, { name: 'Kazumi', age: 30 }]

map

The main idea of map function is to transform sets.

The map method transforms the set by applying the function to all its elements and constructing a new set based on the returned values.

If we don't want to filter people over 21, what we want to do is show something like this: TK is 26 years old.

Using imperative, we usually do this:

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 will do this:

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 one.

Another interesting HackerRank problem is Update List Problem . We want to update the value of an array with its absolute value.

For example, input [1, 2, 3, - 4, 5] needs output [1, 2, 3, 4, 5], and the absolute value of -4 is 4.

A simple solution is to update the median of each collection locally, which is dangerous.

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 the Math.abs function to convert the value to its absolute value and update it locally.

This approach is not the best solution.

First of all, we learn about invariance in the front end, and we know that invariance makes functions more consistent and predictable. Our idea is to build a new set with all absolute values.

Second, why not use map here to "convert" all data?

My first idea was to test the Math.abs function for 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 into a positive (absolute) value.

Now that you know how to perform an absolute value operation on a value, you can use this function as a parameter to pass to the map function.

Remember that higher-order functions can take functions as parameters and use them? Yes, map functions can do that.

let values = [1, 2, 3, -4, 5];

const updateListMap = (values) => values.map(Math.abs);

updateListMap(values); // [1, 2, 3, 4, 5]

Reduce

The idea of reduce function is to receive a function and a set and return the values created by combining these items.

A common example is to get the total amount of an order.

Suppose you are on a shopping website and have added product 1, product 2, product 3 and product 4 to the shopping cart (order). Now, we need to calculate the total number of shopping carts:

In an imperative way, it facilitates the order list and adds the amount of each product to the total amount.

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

Using reduce, we can construct a function to calculate sum and pass it as a parameter to reduce.

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's shopping Cart, which receives the current Total Amount function sumAmount and the order object that sums them up.

We can also use map to convert shoppingCart into a set of amount s, and then use reduce function 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 receives the product object and returns only the amount value, that is, [10, 30, 20, 60], and then reduce s combines all the items by adding them together.

Examples of three functions

See how each higher-order function works. Here's an example of how to combine these three functions in a simple example.

Speaking of shopping carts, suppose we have this list of products in our order.

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 the total number of books in the shopping cart, you usually do this:

  • Filter type for books
  • Use map to convert shopping carts to amount collections.
  • Add all the items together with reduce.
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
web Front-end development learning Q-q-u-n:  784783012 ,Sharing Development Tools, Zero Foundation, Advanced Video Tutorials, I hope novice less detours

Posted by Braveheartt on Thu, 10 Oct 2019 17:24:29 -0700