JavaScript-Functional programming

Source: Internet
Author: User

In recent times, functional programming has been active again. Functional programming is a programming paradigm that treats computer operations as mathematical function calculations and avoids the use of program state and variable data. Functional programming emphasizes the results of program execution rather than the process of execution, advocating the use of a number of simple execution units to keep the computational results progressive, to derive complex operations layer by level, rather than to design a complex execution process.

Today, I tried to use JS to comb some knowledge about functional programming. The code style in this article uses standard, using ES6 syntax.

Functional Programming Preliminary examination

First, let's look at an example that calculates the and of an array element.

We can do this quickly with an imperative code like this:

const arr = [1234]let0for (let0, len = arr.length; i < len; i++) {  sum += arr[i]}

Using the reduce method provided by the ES5 species, the code is as follows:

const arr = [1234]let sum = arr.reduce((x, a) => x + a)

As you can see, the functional code of reduce is completely different from the way and angle that the preceding imperative code solves the problem. The imperative is to care about all the processes, how to traverse them, and how to organize the resulting data. The logic that we really care about is the process of the anonymous function in the parameters of the reduce, because it encapsulates the process of traversing, manipulating, and organizing the resulting data into an array.

Functional Programming feature function is a class citizen

A function is a class citizen, meaning that a function, like any other data type, can be assigned to another variable, passed as a parameter, or as the return value of another function.

For example, in the following code, log is a function, as an argument to another function.

function log (str) {  constnewDate()  console.log(‘[‘‘:‘‘:‘‘]:‘, str)}[‘debug‘‘info‘‘warn‘].forEach(log)
Pure function

Pure functions are such a class of functions that the same input will always get the same output, without any observable side effects. Functional programming emphasizes that there is no "side-effect", meaning that the function remains independent, returns the same result each time, has no other behavior, and, in particular, cannot modify the value of an external variable.

// 纯函数function increase (x) {  return1}// 不纯函数let2function increaseX (x) {  return x + seed}

From the above code, we can clearly see that increasex relies on the external variable seed, which means that the return value of Increasex depends on the state of the system, which can cause some bugs to become elusive.

The pursuit of pure functions is still due to some of the benefits of pure functions, such as the ability to cache based on input, pure function is completely self-sufficient, portability is strong, make testing easier, can run in parallel without worrying about entering a competitive state.

Immutable data

There are altogether 6 primitive types of JavaScript, boolean,null,undefined,number string and symbol (ES6 new). Except for these primitive types, the others are object, and object is mutable.

const‘wen‘}Object25})console// => {name: ‘wen‘, age: 25}

However, we can use a function as the boundary of a state mutation, completely isolated from the changes in the external code. For example, we use merge to hide the variability of the person.

 function   Span class= "Hljs-title" >merge  (...args )  { let  obj = {} args.unshift (obj) return  object . Assign.apply (null , args)} const  person = {name:  ' Wen ' }const  wen = merge (person, {age: 25 }, {telephone: 123456 }" console . log (person); //+ = {name: ' Wen '}  console . log (wen); //+ = {name: ' Wen ', age:25, telephone:123456}   

Object.freeze () is provided in ES5 to freeze an object, but it is shallow, meaning that freezing only occurs at the topmost level. If you want to freeze an object deeply, you need to use a recursive data structure.

function deepFreeze (obj) {  letObject.getOwnPropertyNames(obj)  propNames.forEach((name) => {    let prop = obj[name]    if (typeof‘object‘null) {      deepFreeze(prop)    }  })  returnObject.freeze(obj)}

In addition, Immutable.js is also a solution to JavaScript immutable data.

Lazy evaluation

Lazy evaluation refers to delaying the solution of the expression as much as possible, it does not pre-calculate all the elements, but only when used to calculate. Doing so can make expensive calculations necessary to perform, while caching values to produce more efficient code. Derived from the lazy evaluation characteristics, lazy.js has better performance than Lodash and underscore.

The generator and iterator features are provided in the ES6, and we use these two features to achieve lazy evaluation.

function * calulate () {  yield12  yield34  yield56}let cal = calulate()console// 2console// 12console// 30

The expression after yield is not executed immediately and will not be executed until next is moved to this sentence.

Tail call optimization

The greatest benefit of recursion is simplifying the code, which can be described in a very simple code with a complex problem. However, if the recursion is very deep, the stack will be overwhelmed, resulting in a sharp decline in performance. This requires the use of tail-call optimizations, which reuse the stack each time it is recursive, which can improve performance, which, of course, requires language or compiler support.

Tail call optimization is provided in ES6, which must also be remembered in strict mode when using it:

    • The tail call function cannot reference other variables in the current stack, that is, non-closures;
    • After a tail call is returned, there is no need to process what you have to do (such as n*factorial (n-1) multiplied below);
    • The return value of the last Call function is the value of the function and does not need to be evaluated.

Here we take factorial as an example:

function factorial (n) {  if1) {    return1  else {    // 未优化 - 调用中引用了变量n    return1)  }}

In the above code, we can see that the call function in the n has been changing, need to go to record, then the stack will grow larger. The result of the tail call is saved by the parameter, it is always called at the tail end, and the last tail call does not need to continue the calculation to achieve the effect of the tail call optimization, the code is as follows:

function factorial (n, p = 1) {  if1) {    return1 * p  else {    let result = n * p    // 优化    return1, result)  }}
Common function-type operation and implementation mapping map

The map operations team executes the given element on each element of the original collection, thus becoming a new collection. In general, if you need to transform a collection, use map. The following code is a simple implementation of map

 function map (obj, callback){Constresults = []if(obj = = =undefined|| obj = = =NULL) {returnResults}if(typeofObj.map = = =' function ') {returnObj.map (Callback)}ConstKeys =Object. Keys (obj)ConstLength = (keys | | obj). length for( Leti =0; i < length; i++) {Results[i] = callback (Obj[keys[i]], keys[i], obj)}returnResultsExport defaultMap
Folding reduce

The reduce operation uses a cumulative amount to collect the collection elements, and the reduce function is typically used when the cumulative amount needs to be set to an initial value, and is typically used when the set needs to be divided into small chunks to handle. The implementation is as follows:

 function reduce (obj, callback, initial){if(obj = = =undefined|| obj = = =NULL) {Throw New TypeError(' reduce called on null or undefined ')  }if(typeofCallback!==' function ') {Throw New TypeError(' callback not a function ')  }ConstKeys =Object. Keys (obj)ConstLength = (keys | | obj). length LetValue = Initial Leti =0  if(!initial) {value = obj[keys[0]] I =1}if(!initial && length = = =0) {Throw New TypeError(' Reduce of empty array with no initial value ')  } for(; i < length; i++) {value = callback (value, Obj[keys[i]], keys[i], obj)}returnValueExport defaultReduce
Filtering filter

Filter filters the entries in the list based on user-defined criteria, resulting in a smaller, new list that is used when a sub-collection needs to be generated based on the filter criteria, as shown in the following code:

 function filter (obj, callback, context){if(obj = = =undefined|| obj = = =NULL) {Throw New TypeError(' obj cannot be undefined or null ')  }if(typeofCallback!==' function ') {Throw New TypeError(' callback should be function ')  }ConstKeys =Object. Keys (obj)ConstLength = (keys | | obj). lengthConstRET = [] for( Leti =0; i < length; i++) {if(Callback.call (context, obj[keys[i]], keys[i], obj)) {Ret.push (obj[keys[i])}}returnRetExport defaultFilter
Combination Compose

Compose enables us to build complex functions from simple, general-purpose functions. By using functions as building blocks for other functions, we can create truly modular applications that make them readable and maintainable. One of the most important aspects of composition is that they use pure functions, so the code is more testable, maintainable, and extensible. The following is a code implementation of compose:

function compose (...args) {  let1  return (...applyArgs) => {    let i = start    let result = args[start].apply(this, applyArgs)    while (i--) {      result = args[i].call(this, result)    }    return result  }}exportdefault compose
Currying Curry

Curry creates a new function dynamically by applying a part of a function collection to the function, which will save the duplicate arguments and will also use the full list of parameters that the original function expects to be prepopulated. When we find that the same function is being called and the parameters passed are mostly the same, then the function may be a good candidate for curry. The code is implemented as follows:

function curry (fn, ...args) {  return (...newArgs) => {    return fn.apply(null, args.concat(newArgs))  }}exportdefault curry
Memory Memoize

Memoize improves performance by saving the function return result, without having to redo a potentially heavy calculation the next time the function is called. Using the Function property so that the computed value does not have to be recalculated again, the code is implemented as follows:

function memoize (fn) {  const memoizeFn = (...args) => {    const cache = memoizeFn.cache    constJSON.stringify(args)    if (!cache[key]) {      cache[key] = fn.apply(this, args)    }    return cache[key]  }  memoizeFn.cache = {}  return memoizeFn}exportdefault memoize
Design Patterns in functional languages

The reuse of functional language occurs at a coarser fine-grained level, focusing on extracting common operational mechanisms and adjusting their behavior in a parameterized manner. The following is a simple discussion of some design patterns in functional programming and another way to provide solutions.

Factory method

Factory methods are typically implemented in static methods of a class and are primarily used to perform repetitive operations when creating similar objects and to provide interfaces for clients to create objects.

In functional programming, curry is equivalent to the output function of the factory, we can easily set up a condition to return the function of other functions, that is, the function factory. Let's take a look at the following example, assuming that there is a common function that adds two numbers, which, after curry processing, we can create an increment function that adds one to its parameters.

const adder = (x, y) => {x + y}const1)
Enjoy meta mode

Running sharing technology effectively supports a large number of fine-grained objects, avoiding the overhead of a large number of small classes that have the same content (such as memory-intensive), so that you share a class (meta-Class).

In functional programming, the memory (memoize) function allows the runtime to cache its results, avoiding the overhead of a lot of very similar content.

Policy mode

The policy pattern defines the algorithm family, which is encapsulated separately so that they can be replaced by each other, and this pattern allows the algorithm to change without affecting the client using the algorithm.

In functional programming, because functions are first-class citizens, it makes it easier to build and manipulate various strategies.

Summarize

In the actual work, we do not have to be confined to functional or object-oriented, usually the combination of the two, combined with the advantages of both, the purpose is to make the code more simple, beautiful, and easier to debug.

Code Address: https://github.com/wengeek/examples/tree/master/functional-programming

JavaScript Functional Programming

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.