What exactly is functional programming JavaScript?

Abstract: The understanding 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 radiusas 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 42and 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 PIvalue passed to the function as an argument, so that no external object is introduced.

  • For parameters radius = 10and PI = 3.14always will get the same result: 314.0.
  • For radius = 10and PI = 42always 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 1operation 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 counterto 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 1the value.

    let counter = 1;
    
    const increaseCounter = (value) => value + 1;
    
    increaseCounter(counter); // 2
    console.log(counter); // 1

Pure function increaseCounterreturns 2, but the countervalue 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 1such sand sculpture operation.

    let list = [1, 2, 3, 4, 5];
    
    const incrementNumbers = (list) => list.map(number => number + 1);

Receiving numbersarray, use mapincrements 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 forcycle, foreach traversing ia 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 iand sumOfValuethe 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 sumfunction that receives a vector value. Function calls itself, until it listis empty exit recursion. For each "walk", we will add to the total value accumulatorof.

The use of recursion, remain variables constant. It does not change listand accumulatorvariable. It maintains the same value.

Observation: we can use reduceto 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 slugifymethod 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 slugifyto 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.
  • splitAnd join: 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 squarefunction:

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 2passed as a parameter square function always returns 4. In this way we can put square(2)into 4our 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

fParameters and to deal with it aand b, where transfer of sumfunctions and subtractionusing doubleOperatorfunctions 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 doubleOperatorfunction is a function of a higher order, because it will function as a parameter an operator and use it.

We often use filter, mapand reduceare higher-order functions, Look see see.

Filter

For a given set, we want to filter by attributes. filterA desired function trueor falsevalue 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 arrayevenNumbers
  • Iterate numbers
  • To push the even evenNumbersarray
    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 filterhigher-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 Xof 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 smallerthe function used this, starting to look a bit strange, but it is easy to understand.

filterFunction of the second parameter above this, i.e. xvalues.

We can also use mapthe 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 agethan 21 years of age, by filterthe way

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

map

mapThe main function is to convert a collection of ideas.

mapThe 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], -4the 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 mapall the data to "convert"

My first idea was to test Math.absthe 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 mapa function.

Remember the higher-order functions can receive and use it as a parameter to a function? Yes, mapthe 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

reduceThe 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 sumand passed as a parameter to reducea 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 currentTotalAmountfunction sumAmount, and summing them to orderobjects.

It may also be used mapto shoppingCartconvert a amountset, and then use the reducefunctions and sumAmountfunction.

    const getAmount = (order) => order.amount;
    const sumAmount = (acc, amount) => acc + amount;

    function getTotalAmount(shoppingCart) {
      return shoppingCart
        .map(getAmount)
        .reduce(sumAmount, 0);
    }
    
    getTotalAmount(shoppingCart); // 120

getAmountReceiving productobject and only the return amountvalue, i.e. [10,30,20,60], then, reduceby 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 booksthe total, usually do:

  • Type of filter for the books
  • Use mapto convert the shopping cart to amountthe collection.
  • With reduceall 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!

Guess you like

Origin www.cnblogs.com/fundebug/p/javascript-functional-programing-introduction.html