What is Functional Programming?
Functional programming is a programming paradigm, it will be considered as functional operation of computing, and avoid using program state variable and object in functional programming, the function is the first kind of object, meaning that a function, both can be used as input parameters of other functions, can also return a value from a function, has been modified or assigned to a variable.
Functions in functional programming are not functions (methods) in programs, but mappings in mathematics, such as y = sin(x), the relationship between x and y
Higher-order functions
Let’s take a look at wikipedia’s definition of higher-order functions:
In mathematics and computer science, a higher-order function is one that satisfies at least one of the following conditions:
- Takes one or more functions as input
- Output a function
In simple terms, when we define a function that takes a function or returns a function, the function is a higher-order function. For JavaScript, both are true.
Function as argument
function callbackFn() {
console.log('Higher order function')}function fnParam(fn) {
console.log('Ordinary function')
// the fn function is passed as an argument
fn()
}
/ / test
fnParam(callbackFn)
Copy the code
In the example above, the fn function is passed as an argument and is called a callback function
Callbacks are the cornerstone of asynchrony. In the example above, callbackFn is passed as an argument to the fnParam function. The execution of callbackFn is determined by fnParam.
Many of these functions are built into the JavaScript language as arguments to higher-order functions, such as
- Array.prototype.map
- Array.prototype.filter
- Array.prototype.find
- .
Take array.prototype. map as an example to explain in detail
Use higher-order functions
// map Example
const arr = [1.2.3.4]
const result = arr.map(item= > item * 2)
console.log(arr) // [1, 2, 3, 4]
console.log(result) // // [2, 4, 6, 8]
Copy the code
Higher-order functions are not used
// Synchronize code to implement map function
const arr = [1.2.3.4]
const result = []
for(let i = 0; i < arr.length; i++) {
const item = arr[i]
result.push(item * 2)}console.log(arr) // [1, 2, 3, 4]
console.log(result) // // [2, 4, 6, 8]
Copy the code
Analog implementation
/ / map
const map = (arr,fn) = >{
let res = [];
for(let i = 0; i < arr.length; i++){
res.push(fn(i))
}
return res;
}
/ / test
const newAry = map([1.23.4.5.6].item= >item**2)
console.log(newAry) // [1, 529, 16, 25, 36]
Copy the code
/ / analog filter
function filter(arr,fn){
let res = [];
for(let i = 0; i < arr.length; i++){
if(fn(arr[i])){
res.push(arr[i])
}
}
return res
}
/ / test
const ary = filter([1.2.34.5.6].function(item){
return item%2= = =0;
})
console.log(ary) / / [6] 2, 34,
Copy the code
Function as the return value
Let’s look at a simple example
function getMsg() {
const msg = 'hello msg'
// function as return value
return function() {
console.log('msg')}}/ / test
const firstAction = getMsg()
firstAction() // hello msg
Copy the code
The result of getMsg is a function assigned to firstAction, which is the function returned. There’s going to be a closure
Next we take advantage of these two features of higher-order functions to implement a function that can only be called once
function once(fn) {
let done = false
debugger
return function() {
if(! done) { done =true
return fn.apply(this.arguments)}}}const pay = once(function(money) {
console.log('Paid'+ money)
})
pay(5)
pay(5)
pay(5)
Copy the code
View closures in the browser environment
Closures can call the inner function of a function in another scope and access members of that function’s scope (that is, extend scope)
Closure nature: Functions are placed on an execution stack at execution time, and are removed from the stack when the function completes execution. However, scoped members on the heap cannot be freed because they are referenced externally, so internal functions can still access external function members and are placed on an execution stack at execution time
Currie,
Call a function that takes only part of its arguments (those arguments never change), and then return a new function that takes the rest of the arguments and returns the result
// A simple example of currization
function checkAge(min) {
return function(age) {
return age >= min
}
}
// Only partial parameters are passed
const age18 = checkAge(18);
// Accept the remaining parameters
console.log(age18(22));
console.log(age18(55));
console.log(age18(17));
Copy the code
Simulate currification in LoDash (key point: execute function only if argument is equal to the number of parameters, otherwise cache parameters)
function curry(fn) {
return function curriedFn(. args) {
// Determine the number of arguments and parameters
// function. length The length property specifies the number of parameters of the Function
if (args.length < fn.length) {
return function() {
// Cache parameterscurriedFn(... args.concat(Array.from(arguments)))}}returnfn(... args) } }/ / test
function getSum(a, b, c) {
rerturn a + b + c
}
const curried = curry(getSum)
console.log(curryFun(1.2.3));
console.log(curryFun(1.2) (3));
console.log(curryFun(1) (2.3));
Copy the code
Function composition
If a function has to go through several functions to get the final value, then you can combine the intermediate functions into a single function
Functions are conduits of data, connecting multiple conduits to allow data to pass through to form the final result
Function combinations are executed from right to left by default, with each function taking one argument and returning the corresponding result
// A function that implements a function combination function
function compose(. args) {
return function(value) {
// First invert the array, then execute the functions in turn through reduce
return args.reverse().reduce(function(acc, fn) {
return fn(acc)
}, value)
}
}
// arrow function
const compose_arrow = (. args) = > value= > args.reverse().reduce((acc, fn) = > fn(acc), value)
/ / test
const reverse = (arr) = >arr.reverse();
const first = (arr) = >arr[0];
const test1 = compose(first,reverse)
const test2 = compose_arrow(first,reverse)
console.log(test1([1.5.6.4.8.6.'dsadasdsd']));
console.log(test2([1.5.6.4.8.6.'dsadasdsd']));
Copy the code
Pure functions
The same input gets the same output without any observable side effects
Pure function example
const array = [1.2.3.4.5]
console.log(array.slice(0.3))
console.log(array.slice(0.3))
console.log(array.slice(0.3))
// Execute three times to get the same result
Copy the code
Example of impure functions
const array = [1.2.3.4.5]
console.log(array.splice(0.3))
console.log(array.splice(0.3))
console.log(array.splice(0.3))
// Execute three times with different results
Copy the code
Benefits of pure functions
- Cacheable: Because a pure function always has the same output for the same input, its results can be cached using closures (memory function)
Implement a memory function
function getArea (r) {
console.log(r);
return Math.PI * r * r
}
function memoize(fn) {
// Memory objects
const cache = {}
// Closure functions
return function() {
const key = JSON.stringify(arguments)
// If there is a cache, fetch the cache directly; if there is no cache, recalculate.
cache[key] = cache[key] || fn.apply(fn, arguments)
return cache[key]
}
}
/ / test
let memoizeFun = memoize(getArea)
console.log(memoizeFun(4));
console.log(memoizeFun(4));
Copy the code
- Testable: Pure functions make testing easier (because pure functions always have inputs and outputs, and unit tests assert that result
- Parallel processing (Web Worker): Operating on shared memory data in parallel in a multi-threaded environment is likely to cause unexpected problems. Pure functions do not need to access shared memory data, so they can be run arbitrarily in parallel
Side effects
Examples of side effects
/ / not pure
let mini = 18
function checkAge(age) {
// Depending on the external variable mini, when mini changes, the same input does not necessarily get the same output
return age >= mini
}
Copy the code
If the function depends on the external state and cannot guarantee the same output, then you have a side effect, which makes the function impure
Side effects: Configuration files, databases, obtaining user input… (All interactions with the outside world have potential side effects)
Side effects can not be completely banned, as much as possible to control their occurrence within a controllable range
Functor (Functor)
Container: contains values and worth distortion relations (this distortion relation is the function)
Functor: a special container implemented by an ordinary object that has a map method that runs a function to manipulate values (deformation relationships)
/ / Functor Functor
class Container {
// Encapsulates the Container
static of(value) {
return new Container(value);
}
constructor(value) {
this._value = value;
}
// Contract object
map(fn) {
/** * fn is a callback function (pure function) * that takes the value handled by fn as an argument to instantiate Container */
return Container.of(fn(this._value)); }}let result = Container.of(5).map((x) = > x + 2).map((x) = > x * x);
console.log(result);
Copy the code
Error: Invalid value input value cannot be judged, resulting in program termination (MapBe functor)
MayBe functor
Function: Handles external null cases (null side effects are allowed)
/ / MayBe functor
class MayBe {
static of(value) {
return new MayBe(value);
}
constructor(value) {
this._value = value;
}
map(fn) {
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value));
}
isNothing() {
return this._value === null || this._value === undefined; }}let result = MayBe.of(null).map((x) = > x.toUpperCase());
console.log(result);
Copy the code
The nullMayBe functor can’t accurately locate the number of steps assigned to null when there are too many chain calls (Either functor)
Either functor
Either of the two, similar to if… else… The processing of
Exceptions make functions impure, and Either functors can be used for exception handling
/ / Either functor
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)); }}// test
function parseJSON(str) {
try {
return Right.of(JSON.parse(str));
} catch (e) {
// Error message saved
return Left.of({ error: e.message }); }}let result3 = parseJSON('{name: zs}');
let result4 = parseJSON('{ "name": "zs" }');
console.log(result3);
console.log(result4);
Copy the code
IO functor
The _value in the IO functor is a function, and we’re treating the function as a value
IO functors can store impure actions in _value, delay execution of the impure operation (lazy execution), and wrap the current operation pure
Leave impure operations to the caller
// Function combination
function compose(. args) {
return function(value) {
// First invert the array, then execute the functions in turn through reduce
return args.reverse().reduce(function(acc, fn) {
return fn(acc);
}, value);
};
}
/ / IO functor
class IO {
static of(value) {
return new IO(function() {
return value;
});
}
constructor(fn) {
this._value = fn;
}
map(fn) {
// Combine the current value and the incoming fn into a new function
return new IO(compose(fn, this._value)); }}// test
const result = IO.of(process).map((p) = > p.execPath);
console.log(result._value());
Copy the code
Problem:.value().value() is not nice to write when calling nested functors to execute results (Monad functors solve this problem)
Monad functor
Monad functor is a linear functor that can be flattened. Monad mainly solves the problem of nested output of IO(IO(x)) functors.
A functor is a Monad if it has both join and of methods and obeies some laws
If function nesting can be combined with functions, functor nesting can be Monad
// Function combination
function compose(. args) {
return function(value) {
// First invert the array, then execute the functions in turn through reduce
return args.reverse().reduce(function(acc, fn) {
return fn(acc);
}, value);
};
}
class Monad {
static of (value) {
The //of method still receives a value, but it encapsulates the value in the function
return new Monad(function(){
return value
})
}
constructor(fn){
// Accept a function to minimize side effects
this.value = fn
}
map(fn){//map is an operation that processes values
// The output is always a function stored in value
return new Monad(compose(fn,this.value))
}
join(){
// This is the flatMap operation
return this.value();
}
flatMap(fn){
//flatMap handles nested functors to avoid repeating the.value() operation
return this.map(fn).join(); }}Copy the code
Functional programming library
Folktale: folktale.origamitower.com/
Folktale is a standard functional programming library that differs from Lodash and Ramada in that it does not provide many functions. It only provides functions for composing,curry, etc., and functors :Task(asynchronous operation),Either,MayBe, etc.
Folktale Task asynchronous operations
// The task implementation is too complex to emulate
// Folktale 2.x is quite different from tasks in 1.x, and 1.0 is closer to the functors we are demonstrating now
// Use 2.3.2 to illustrate
const {task} = require('folktale/concurrency/task');
const _ = require('lodash/fp');
const fs = require('fs');
function readFile (fileName) {
return task(resolver= > {
fs.readFile(fileName,'utf-8'.(err,data) = >{
if(err) resolver.reject(err)
resolver.resolve(data)
})
})
}
readFile('package.json')
.map(_.split('\n'))
.map(_.find(x= >x.includes('version')))
.run()
.listen({
onRejected(err){
console.log(err);
},
onResolved(data){
console.log('resolution version',data); }})Copy the code