Reprint please retain this part of the content, indicate the source. In addition, the front end team of Toutiao is looking forward to your joining us

Background:

Software development is always complicated with the expansion of business, which puts forward higher requirements for development efficiency and cost. Good programming paradigms are needed to reduce development costs and be as modular as possible to increase efficiency. Refactoring is about breaking up the changing and unchanging blocks of code into modules for maintenance, and functional features, high-order functions and lazy evaluation, can greatly facilitate modularity. It’s important to understand functional concepts, like the latest hooks in React and Redux.

Definition:

Functional programming means avoiding Shared State, Mutable Data, and Side Effects in software development projects. In functional programming, the entire application is data-driven, and the state of the application flows between different pure functions. Functional programming is more declarative than object-oriented programming, which is more imperative. The code is cleaner, more predictable, and more testable. Functional Programming is essentially a Programming Paradigm that represents a set of basic defining criteria for building software systems. Other Programming paradigms include Object Oriented Programming and Procedural Programming. Lisp, the granddaddy of functions

Take the example of making tea to distinguish imperative programming from declarative programming

  • Imperative programming (telling the compiler what to do)

    • 1. Boil water (first person)

    • 2. Grab a tea cup

    • 3. Put the tea

    • 4. Flush

  • Declarative programming (telling the compiler what to do)

    • 1. Make me a cup of tea

    • For the demo

Const convert = function(arr) {const result = [] for (let I = 0; i < arr.length; I ++) {result[I] = arr[I].tolowerCase ()} return result} const convert = function(arr) {return arr.map(r =>) r.toLowerCase())}Copy the code

At the most intuitive point of view, functional style code can be written very concise, greatly reducing keyboard wear!

SQL statements are declarative, you don’t need to care how Select statements are implemented, different databases will implement their own methods and optimizations. React is also declarative, so you just describe your UI and how it will update when the state changes. React handles it for you at runtime, rather than rendering and optimizing diff algorithms yourself.

Features:

Referential transparency

Mathematically, the function is expressed as y = f(x). The specific example assumes that:

For any value of x, like 2, if you plug it into the equation, you get 11. But what is 11? It’s the return value of the function f of x, which is one of the y values that we described earlier. In mathematics, a function that takes in one or more values can always get a corresponding output. You often hear the term “morphism” in functional programming. This is a fancy way of mapping from one set of values to another, like the relationship between the inputs and outputs of a function.

A function in functional programming is not a function in a computer, but an exponential function, which is a mapping of independent variables. The value of a function depends only on the value of its parameters, not on other states. The same input gives the same output. This property is known as referential transparency.

Specific advantage is convenient for caching and concurrent processing (specify can view www.zhihu.com/question/28…).

No Side effects

Side effects refer to any observable change in application state that is not represented by a return value during a function call. Common side effects include, but are not limited to:

  • Modify any external variables or external object properties

  • Output logs in the console

  • Written to the file

  • Initiating network communication

  • Triggers any external process events

  • Call any other functions that have side effects

In functional programming we try to avoid side effects as much as possible and make our programs easier to understand and test.

  • In most of the real application scenario at the start of the program, we can’t ensure all the functions of the system is pure function, but we should increase the number of pure functions as much as possible and will be part of the side effects and separation of meromorphic functions, especially the business logic abstraction as pure functions, to ensure that the software is more easy to expand, refactoring, debugging, testing and maintenance. This is why many front-end frameworks encourage developers to decouple user state management from component rendering and build loosely coupled modules.

  • Ensuring that the function has no side effects ensures that the data is immutable and avoids many of the problems associated with shared state. It may not be obvious when you’re maintaining the code alone, but as the project progresses, the number of people working on it increases, and everyone relies on and references the same variable more and more, the problem gets worse. Ultimately, the maintainer may not even know exactly where the variable is being bugged.

After all, the absence of side effects is one aspect of immutability, but what is?

immutability

Immutable objects are objects that cannot be modified after they are created, as opposed to Mutable objects that can still be modified after they are created. Immutability is one of the core ideas of functional programming, which ensures the lossless data flow in program running. If we ignore or discard the history of state changes, it is hard to capture or reproduce strange, low-probability problems. The advantage of using immutable objects is that when you access any variable from anywhere in the program, you only have read-only permissions, which means you don’t have to worry about accidental illegal changes. On the other hand, especially in multithreaded programming, variables accessed by each thread are constants, thus ensuring thread safety fundamentally. In summary, immutable objects help us build simpler and more secure code. As you might imagine, wouldn’t the memory footprint be high if the function generated a new value every time it ran? In fact, because of this, so js language design at the beginning, objects are mutable objects. The variable name declared by const Js is bound to some memory space and cannot be allocated twice. It does not create a true immutable object. You don’t have to change the point to a variable, but you can change the value of a property of that object, so const is still creating a mutable object. The easiest way to create immutable objects in JavaScript is to call object.freeze (), which creates a layer of immutable objects:

const a = Object.freeze({const a = Object.freeze({ foo: 'Hello', foo: 'Hello', bar: 'world', bar: 'world', baz: '! ' baz: '! '}); }); a.foo = 'Goodbye'; a.foo = 'Goodbye'; // Error: Cannot assign to read only property 'foo' of object Object// Error: Cannot assign to read only property 'foo' of object ObjectCopy the code

However, such objects are not completely immutable data. For example, the following objects are mutable:

const a = Object.freeze({const a = Object.freeze({ foo: { greeting: 'Hello' }, foo: { greeting: 'Hello' }, bar: 'world', bar: 'world', baz: '! ' baz: '! '}); }); a.foo.greeting = 'Goodbye'; a.foo.greeting = 'Goodbye'; console.log(`${ a.foo.greeting }, ${ a.bar }${a.baz}`); console.log(`${ a.foo.greeting }, ${ a.bar }${a.baz}`); // Goodbye, world!Copy the code

As you can see, the top-level attributes of the underlying type cannot be changed, but attributes of the object type, such as arrays, can still be changed. To address this problem, many functional programming languages provide special immutable data structures. Trie Data Structures to achieve true immutable Data Structures, where no level of properties can be changed. Tries can also use Structural Sharing to share unchanged object property values between old and new objects, reducing memory footprint and significantly improving the performance of some operations (data persistence). JavaScript doesn’t give us this feature, but it can take advantage of Tries’ features through helper libraries such as Immutable. Js and Mori.

Immutable data is also an important principle in Redux. When using Redux, changing stores must return new states. So react-Redux makes immutable data popular, and immutable is the best way to do it.

Functional programs do not contain any assignment statements, so once a variable is assigned a value, it does not change. More generally, functional programs contain no side effects: a function does nothing except evaluate its own value. This feature eliminates a major source of “bugs” and makes the order of execution unimportant — no side effects can change the value of an expression, so it can be evaluated at any time. This feature saves the programmer from the burden of deciding control flow. Since an expression can be evaluated at any time, the programmer can use the value of a variable in place of a variable at will, and vice versa — that is, the program is reference-transparent. This freedom makes functional programs much easier to control mathematically than their traditional counterparts.

Higher-order functions:

Higher-order functions are functions that take arguments or return values of functions. With higher-order functions, the granularity of reuse is reduced to the function level, which is lower than object-oriented. When a language allows functions to be used as any other data type, functions are said to be first-class citizens. That is, functions can be assigned to variables, passed as arguments, or returned by other functions.

Higher-order functions are often used in the following scenarios:

  • Use callback functions, promises, or Monad to abstract or isolate actions, effects, and any asynchronous control flows

  • Build utility functions that work with generic data types

  • Function reuse or create currie functions

  • Returns a composite function of multiple functions that are input

Recursive:

“Historically, recursion and functional programming have been related, or at least they’ve often been introduced together. Understanding recursion is important to understanding functional programming for three reasons:

  • A recursive solution involves the use of a single abstraction for a subset of common problems.

  • Recursion can hide mutable state.

  • Recursion is a way to achieve lazy and infinite structure.”

Fibonacci numbers

The Fibonacci sequence, also known as the Golden section sequence, refers to such a sequence 1, 1, 2, 3, 5, 8, 13, 21…… The sequence starts with the third term and each term is equal to the sum of the first two.

function fibonacci(n){
   if(n==0)return 0   if(n==0)return 0
   else if(n==1)return 1   else if(n==1)return 1
   else return fibonacci(n-1) + fibonacci(n-2)   else return fibonacci(n-1) + fibonacci(n-2)
}}
Copy the code

Functional programming languages cannot implement loops because of immutable data structures, so loops are implemented recursively. Cyclic implementations included in the Fibonacci sequence above:

function fibonacci(n){ let last = 1 let last = 1 let last2 = 0 let last2 = 0 let current = last2 let current = last2 for(let i=1; i<=n; i++){ for(let i=1; i<=n; i++){ last2 = last last2 = last last = current last = current current = last + last2 current = last + last2 } } return current return current }}Copy the code

Improper use of recursion is easy to Stack Overflow, so tail recursion is generally used to optimize. Tail recursion of Fibonacci sequence:

const Fibonacci = (n, sum1 = 1, sum2 = 1) => {
     if (n <= 1) return sum2;     if (n <= 1) return sum2;
     return Fibonacci(n - 1, sum2, sum1 + sum2)     return Fibonacci(n - 1, sum2, sum1 + sum2)
}}
Copy the code

Closure:

“Closures and first-class functions go hand in hand. While languages without first-class functions can support closures, there are often significant obstacles. Thankfully, JavaScript supports first-class functions, so its closures can bypass temporary wrapped state in a powerful way.” Excerpt from: [US]Michael Fogus. “JavaScript functional programming.” An Apple Books. closure is an inner function (that is, a function inside another function). Closure functions are powerful because of their access to the chain/hierarchy of scopes. Closures have three accessible scopes:

  • A variable declared inside a closure function itself, such as:
function outer() { function outer() { function inner() { function inner() { let a = 5; let a = 5; console.log(a); console.log(a); } } inner(); // Call inner, output 5. inner(); // Call inner, output 5. }}Copy the code
  • Access to global variables

  • Access to external function variables (key), which makes closure functions very powerful!

let global = 'global'; let global = 'global'; function outer() {function outer() { let outer = "outer"; let outer = "outer"; function inner() { function inner() { let a = 5; let a = 5; console.log(outer); console.log(outer); } } inner(); // call inner and print outer. inner(); // call inner and print outer. }}Copy the code

A closure can remember its context.

let fn = (arg) => { let outer = "visible"; let innerFn = () => { console.log(outer); console.log(arg); } return innerFn; } let closureFn = fn(5); // innerFn is called by parameter 5, and returns innerFn. When innerFn is returned, the // JS execution engine treats the innerFn as a closure and sets its scope accordingly. // The reference to the return function is stored in closureFn. The arg, outer values are remembered when closureFn is called through the scope chain! closureFn(); // Output: visible 5Copy the code

For the Fibonacci sequence above, we can use closures to remove double counting:

function fib(n){function fib(n){ function fib_(n,a,b){ function fib_(n,a,b){ if(n==0) return a if(n==0) return a else Fib_ (n-1,b,a+b) else return fib_(n-1,b,a+b)}} return fib_(n,0,1) return fib_(n,0,1)}}Copy the code

Inertia is evaluated

A complete Functional program is nothing more than a function that maps from an input to an output. If f and g are such programs, then for the program (g.f), after providing the input parameter input, we get:

g (f input)g (f input)
Copy the code

Program F evaluates its own output, which is used as input to program G. Traditionally, this is done by storing the output of F in a temporary file. The problem with this approach is that temporary files can take up so much space that gluing programs together becomes impractical. Functional languages provide a solution. Programs F and G run in strict synchronization, starting only when G tries to read input and running just long enough to provide the output data THAT G needs to read. F is then suspended, and G continues execution until it tries to read another input. As an added bonus, if G terminates without reading the entire output of F, then f is terminated. F can even be a program that does not terminate on its own, producing an infinite amount of output because f will be terminated forcibly when G finishes running. This allows termination conditions to be separated from the body of the loop — a powerful form of modularity. This evaluation makes f run as little as possible and is called “lazy evaluation”. It makes it possible to modularize the program into a generator that produces a large number of possible solutions and a selector that selects the appropriate solutions. Some other systems allow programs to run this way, but only functional languages use lazy evaluation uniformly for every function call, allowing every part of the program to be modularized in this way. Lazy evaluation is perhaps the most powerful modularity tool in the functional programmer’s Arsenal.

Functional Programming For The Rest of Us (Functional Programming For The Rest of Us)

String s1 = somewhatLongOperation1(); String s2 = somewhatLongOperation2(); String s2 = somewhatLongOperation2(); String s3 = concatenate(s1, s2); String s3 = concatenate(s1, s2);Copy the code

In an instruction language the order in which the above code is executed is obvious. Because each function can change or depend on its external state, it must be executed sequentially. SomewhatLongOperation1 is computed, then somewhatLongOperation2, and finally concatenate. Functional languages are different.

SomewhatLongOperation1 and somewhatLongOperation2 can be executed concurrently because the functional language guarantees that no function affects or depends on global state. But what if we don’t want these two functions to execute concurrently? Is it still necessary to execute these functions sequentially? The answer is no. You only really need to execute s1 and s2 when you need to execute a function that takes s1 and s2 as arguments. Thus, there is no need to execute either function until concatenate is executed: the execution of these functions can be deferred until s1 and s2 are needed in concatenate(). If you replace concatenate with another function that has a conditional statement and actually only needs one of the two arguments, there is no need to perform a function that evaluates the other argument! The Haskell language is an example of lazy evaluation. Haskell does not guarantee that any statements will be executed sequentially (or at all) because Haskell code will only be executed when needed.

Advantages:

Code optimization

Lazy evaluation gives code a lot of optimization potential. Compilers that support lazy evaluation treat functional programs the way mathematicians treat algebraic expressions: cancel out the same items to avoid unnecessary code execution, order code execution to achieve more efficient execution and even fewer errors. Optimizing on this basis does not break the code. The greatest benefit of programming strictly with the basic elements of formal systems is that the code can be analyzed mathematically, because such programs are mathematically correct

Abstract control structures

Lazy evaluation techniques provide a higher level of abstraction, which provides a unique approach to programming. For example, the following control structure:

unless(stock.isEuropean()) {
    sendToSEC(stock);
}
Copy the code

SendToSEC is executed in the program except when stock is European. How do you implement unless in the example? If there is no lazy evaluation, you need to resort to some form of macro. , but in a language like Haskell, you don’t have to. Simply implement a unless function!

void unless(boolean condition, List code) { if(! condition) code; }Copy the code

Note that if condition is true, code is not evaluated. In other strict languages (see strict evaluation) this behavior is not possible because the code as an argument has already been computed before entering unless.

Infinite data structure

Lazy evaluation techniques allow the definition of infinite data structures, which would be complicated to implement in a strict language. For example, a list of Fibonacci numbers. It is obvious that such a list would not be able to compute an infinite number of numbers in a finite amount of time and store them in memory. In a strict language like Java, you can define a Fibonacci function that returns some number in this sequence. In Haskell or similar languages, you can further abstract this function and define an infinite list structure of Fibonacci sequences. Since the language itself supports lazy evaluation, only the numbers in the list that will actually be used will be computed. This allows us to abstract many problems and then solve them at a higher level (such as a list that can handle an infinite number of data in a list processing function).

disadvantages

As the saying goes, there is no such thing as a free lunch. Lazy evaluation has its drawbacks, of course. One of the biggest is, well, inertia. In the real world, a lot of problems need to be evaluated strictly. Take the following example:

System.out.println("Please enter your name: ");
System.in.readLine();
Copy the code

In an inert language there is no guarantee that the first line will be executed before the second! This means that we can’t process IO, call system functions to do anything useful (these functions need to be executed in order because they depend on external state), or interact with the outside world! If we introduce code primitives that support sequential execution, we lose the benefit of analyzing our code mathematically (which means we lose all the benefit of functional programming). Mathematicians have developed different ways to ensure that code executes in a certain order (in a functional setting?). . This allows us to take advantage of both functional and instruction programming! These methods are Continuations, Monads, and Uniqueness typing. But these are covered later.

Lazy evaluation is as opposed to early evaluation. For example, in most languages, expressions in parameters are evaluated first, which is a common calculation that most programming languages have. For example, look at the following JavaScript function:

wholeNameOf(getFirstName(), getLastName())wholeNameOf(getFirstName(), getLastName())
Copy the code

GetFirstName and getLastName are executed in sequence, returning the value as an argument to wholeNameOf, which is called last. In addition, for array manipulation, most languages also use the application sequence.

[1, 2, 3, 4].map(x => x + 1)[1, 2, 3, 4].map(x => x + 1)
Copy the code

Therefore, the expression immediately returns the result [2, 3, 4, 5]. Of course, js also has a library to implement lazy evaluation: github.com/dtao/lazy.j… Here’s an example from how to Implement an Array Lazy Evaluation Library in JavaScript

  • Lazy functions in JavaScript:
function addEvent (type, element, fun) { if (element.addEventListener) { if (element.addEventListener) { addEvent = function (type, element, fun) { addEvent = function (type, element, fun) { element.addEventListener(type, fun, false); element.addEventListener(type, fun, false); } } } else if(element.attachEvent){ } else if(element.attachEvent){ addEvent = function (type, element, fun) { addEvent = function (type, element, fun) { element.attachEvent('on' + type, fun); element.attachEvent('on' + type, fun); } } } else{ } else{ addEvent = function (type, element, fun) { addEvent = function (type, element, fun) { element['on' + type] = fun; element['on' + type] = fun; } } } } return addEvent(type, element, fun); return addEvent(type, element, fun); }}Copy the code

Popular JavaScript functional libraries include:

  • Ramda

  • lodash/fp

  • Immutable.js

Follow-up: This paper only mentions the definition and some characteristics of the function, and does not involve the calculus of λ, Y combinator, flow and pipe and other deeper concepts, as well as the specific application. We will continue to learn more about it in the future

Reference:

  1. www.byvoid.com/zht/blog/wh…

  2. Juejin. Cn/post / 684490…

  3. Github.com/getify/Func… (book)

  4. zhuanlan.zhihu.com/p/24819380

  5. Blog.csdn.net/ImagineCode…

  6. www.zhihu.com/question/28…

  7. Immutable data structures

  8. zhuanlan.zhihu.com/p/38007829

  9. Llh911001. Gitbooks. IO/mostly – at… (book)