Front- end-Basics: Watch, Star, Front-End-Basics: Front-End-Basics

The address of this article: JavaScript functional programming primer classic


The text start



What is functional programming? Why is it important?

Function in mathematics

F (x) = y // A function f that takes x and returns output yCopy the code

Key points:

  • A function must always take an argument
  • A function must always return a value
  • Functions should be run based on the parameters they receive (for example, x) rather than the external environment
  • For a given x, only a unique y is printed

Functional programming technology is mainly based on mathematical functions and its ideas, so to understand functional programming, it is necessary to understand mathematical functions first.

The definition of functional programming

A function is a piece of code that can be called by its name. It can take arguments and return values.

Like object-oriented programming and Procedural programming, Functional programming is a programming paradigm. This allows us to create functions that rely on input alone to do their own logic. This ensures that the same result is returned when the function is called multiple times (reference transparency). The function does not change any external environment variables, which results in a cacheable, testable code base.

Functional programming has the following characteristics

1. Referential transparency

All functions return the same value for the same input. This property of functions is called Referential Transparency.

Var identity = (I) => {var identity = (I) => {return i}
Copy the code
Replace the model

Use a function with a transparent reference between other function calls.

The sum (4, 5) + identity (1)

According to the definition of reference transparency, we can replace the above statement with:

The sum (4, 5) + 1

This process is called Substitution modeling, because the logic of a function doesn’t depend on other global variables, and you can simply replace the result of the function, which is the same as its value. So, this makes concurrent code and caching possible.

Concurrent code: When running concurrently, if global data is relied upon, synchronization must be performed to ensure data consistency, and locking mechanisms are required if necessary. Functions that follow transparent references depend only on the input of arguments, so they can run freely.

Caching: Since the function returns the same value for the given input, we can actually cache it. For example, if we implement a function that computes the factorial of a given value, we can cache the result of each factorial and use it next time without calculating it. For example, if we type 5 the first time, we get 120. If we type 5 the second time, we know that the result must be 120, so we can return the cached value without having to evaluate it again.

Declarative and abstract

Functional programming advocates declarative programming and writing abstract code.

Compare imperative and declarative
/* command */ var array = [1,2,3]for(var i = 0; i < array.length; I ++) console(array[I]) // print 1,2,3 // in imperative programming, we tell the program exactly what to do: get the length of the array, loop through the length of the array, get each element of the array with an index on each loop, and print it out. // But our task is just to print out the elements of the array. It's not about telling the compiler how to implement a traversal. /* declarative */ var array = [1,2,3] array.foreach ((element) => console.log(element)) Then we can only care about doing "what"Copy the code
Functional programming advocates creating functions in an abstract way, such as forEach above, that can be reused in other parts of the code.

3. Pure functions

Most of the benefits of functional programming come from writing pure functions that return the same output for a given input, and that should not depend on or change any external variables.

The benefits of pure functions
  1. Pure functions produce code that is easy to test
  2. Pure functions are easy to write sensible code
  3. Pure functions make it easier to write concurrent code. Pure functions always allow us to execute code concurrently. Because a pure function doesn’t change its environment, it means we don’t need to worry about synchronization at all.
  4. Since pure functions always return the same output for a given input, we can cache the output of a function.

Higher-order functions

Data and data types

Programs act on data, and data is important to the execution of programs. Every programming language has data types. These data types can store data and allow programs to manipulate it.

In JavaScript, functions are First Class Citizens.

** When a language allows functions to be used as any other data type, functions are called first-class citizens. This means that functions can be assigned to variables, passed as arguments, or returned by other functions.

Functions are a JavaScript data type, and since functions are a String type, we can store them in a variable that can be passed as arguments to the function. So functions in JavaScript are first-class citizens.

Definition of higher order functions

A Function that takes another Function as an argument is called higher-order Function, or a higher-order Function that takes a Function as an argument and/or returns a Function as output.

Abstract and higher-order functions

In general, higher-order functions are used to abstract general problems; in other words, higher-order functions define abstractions.

Abstract: In software engineering and computer science, abstraction is a technique for managing the complexity of a computer system. By establishing the level of complexity at which a person interacts with the system, more complex details are suppressed below the current level. In short, abstraction lets us focus on predetermined goals without worrying about the underlying system concepts.

For example, if you’re writing code that involves numeric manipulation, you won’t have a deep understanding of whether the underlying hardware’s numeric representation is a 16-bit integer or a 32-bit integer, including where those details are masked. Because they’re abstracted out, we’re left with simple numbers to use.

/ / useforEach abstracts the operation that iterates through an array of numbers constforEach = (array,fn) => {
  let i;
  for(i=0; i<array.length; I ++) {fn(array[I])}} // Users do not need to understandforHow Each implements traversal is then abstracted from the problem. // For example, you want to print out each item of the arrayletArray = [1, 2, 3]forEach(array,(data) => console.log(data)) 
Copy the code

Closures and higher-order functions

What is a closure? In short, the ** closure is an inner function. ** What is an inner function? It’s a function inside another function.

The power of a closure lies in its access to the scope chain (or scope hierarchy). Technically, closures have three accessible scopes.

(1) a variable declared within its own declaration

(2) Access to global variables

(3) Access to external function variables (key points)

Example 1: Suppose you iterate through an array from a server and find the data is wrong. You want to debug and see what’s in the array. Don’t do it in an imperative way, do it in a functional way. So we need a tap function here.

const tap = (value) => {
  return (fn) => {
    typeof fn === 'function'&& fn(value) console.log(value)}} // No debugging beforeforEach(array, data => {console.log(data + data)}) // yesforTap debugging is used in EachforEach(array, data => {
  tap(data)(() => {
    console.log(data + data)
  })
})
Copy the code

Complete a simple reduce function

const reduce = (array,fn,initialValue) => {
  let accumulator;
  if(initialValue ! = undefined) accumulator = initialValueelse
    accumulator = array[0]

  if(initialValue === undefined)
    for(let i = 1; i < array.length; i++)
      accumulator = fn(accumulator, array[i])
  else
    for(let value of array)
      accumulator = fn(accumulator,value)
  returnAccumulator} console.log(reduce([1,2,3], (accumulator,value) => accumulator + value)) // print 6Copy the code

Currization and bias applications

Some of the concepts

A function of

Functions that take only one argument are called unary functions.

Dual function

A function that takes only two arguments is called binary.

Variable and function

An argument function is a function that accepts a variable number.

Currie,

Currization is the process of converting a multi-parameter function into a nested unary function.

For example,

// const add = (x,y) => x + y; Add (2,3) // a nested unary function const addCurried = x => y => x + y; AddCurried (2)(3) // Then we write a higher-order function that converts Add to the form addCurried. const curry = (binaryFn) => {return function (firstArg) {
    return function (secondArg) {
      return binaryFn(firstArg,secondArg)
    }
  }
}
let autoCurriedAdd = carry(add)
autoCurriedAdd(2)(3)
Copy the code

This is a simple implementation of a function of two variables. Now we are going to implement a more parametric function.

const curry = (fn) => {
  if(typeof fn ! = ='function') {
    throw Error('No function provided')}return functioncurriedFn (... Args) {// Determine whether the currently accepted arguments are less than the number of arguments of the currized functionif(args. Length < fn.length) {// If less than the value of the args, return a function to receive the rest of the argumentsreturn function(... argsOther) {return curriedFn.apply(null, args.concat(argsOther))
      }
    }else {
      return fn.apply(null,args)
    }
  }
}

 const multiply = (x,y,z) => x * y * z;
 console.log(curry(multiply)(2)(3)(4))
Copy the code

Example of currization: Finding elements that contain numbers from an array

let match = curry(function (expr,str) {
  return str.match(expr)
})
let hasNumber = match(/[0-9]+/)

let initFilter = curry(function (fn,array) {
  return array.filter(fn)
})

let findNumberInArray = initFilter(hasNumber)
console.log(findNumberInArray(['aaa'.'bb2'.'33c'.'ddd',])) // Print ['bb2'.'33c' ]
Copy the code

Partial application

The Currization function we designed above always takes an array at the end, which makes it accept only a list of arguments from the left to the right.

Sometimes, however, we can’t strictly pass in arguments from left to right, or we just want to partially apply function arguments. Here we need to use the concept of partial application, which allows the developer to partially apply function parameters.

const partial = function(fn, ... partialArgs) {return function(... fullArguments) {let args = partialArgs
    let arg = 0;
    for(let i = 0; i < args.length && arg < fullArguments.length; i++) {
      if(args[i] === undefined) {
        args[i] = fullArguments[arg++]
      }
    }
    return fn.apply(null,args)
  }
}
Copy the code

Examples of partial applications:

// Prints some formatted JSONlet prettyPrintJson = partial(JSON.stringify,undefined,null,2)
console.log(prettyPrintJson({name:'fangxu',gender:'male'})) // Print out {"name": "fangxu"."gender": "male"
}
Copy the code

Assembly and pipeline

The concept of Unix

  1. Each program only does one thing, and to accomplish a new task, rebuilding is better than adding new “attributes” to a complex old program.
  2. The output of each program should be the input of another as yet unknown program.
  3. Each of the basic functions needs to take an argument and return data.

Combination (compose)

const compose = (... fns) => {return (value) => reduce(fns.reverse(),(acc,fn) => fn(acc), value)
}
Copy the code

The compose combination of functions that are called from right to left in the order they are passed in. So the incoming FNS needs to be reversed first, and then we use reduce. The initial value of the reduce accumulator is value, and then call (acc,fn) => fn(ACC) to fetch FN from the FNS array in turn. Pass the current value of the accumulator to fn, that is, pass the return value of the previous function to the argument of the next function.

Examples of combinations:

let splitIntoSpace = (str) => str.split(' ')
let count = (array) => array.length
const countWords = composeN(count, splitIntoSpace)
console.log(countWords('make smaller or less in amount') // Print 6Copy the code

Pipeline/sequence

The compose function streams data from right to left, with the right-most one executed first. Of course, we could have the left-most function execute first and the right-most function execute last. This process of processing a data stream from left to right is called a pipeline or sequence.

Reverse () const pipe = (composing) const pipe = (... fns) => (value) => reduce(fns,(acc,fn) => fn(acc),value)Copy the code

functor

What is a Functor?

Definition: A functor is an ordinary object (or, in other languages, a class) that implements a map function that generates a new object as it iterates over each object value.

Implement a functor

In short, a functor is a container that holds values. And the functor is an ordinary object. We can create a container (that is, an object) that can hold any value passed to it.

const Container = function (value) {
  this.value = value
}

let testValue = new Container(1)
// => Container {value:1}
Copy the code

We add a static method to the Container that lets us omit the new keyword when creating new Containers.

Container.of = function (value) {
  returnNew Container(value)} // Now we can create container.of (1) // => Container {value:1}Copy the code

2. The function needs to implement the map method. The map function fetches the value from the Container, and the passed function calls the value as an argument and puts the result back into the Container.

Why do we need the map function? The Container we implemented above just holds the values passed to it. But holding values has almost no application scenarios, and what the map function does is allow us to call any function with the value held by the current Container.

Container.prototype.map = function (fn) {
  returnContainer. Of (fn(this.value))} // Then we implement a double operation on a numberlet double = (x) => x + x;
Container.of(3).map(double)
// => Container {value: 6}
Copy the code

3. Map returns a Container instance with a value as the result of the passed function, so we can chain operations.

Container.of(3).map(double).map(double).map(double)
// => Container {value: 24}
Copy the code

From the above implementation, we can see that the functor is an object that implements the Map contract. Functor is a concept that seeks a contract, which is simply to implement a map. Depending on how you implement the map function, there are different types of functors, such as MayBe and Either

What can functors be used for? Before we use the tap function to solve the problem of debugging code error function, how to handle the problem in the code more function, then we need to use the following MayBe functor

MayBe functor

Let’s start by writing an upperCase function to assume a scenario

let value = 'string';
functionUpperCase (value) {// To avoid errors, we need to write a judgment like thisif(value ! = null || value ! = undefined)return value.toUpperCase()
}
upperCase(value)
// => STRING
Copy the code

As shown above, we often need to determine null and undefined cases in our code. Let’s look at the implementation of the MayBe functor.

// MayBe similar to the Container aboveexport const MayBe = function (value) {
  this.value = value
}
MayBe.of = function (value) {
  returnNew MayBe(value)} // extra isNothing may.prototype. isNoting =function () {
  returnthis.value === null || this.value === undefined; } // functors must have a map, but the implementation of the map MayBe differentfunction(fn) {
  returnthis.isNoting()? MayBe. Of (null):MayBe. Of (fn(this.value))let value = 'string';
MayBe.of(value).map(upperCase)
// => MayBe { value: 'STRING' }
letNullValue = null maybe. of(nullValue).map(upperCase)Copy the code

Either functor

MayBe.of("tony")
  .map(() => undefined)
  .map((x)f => "Mr. " + x)
Copy the code

MyaBe {value: null} = MyaBe {value: null} = MyaBe {value: null} = MyaBe {value: null} That’s where we need the Either functor, which solves the branch expansion problem.

const Nothing = function (value) {
  this.value = value;
}
Nothing.of = function (value) {
  return new Nothing(value)
}
Nothing.prototype.map = function (fn) {
  return this;
}
const Some = function (value) {
  this.value = value;
}
Some.of = function (value) {
  return new Some(value)
}
Some.prototype.map = function (fn) {
  return Some.of(fn(this.value));
}

const Either = {
  Some,
  Nothing
}

Copy the code

Pointed functor

A functor is simply an interface that implements the Map contract. The 50% functor is also a subset of functors that has an interface that implements the OF contract. We also implement the of methods in MayBe and Either to create containers without using the new keyword. So MayBe and Either can both be called conservative functors.

ES6 adds array. of, which makes the number composition a conservative functor.

Monad functor

The MayBe functor is likely to be nested, and if it is, it will be difficult to proceed with the real value. You have to go deep inside MayBe.

letjoinExample = MayBe.of(MayBe.of(5)); Joinexample.map ((MayBe) => {// MayBe {value: MayBe {value: 5}}return insideMayBe.map((value) => value + 4)
})
// => MayBe { value: MayBe { value: 9 } }
Copy the code

We can now implement a Join method to solve this problem.

// If isNothing checks, return its value maybe.prototype.join =function () {
  return this.isNoting()? MayBe.of(null) : this.value
}
Copy the code
letjoinExample2 = MayBe.of(MayBe.of(5)); // => MayBe {value: MayBe {value: 5}} joinExample2.join().map((value) => value + 4) // => MayBe { value: 9 }Copy the code

To extend this, we extend the chain method.

MayBe.prototype.chain = function (fn) {
  return this.map(fn).join()
}
Copy the code

You can expand the nested MayBe by calling chain.

let joinExample3 = MayBe.of(MayBe.of(5));
// => MayBe { value: MayBe { value: 5 } }


joinExample3.chain((insideMayBe) => {
  return insideMayBe.map((value) => value + 4)
})
// => MayBe { value: 9 }
Copy the code

Monad is essentially a functor with a chain method. Only the MayBe of and map is a functor, and the chain functor is a Monad.

conclusion

Is JavaScript functional programming language?

Functional programming insists that functions must take at least one argument and return a value, but JavaScript allows us to create a function that takes no arguments and actually returns nothing. So JavaScript is not a purely functional language, it’s more of a multi-paradigm language, but it fits nicely into the functional programming paradigm.

supplement

1. Pure functions are mathematical functions

function generateGetNumber() {
  let numberKeeper = {}
  return function (number) {
    returnnumberKeeper.hasOwnProperty(number) ? NumberKeeper [number] = number + number}} const getNumber = generateGetNumber() getNumber(1) getNumber(2)...... GetNumber (9) getNumber(10) {1: 2 2: 4 3: 6 4: 8 5: 10 6: 12 7: 14 8: 16 9: 18 10: 20}Copy the code

Now we specify that getNumber only accepts arguments in the range 1-10, so the return value must be one of the values in numberKeeper. With that in mind, let’s examine getNumber, which takes an input and maps the output to a given range (here the range is 10). Inputs have mandatory, corresponding outputs, and there is no input that maps the two outputs.

Let me look again at the definition of a mathematical function (Wikipedia)

In mathematics, a function is a relationship between a set of inputs and a set of permissible outputs, with the property that each input relates exactly one output. The inputs to a function are called parameters and the outputs are called values. For a given function, the set of all allowed inputs is called the domain of that function, and the set of allowed outputs is called the range.

According to our analysis of getNumber, as opposed to the definition of mathematical functions, we will find exactly the same. Our getNumber function above has a range of 1-10 and values of 2,4,6… 18, 20

2, the instance,

Examples of all the concepts in this article can be found at github.com/qiqihaobenb… Get, you can open the corresponding comment to actually execute.

3, recommendation of books

“JavaScript ES6 functional programming classic”, I strongly recommend students who want to start functional programming to have a look, the book is a little old, you can skip the tool introduction and so on, the key to look at its internal ideas, the most important is that this book is very thin, almost like a comic book similar.

4. Recommended articles (non-cited articles)

  1. JS Functional Programming (PART 1)
  2. Functional programming from an embarrassing interview question
  3. Functional programming introductory tutorial
  4. A little practice in functional programming