Preliminary solution of js function programming

Functional programming

Why learn functional programming

An interesting survey shows that the average amount of code for programmers around the world is 5 lines / person day. In other words, you spend 7.5 hours a day reading the code (mainly studying where and how to write the five letter code), and then spend 30 minutes writing the five lines of code. This is the programmer's work of the day.
The code we write will be handed over to others or maintained by ourselves in the future, so how to improve the readability of the code is a very important task. Functional programming can do this well, so learning functional programming is very important: improve code readability. Of course, there are other factors:

  • With the popularity of react, functional programming has attracted more and more attention
  • vue3 also embraces functional programming
  • Functional programming can abandon this
  • Convenient for testing and parallel processing
  • There are many libraries that can help us with functional development

What is function programming

  • Baidu definition: simply put, "functional programming" is a "programming paradigm", that is, the methodology of how to write programs.
  • Functional programming (FP) is to help pure functions realize fine-grained functions, and then combine fine-grained functions.

Function VS program

  • The function in functional programming is not a function in function (method), but a function in Mathematics (mapping relationship, y = f(x));
  • A program is an arbitrary set of functions. It may or may not have many input values. It may or may not have an output value (return value).
  • The function receives the input value and explicitly return s the value

Programming paradigm

What is the programming paradigm

A pattern used in the process of solving problems in programming, which is reflected in the way of thinking and code style.

Several common programming paradigms

  • Imperative programming
  • object-oriented programming
  • Functional programming

Different programming paradigms have different code expressions

  • Imperative programming
const name = 'world';
console.log(`hello ${name}`);

  • Object oriented programming: abstract the whole program into an object in real life, which will contain properties and methods. Through the concept of class, we have a factory for generating objects. Create an object using the new keyword, and finally call the object.
const Program = function(name) {
    this.name = name;
    this.greet = function() {
        console.log(`hello ${this.name}`);
    };
};
const program = new Program('world');
program.greet();
  • Functional programming: complete the function of the program through the call of functions
const greet = function(name) {
    return `hello ${name}`
};
console.log(greet('world'));

Different programming paradigms apply to different scenarios

  • Imperative programming: process sequence, operation instructions on the machine
  • Object oriented programming: objects, games, character models
  • Functional language: data processing, business front end

Misunderstanding of functional programming

There will be a misunderstanding when you just come into contact with functional programming: only 100% of a program uses functional programming can it be called functional programming. Nothing is absolute. We usually use the combination of imperative programming, object-oriented programming, functional programming and other programming paradigms in the project process.

Function is a first-class citizen

Why functional first-class citizens

  • <Programming Language Pragmatics>

In general, a value in a programming language is said to have first-class status if it can be passed as a parameter, returned from a subroutine, or assigned into a variable.

In other words, in the programming language, first-class citizens can be used as function parameters, function return values, and variables.

  • For example, strings are first-class citizens in almost all programming languages. Strings can be used as function parameters, strings can be used as function return values, and strings can also be assigned to variables.

  • For various programming languages, functions are not necessarily first-class citizens.

  • In js, a function is an ordinary object (through new Function()), we can store the function in variables and arrays, and it can also be used as the parameter and return value of another function. We can even construct a new function through new Function('alert(1) 'when the program is running.

ps: the reason why we say functional first-class citizens is that in js language, functions meet the characteristics of first-class citizens:

  • Functions can be stored in variables
  • Functions can be used as arguments
  • Function can be used as a return value

Assign a function to a variable

let fn = function(){
	console.log('hello world');
}

fn();

Function as parameter

[1, 2, 3].map( item => item ** 2);

Function as return value

function getGreep() {
	const greepPre = 'hello ';
	return function(name) {
		console.log(`${greepPre}${name}`)
	}
}

let greep = getGreep();
greep('world');

Why learn functional first-class citizens

Functional first-class citizens are the basis for us to learn higher-order functions, Coriolis, etc.

Higher order function

What is a higher order function

Function is acceptable and returns any type of value. If a function can accept or return one or more functions, it is called a higher-order function.
That is, a function can be called a high-order function if it can meet any of the following characteristics:

  • You can accept a function as an argument
  • You can take a function as a return value

Function as parameter

function forEach(arr, fn) {
	for(let i = 0; i < arr.length; i++){
		fn(arr[i]);
	}
}
forEach([1,2,3], item => console.log(item + 1));

Function as return value

function getGreep() {
	const greepPre = 'hello ';
	return function(name) {
		console.log(`${greepPre}${name}`)
	}
}

let greep = getGreep();
greep('world');

Significance of using higher-order functions

  • Help us shield the details, just focus on the target
  • Abstract general problems

Common higher order functions

forEach,map,ilter,every,some,find,reduce,sort

closure

What is a closure

When the scope of another function exists inside a function, operate on the current function. When an internal function references a variable from an external function, this is called a closure.
In fact, closure is that it can record and access variables outside its scope, even when the function is executed in different scopes

function getGreep() {
	const greepPre = 'hello ';
	return function(name) {
		console.log(`${greepPre}${name}`)
	}
}

let greep = getGreep();
greep('world');

The essence of closure

When a function is executed, it will be placed on an execution stack. After the function is executed, it will be removed from the execution stack. However, the scope members on the heap cannot be released because they are externally referenced. Therefore, internal functions can still access the members of external functions.

// Generate a function that calculates the power of a number
function makePower (power) {
	return function (x) {
	  return Math.pow(x, power);
	}
}
let power2 = makePower(2);
let power3 = makePower(3);
console.log(power2(4));
console.log(power3(4));

Pure function

What is a pure function

The same input will always get the same output without any observable side effects.

Slice and slice of array are pure function and impure function respectively

  • slice returns the specified part of the array without changing the original array
  • splice operates on the array and returns the array, which will change the original array
let numbers = [1, 2, 3, 4, 5]
 // Pure function
 numbers.slice(0, 3)
 // => [1, 2, 3]
 numbers.slice(0, 3)
 // => [1, 2, 3]
 numbers.slice(0, 3)
 // => [1, 2, 3]
 // Impure function
 numbers.splice(0, 3)
 // => [1, 2, 3]
 numbers.splice(0, 3)
 // => [4, 5]
 numbers.splice(0, 3)
 // => []

Benefits of pure functions

  • Cacheable: because pure functions always have the same results for the same input, the results of pure functions can be cached
const _ = require('lodash')
function getArea (r) {
 return Math.PI * r * r
}
let getAreaWithMemory = _.memoize(getArea)
console.log(getAreaWithMemory(4))
  • Testability: pure functions make testing easier
  • parallel processing
  • In a multithreaded environment, parallel operation of shared memory data is likely to occur unexpectedly
  • Pure functions do not need to access shared memory data, so pure functions (web workers) can be run arbitrarily in a parallel environment

side effect

// Impure
let mini = 18
function checkAge (age) {
 return age >= mini
}
// Pure (hard coded, which can be solved by coritization later)
function checkAge (age) {
 let mini = 18
 return age >= mini
}

Side effects make a function impure (as in the above example). Pure functions return the same output according to the same input. If the function depends on the external state, the same output cannot be guaranteed, which will bring side effects.

Sources of side effects:

  • configuration file
  • database
  • Get user input

All external interactions may bring side effects. The side effects also reduce the universality of the method and are not suitable for expansion and reusability. At the same time, the side effects will bring security risks to the program and uncertainty to the program, but the side effects cannot be completely prohibited and controlled within the controllable range as far as possible.

currying

How to carry out coritization

When a function has multiple parameters, first pass some parameters to call it (these parameters will never change in the future), and then return a new function to receive the remaining parameters and return the results.

function checkAge (age) {
 let min = 18
 return age >= min
}
// Ordinary pure function
function checkAge (min, age) {
 return age >= min
}
checkAge(18, 24)
checkAge(18, 20)
checkAge(20, 30)
// currying 
function checkAge (min) {
 return function (age) {
  return age >= min
}
}
// ES6 writing method
let checkAge = min => (age => age >= min)
let checkAge18 = checkAge(18)
let checkAge20 = checkAge(20)
checkAge18(24)
checkAge18(20)

Coriolis function

// Ordinary pure function
function checkAge (min, age) {
 return age >= min
}

function curry (func) {
 return function curriedFn (...args) {
  // Determine the number of arguments and formal parameters
  if (args.length < func.length) {
   return function () {
    return curriedFn(...args.concat(Array.from(arguments)))
  }
 }
  // If the number of arguments and formal parameters is the same, call func to return the result
  return func(...args)
 }
}

let checkAge18 = curry(checkAge)(18)
let checkAge20 = curry(checkAge)(20)
checkAge18(24)
checkAge18(20)

Coriolis function in lodash curry(func)

  • Function: create a function that receives one or more func parameters. If all the parameters required by func are provided, execute func and return the execution result. Otherwise, continue to return the function and wait to receive the remaining parameters.
  • Parameter: function to be coriolised
  • Return value: coriolised function
const _ = require('lodash')
// Functions to be coriolised
function getSum (a, b, c) {
 return a + b + c
}
// Coriolised function
let curried = _.curry(getSum)
// test
curried(1, 2, 3)
curried(1)(2)(3)
curried(1, 2)(3)

summary

  • Coriolism allows us to pass fewer parameters to a function to get a new function that has remembered some fixed parameters
  • This is a 'cache' of function parameters
  • Make the function more flexible and make the granularity of the function smaller
  • Multivariate functions can be converted into univariate functions, and functions can be combined to produce powerful functions

Function combination

problem

  • Pure functions and Coriolis make it easy to write onion code h(g(f(x)))
  • Get the last element of the array and convert it to uppercase letters toUpper(_.first(_.reverse(array)))

What is a function combination

  • If a function needs to be processed by multiple functions to get the final value, the functions of the intermediate process can be combined into one function at this time,
fn = compose(f1, f2, f3)
b = fn(a
  • Function combinations are executed from right to left by default
// Multi function combination
function compose (...fns) {
 return function (value) {
  return fns.reverse().reduce(function (acc, fn) {
   return fn(acc)
  }, value)
 }
}

const _ = require('lodash')
const toUpper = s => s.toUpperCase()
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const f = compose(toUpper, first, reverse)
console.log(f(['one', 'two', 'three']))

Combinatorial functions in lodash

In lodash, the combination function flow() or flowRight() can combine multiple functions

  • flow() runs from left to right
  • flowRight() runs from right to left and uses more
const _ = require('lodash')
const toUpper = s => s.toUpperCase()
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const f = _.flowRight(toUpper, first, reverse)
console.log(f(['one', 'two', 'three']))

Characteristics of function combination

The combination of functions satisfies the combination law (the combination law in Mathematics): in compose(f, g, h), we can combine g and h, or f and G, and the results are the same.

// Associative law
let f = compose(f, g, h)
let associative = compose(compose(f, g), h) == compose(f, compose(g, h))
// true

lodash/fp

The fp module of lodash provides a practical and friendly method for functional programming

// lodash module
const _ = require('lodash')
_.map(['a', 'b', 'c'], _.toUpper)
// => ['A', 'B', 'C']
_.map(['a', 'b', 'c'])
// => ['a', 'b', 'c']
_.split('Hello World', ' ')
// lodash/fp module
const fp = require('lodash/fp')
fp.map(fp.toUpper, ['a', 'b', 'c'])
fp.map(fp.toUpper)(['a', 'b', 'c'])
fp.split(' ', 'Hello World')
fp.split(' ')('Hello World')

Functor

Functor is the most important data type in functional programming, and it is also the basic operation unit and functional unit.

What is functor

  • Container: contains a deformation relationship between values and values (this deformation relationship is a function)
  • Functor: it is a special container, which is implemented through an ordinary object. The object has a map method. The map method can run a function to process the value (deformation relationship)
// A container that wraps a value
class Container {
 // of static method, you can omit the new keyword to create an object
 static of (value) {
  return new Container(value)
}
 constructor (value) {
  this._value = value
}
 // Map method, pass in the deformation relationship, and map each value in the container to another container
 map (fn) {
  return Container.of(fn(this._value))
}
}
// test
Container.of(3)
.map(x => x + 2)
.map(x => x * x)
  • The operation of functional programming is not directly operated on the value, but completed by the functor
  • A functor is an object that implements a map contract
  • We can think of a functor as a box in which a value is encapsulated
  • To handle the values in the box, we need to pass a value processing function (pure function) to the map method of the box, which processes the values
  • Finally, the map method returns a box (functor) containing the new value

MayBe functor

  • We may encounter many errors in the process of programming, and we need to deal with these errors accordingly
  • The function of MayBe functor is to handle the external null value (control the side effects within the allowable range)
class MayBe {
 static of (value) {
	return new MayBe(value)
 }
 constructor (value) {
  this._value = value
 }
 // If the null value is deformed, the functor with null value is returned directly
 map (fn) {
  return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
 }
 isNothing () {
  return this._value === null || this._value === undefined
 }
}

// Pass in specific value
MayBe.of('Hello World')
.map(x => x.toUpperCase())
// When null is passed in
MayBe.of(null)
.map(x => x.toUpperCase())
// => MayBe { _value: null }

Either functor

  • Either of the two, similar to if... else
  • Exceptions will make the function impure. Either functor can be used for exception handling
class Left {
 static of (value) {
  return new Left(value)
 }
 constructor (value) {
  this._value = value
 }
 map (fn) {
  return this
 }
}

class Right {
 static of (value) {
  return new Right(value)
 }
 constructor (value) {
  this._value = value
 }
 map(fn) {
  return Right.of(fn(this._value))
 }
}

function parseJSON(json) {
 try {
  return Right.of(JSON.parse(json));
 } catch (e) {
  return Left.of({ error: e.message});
 }
}

let r = parseJSON('{ "name": "zs" }')
    .map(x => x.name.toUpperCase())
console.log(r)

IO functor

  • In IO functor_ Value is a function. Here, the function is treated as a value
  • IO functors can store impure actions in_ value, delay the execution of this impure operation (lazy execution), and wrap the current pure operation
  • Leave the impure operation to the caller
const fp = require('lodash/fp')
class IO {
 static of (x) {
  return new IO(function () {
   return x
 })
}
 constructor (fn) {
  this._value = fn
 }
 map (fn) {
  // Combine the current value and the passed in fn into a new function
  return new IO(fp.flowRight(fn, this._value))
 }
}
// call
let io = IO.of(process).map(p => p.execPath)
console.log(io._value())

Task asynchronous execution

folktale is a standard functional programming library

const fs = require('fs');
const { task } = require('folktale/concurrency/task')

function readFile(filename) {
 return task(resolver => {
  fs.readFile(filename, 'utf-8', (err, data) => {
   if (err) resolver.reject(err)
   resolver.resolve(data)
  })
 })
}
// Call run to execute
readFile('package.json')
 .map(split('\n'))
 .map(find(x => x.includes('version')))
 .run().listen({
  onRejected: err => {
   console.log(err)
 },
  onResolved: value => {
   console.log(value)
 }
})

Keywords: Javascript Front-end

Added by nolos on Sat, 23 Oct 2021 13:12:02 +0300