1. Introduction

Functional Programming (FP) is gaining popularity both at the front end and the back end, such as React (Core Thinking Data as Views), Composition API of Vue3.0, Redux, Lodash, Underscore is certainly not a programming idea invented in recent years. Its roots are in a branch of mathematics called Category Theory.

2. Learn functional programming

  • The idea of functional programming
  • Functional programming features (declarative programming, side effects, pure functions)
  • Currization, combination of functions
  • Functor (container), map

3. Give me an example

'id=1'
/ / convert
{id: 2}
Copy the code

Procedural programming:

Using traditional programming ideas, you can start by uncoding code, defining temporary variables, and coding them:

var str1 = 'id=1';
var numArr = num.split('=');
numArr[1] = numArr[1] * 2;
var obj = {
  [numArr[0]]: numArr[1]};Copy the code

Here is in accordance with the process of programming ideas, fully oriented to the process. It can complete the task, of course, but the result is the emergence of a pile in the middle of the temporary variables, see the light of the variable name is to despair, at the same time contains implementation logic among them, such an implementation approach usually need read from beginning to end to know what it did, once there is an error in the middle, screening is also a problem.

Here we don’t use functional thinking to solve this problem, and after learning the basic content of the function, we will go back to see how to achieve this problem in a functional way.

4. Functional programming

Functional programming is a completely different way of thinking from procedural programming. It focuses on functions, not procedures, but how to solve problems by combining and changing functions, rather than what kind of statements to write. As you get more and more code, this kind of function splitting and combining can be powerful.

In fact, I have learned functions in junior high school, such as primary function, quadratic function, inverse proportional function and so on. In fact, different functions actually describe the conversion relationship between sets and sets, and the value of the input function has only one output value.

That is, the function actually describes a relationship, or a mapping. And functions can be combined with each other, that is, the output of one function as the input of another function.

In programming, we only need to deal with “data” and “relations”, and to describe “relations” is to use functions, so in general we only need to program to implement functions, that is, to find “relations”. Once the function (” relation “) is implemented, the data can also be converted to other target data through the function.

In fact, we can think of functional programming as an assembly line, taking the input as a raw material, the output as a product, the raw material through the processing steps of the assembly line (functions), and finally produce the product (results).

5. Features of functional programming

5.1 Function is “First-class citizen”

The implementation of functional programming requires manipulation of functions, so this feature means that functions have the same status as other data types. That is, functions can be assigned to other variables, passed as arguments to functions, and returned as values from other functions, such as:

function fn1(x) {
  return x + 2;
}

// 1. Assign the function to another variable
var f = fn1;
f(1); / / 3

// 2. One function is passed as an argument to another
function fn2(f) {
  var num = f(3) - 2;
  return num;
}
fn2(fn1); / / 3

// 3. One function as the return value of another function
function fn3(x) {
  return 2 * fn1(x);
}

fn3(1); / / 6
Copy the code

5.2 Declarative programming

var arr = [{name: "Zhang"}, {name: "Bill"}, {name: "Fifty"}];
/ / convert
['Joe'.'bill'.'Cathy'];

/ / imperative
var names = [];
for (var i = 0; i < arr.length; i++) {
  names.push(arr[i].name);
}

/ / the declarative
var names1 = arr.map(function (obj) { return obj.name; });
Copy the code

The imperative starts by instantiating an array, and then the interpreter executes the rest of the code. Then we loop through the ARR array, increment the counter I, and display the various logical statements.

The Map version is an expression with no order of execution and no exposed loop statements, implemented by a Map tool. This style of programming is declarative, and the benefits of such code are particularly readable because most declarative code is close to natural language. Declarative programming is about what to do, not how to do it;

5.3 No Side effects

Functional programming is based on the premise that there are no side effects. If a function has side effects, then it is procedural programming (imperative programming), and we need to know what the common side effects are.

1. Side effects caused by external interaction:

var result = 0;

function fn(x) {
  for (let i = 0; i < x; i++) {
    result += i;
  }
  return result;
}

fn(3); 
fn(3);
// The function fn receives the same parameters, but the result is different. So what can be done to address this side effect?
Copy the code

2. Call the I/O

Including: read and write files, network, and database

let res = await fetch('/url');
// The same argument is passed each time, but the result is not the same.
Copy the code

3. Disk search

function getRandom() {
  return Math.random();
}
Copy the code

4. Throw an exception

function fn(x) {
  throw new Error(a); }Copy the code

Note:

  1. The side effects are bad because the results are unpredictable. A function that has no side effects and executes at any time, given the same input, must produce the same result.
  2. Is functional programming free of side effects? And it isn’t. We just limit them when we need them, and since there are no side effects, our code just computes. Frequently used read and write database, network requests, read and write files, are needed, so it is not side effects will not be used.
  3. By ensuring that the function has no side effects, the data obtained can be guaranteed to be invariable and various problems caused by cross-reference can be avoided.

5.4 pure functions

A pure function is a function that receives the same input data and produces the same output, independent of changes in the external state. It’s a function that doesn’t have any side effects.

For example, use Splice and slice:

var nums = [1.2.3.4.5];

/ / pure
nums.slice(0.3); / / [1, 2, 3]

nums.slice(0.3); / / [1, 2, 3]

nums.slice(0.3); / / [1, 2, 3]

/ / not pure
nums.splice(0.3) / / [1, 2, 3]

nums.splice(0.3) / / [4, 5]

nums.splice(0.3) / / []
Copy the code

Slice is a pure function, while splice is a function with side effects.

Note:

  1. The reason why the emphasis on pure function, one is that the use of pure function input the same will get the same result, such code is more conducive to maintenance, optimization code will not affect the execution of other code; Second, by using the characteristics of pure functions, the results of function execution can be cached in advance.

    // Cache examples
    function memoize(fn) {
      var cache = {};
      return function() {
        var arg_str = JSON.stringify(arguments);
        cache[arg_str] = cache[arg_str] || fn.apply(null.arguments);
        console.log(JSON.stringify(cache));
        returncache[arg_str]; }}// 
    var fibonacci = memoize(n= > n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2));
    
    fibonacci(4); // first cache 1,2,3, 4
    fibonacci(5); // get 1,2,3,4 directly the second time
    Copy the code
  2. Pure functions do not depend on external scopes, so they are easy to understand.

6. Functional programming builds code

Two essential operations for functional programming are curry and compose, which is essentially a pipeline of processing stations on a factory assembly line.

6.1 Mr Currie

The so-called “Cremation” is to transform a function with many parameters into a single parameter function, also known as a unit function.

fn(a, b, c) -> fn(a)(b)(c)
Copy the code

To implement a method of summing two numbers:

function add(x, y) {
  return x + y;
}

// add currization
function add1(x) {
  return function(y) {
    returnx + y; }}const increment = add1(1);
inccrement(10); / / 11
Copy the code

Note:

  1. The Coriolization emphasizes single-parameter functions, as opposed to partial function applications, which emphasize fixed arbitrary parameters.

    / / curry
    fn(1.2.3) -> fn(1) (2) (3)
    // Some functions are applied
    fn(1.2.3) -> fn(1.2) (3) | fn(1) (2.3)
    Copy the code

6.2 Advanced Corrification

Is there a function that can directly convert a normal function into a coriolization function? The answer is yes. Most libraries have currie implementations, such as Lodash and underscore, so let’s simulate the implementation of the advanced Currie function curry

function curry(fn) {
  // Determine the number of arguments and parameters, using the rest of the argument, so that the args argument is an array
  return function curried(. args) {
    if (args.length < fn.length) {
      return function () {
        returncurried(... args.concat(Array.from(arguments))); }}// Fn is called when the number of parameters and arguments is the same
    return fn(...args);
  }
}

/ / the add method
function add(a, b, c) {
  return a + b + c;
}

const addCurry = curry(add);
addCurry(1) (2) (3);/ / 6
const addCurry1 = addCurry(1);
addCurry1(2.3); / / 6; This is not purely A Currization, but rather a partial function application

Copy the code

The curry function we implemented above is not a pure Curryization function. The implementation of this function has no limit on the number of arguments, so it returns the corresponding Coriolization function or result value based on the argument. The advanced Coriolization functions here can implement coriolization and partial function application, but the Coriolization is not partial function application.

Application of the Currie function

const replace = curry((a, b, str) = > str.replace(a, b));
const replaceSpaceWith = replace(/\s*/g);
const replaceSpaceWithComma = replaceSpaceWith(The '*');
const replaceSpaceWithDash = replaceSpaceWith('|');
Copy the code

And finally, just to remind you, why do we use currization in functional programming? This is a complete problem to think about in terms of combinations of functions, so let’s look at combinations of functions to find the answer.

Function composition (compose)

Function combination is the combination of multiple functions into a new function. This can be understood as each pipeline site (the Coriolization function), combined to form a pipeline. You combine currization functions, and that’s the job of combining functions. If the composition is not a Corrified function, then the integration is not linear, but branches occur.

// fn2->fn1
// compose(fn1, fn2);
function compose(fn1, fn2) {
  return function (p) {
    returnfn1(fn2(p)); }}// What happens if fn1 and fn2 are not pure functions?
Copy the code

The implementation of the compose function

function compose(. fns) {
  return function (value) {
    return fns.reduce(function (args, fn) {
      return fn(args);
    }, value)
  }
}
Copy the code

Trace Debug of function composition

When there is a problem with the combination of functions, how to debug it? At this time, you can add a trace function (Currie function) to debug the problem function.

var trace = curry((tag, x) = > {console.log(tag, x); returnx; });var f = compose(fn1, fn2, fn3, trace('after fn3'), fn4);
Copy the code

Point Free

The PointFree style of programming means that functions do not need to mention what data they are manipulating. That is, there are no parameters in the function writing process, but only through the combination of functions to generate a new function call can be passed

/ / PointFree writing
var f = compose(fn1, fn2, fn3, fn4);
f(1);

// non-pointfree
function f(num) {
  var n1 = fn1(num);
  var n2 = fn2(n1);
  var n3 = fn3(n2);
  var n4 = fn4(n3);
  return n4;
}
f(1);
Copy the code

PointFree uses:

  1. What pointFree does is make code cleaner and easier to understand (no argument naming, no code simplification), but it doesn’t force you to write code that doesn’t have any arguments. That’s not practical.
  2. The purpose of learning programming paradigms is to make myself write more understandable and maintainable code, not to increase my programming cost and not to put the cart before the horse.

6.5 Implement the initial example through function composition

'id=1'
/ / convert
{id: 2}
Copy the code
// Function combination
function compose(. fns) {
  return function (value) {
    return fns.reduce(function (args, fn) {
      return fn(args);
    }, value);
  };
}
// 1. String to array
var split = function (str) {
  return str.split("=");
};
// 2. Multiply the first element of the array by 2
var double = function (arr) {
  arr[1] = arr[1] * 2;
  return arr;
};
// 3. Array to object
var arrToObj = function (arr) {
  return { [arr[0]]: arr[1]}; };// combine (1,2,3) methods
var strToObj = compose(split, double, arrToObj);
var result = strToObj('id=1');
console.log(result);
Copy the code

Functor of functional programming

Compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose Next we’ll look at functional programming to keep side effects under control, exception handling, asynchronous operations, and more.

7.1 What is a Functor

Let’s start with the above example as a functor

function Functor(x) {
  return {
    map: function (fn) {
      return Functor(fn(x));
    }
  }
}

Functor('id=1')
  .map(str= > str.split('='))
  .map(arr= > {arr[1] = arr[1] * 2; returnarr; }) .map(arr= > ({[arr[0]]: arr[1]}))
Copy the code

The above code, Functor, is a Functor. Its map method takes the function fn as an argument and returns a new Functor containing values that FN has processed (string to array, array elements multiplied by 2, array to object). In general, the hallmark of a functor is that the container has a map method. This is a way of mapping the values in a container to a new container called a functor, which is a concept in category theory.

All operations in functional programming are performed by functors, that is, operations are performed not directly on a value, but on the container of that value (functors). Functors generally have external interface map methods. Fn is passed in as an operator to change the value of the functor.

Learning functional programming is really learning the operations of containers (functors). Since operations can be encapsulated inside functors, different types of functors are derived, and there are as many functors as there are operations. Functional programming becomes using different functors to solve different problems.

In the example above, we still don’t get the final value, so how do we get the final value? Add an output method output to output the final result.

function Functor(x) {
  return {
    map: function (fn) {
      return Functor(fn(x));
    },
    output: function (fn) {
      return fn(x);
    }
  }
}

Functor('id=1')
  .map(str= > str.split('='))
  .map(arr= > {arr[1] = arr[1] * 2; returnarr; }) .map(arr= > ({[arr[0]]: arr[1]}))
	.output(x= > x); // {id: 2}
Copy the code

7.2 Either functor

If appear if…. else… , try… catch… How do we design functors? At this time, we need to deal with the case.

// For a try-catch example, the logic of a try is assigned to the Left function and the logic of a catch is assigned to the Right function
function Left(x) {
  return {
    map: function(fn) {
      return Left(fn(x));
    },
    output: function (fn) {
      returnfn(x); }}}function Right(x) {
  return {
    map: function(fn) {
      return Right(x);
    },
    output: function(fn) {
      return fn(x)
    }
  }
}

/ / define tryCatch
function tryCatch(fn) {
  try {
    return Left(fn());
  } catch(e) {
    returnRight(e); }}// Error, catch into Right
tryCatch(() = > a()).map(x= > console.log('map')).output(x= > x);
// Go to Left
tryCatch(() = > 3).map(x= > x + 3).output(x= > x);
Copy the code

The above tryCatch function, the catch part of the Right function, is more like a bottom-pocket operation, and the map method of the Right function does not execute the passed function, thus ensuring that the chain call is error-free even if an error occurs.

Thinking: can I merge the Left function with the Right function?

function Either(left, right) {
  return {
    map: function (fn) {
      return right ? Either(right) : Either(fn(left))
    },
    output: function (fn) {
      returnright ? fn(right) : fn(left); }}}/ / define tryCatch
function tryCatch(fn) {
  try {
    return Either(fn());
  } catch(e) {
    return Either(null, e);
  }
}

tryCatch(() = > a()).map(x= > console.log('map')).output(x= > x);
tryCatch(() = > 3).map(x= > x + 3).output(x= > x);
Copy the code

What is 7.3? Why is that? How does it work?

What exactly is a functor?

I’m not going to burden you with category theory, but if you’re interested, you can do your own search. A Functor is a container with a map method that maps the values of the original container to the corresponding values of the new container.

Why do we use functors?

What is the purpose of putting a value into a container (functor) and then manipulating it through a map? The idea is to combine small functions into more advanced functions by means of functors.

How do I use functors?

The concept of functors has already been used in the code, but I didn’t know they were functors at the time, such as Array map and filter, jQuery CSS and style, and Promise then and catch methods. So it’s the same type, so you can keep doing chain calls.

The last

Due to the length of the article, specific functor applications are not covered in this article.

Refer to the connection

  • Functional programming introductory tutorial
  • Functional programming is north
  • The illustration Monad
  • Category theory