define

Abstract the operation process and describe the mapping between data (functions)

  • First class citizens
  • Higher-order functions
  • closure

Higher-order functions

Abstraction can mask details and abstract away general problems

closure

You can call an inner function of a function from another scope and access its scope members

Essence: Functions are placed on an execution stack at execution time, and removed from the stack when the function completes execution, but the scope member on the heap cannot be freed because it is referenced externally, so the inner function can also access the external member

Pure functions

Concept: The same input always produces the same output. There were no observable side effects

Eg: Slice (pure function)/splice(impure function)

The characteristics of

Functional programming does not preserve the results of calculations so variables are immutable (stateless);

You can pass the result of a function to another function for processing;

Lodash (for pure functions)

The benefits of pure functions

  • cacheable
// memory function const _ = require("lodash"); function getArea(r) { console.log(r) return Math.PI * r * r } // let getAreaWithMemory = _.memoize(getArea) function memoize(f) { let cache = {} return function() { let args = JSON.stringify(arguments); cache[args] = cache[args] || f.apply(f, arguments) return cache[args] } } let getAreaWithMemory = memoize(getArea) getAreaWithMemory(4); getAreaWithMemory(4); getAreaWithMemory(4);Copy the code
  • testable

    • To assert that
  • Parallel processing

    • Pure functions do not need to access shared memory data and can run pure functions at will

Side effects of pure functions

If the function depends on external state, the output is not guaranteed to be the same

Sources of side effects:

  • The global variable

  • The configuration file

  • The database

  • User input

  • .


Currie, \color{red}{corrified}

Corrification in Lodash

_.curry(fn)

  • Function: creates a function that takes one or more arguments of func, executes func if all the arguments required by func are provided and returns the result of the execution. Otherwise continue to return the function and wait for the remaining arguments to be received.
  • Parameters: functions that require currization
  • Return value: The currified function

Eg: Filter empty strings in the array

const _ = require("lodash") const match = _.curry(function (reg, STR) {return str.match(reg)}) const haveSpace = match(/\s+/g) const filter = _. Curry (fn => arrary => arrary.filter(fn)) const findSpace = filter(haveSpace) console.log(findSpace(['hello world', 'helloworld']))Copy the code

implementation

function curry(fn) { return function curriedFn(... Args) {if (args. Length < fn.length) {return function () {return curriedFn(... args.concat(... Array.from(arguments)))}} return fn. Apply (fn, args) // args) } } function getSum(a, b, c) { return a + b + c; } const curried = curry(getSum); console.log(curried(1, 2, 3)); console.log(curried(1)(2)(3)); console.log(curried(1, 2)(3));Copy the code

conclusion

  • Currie, Allows us to pass a function parameter are less already remember some fixed parameters of new function \ color {red} {allows us to pass a function parameter are less a already remember some fixed parameters of new function} allows us to pass a function parameter are less an already remember some fixed parameter’s new schedule The number
  • Caching of functions
  • Functions are more flexible and less granular
  • To convert a multivariate function into a unary function, you can combine functions

Function composition

Concept: If a function needs to be processed by multiple functions to get the final value, it is possible to combine the intermediate functions into a single function


  • Function combinations are executed from right to left by default Color {red}{function combinations are executed from right to left by default}

Eg: Fetch the last element of the array

// function compose (f, g) { return function (x) { return f(g(x)) } } function first (arr) { return arr[0] } function reverse (arr) { return Arr.reverse ()} // Run let last = compose(first, reverse) console.log(last([1, 2, 3, 4]) from right to left.Copy the code

Combinatorial functions in Lodash

_. FlowRight Executes from right to right

const _ = require("lodash")

const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()

const fn = _.flowRight(toUpper, first, reverse)

console.log(fn(["a","b","c","d"]));

//D
Copy the code

Analog implementation

const _ = require("lodash") const reverse = arr => arr.reverse() const first = arr => arr[0] const toUpper = s => // Reduce (fn,initValue) receives 2 parameters. The first is the iterator function, which processes each element in the array from left to right. Accumulator is an accumulator, currentValue, currentIndex, and Array. // accumulator is the return value from the last call to this function. The first time for the initialValue | | arr [0] / / the value of the currentValue array function is processing. The first time the initialValue | | arr [1] / / currentIndex array function is dealing with the index of the array / / / / array function call initValue the reduce of the optional second parameter, The initial value of the accumulator. If no, the accumulator's first value is currentValue; // const compose = function(... args) { // return function(value) { // return args.reverse().reduce((acc, fn) => { // return fn(acc) // }, Const compose = (const compose = (... args) => value => args.reverse().reduce((acc, fn) => fn(acc), value) const fn = compose(toUpper, first, reverse)Copy the code

Combination of functions satisfies associative law

let f = compose(f, g, h)
let associative = compose(compose(f, g), h) == compose(f, compose(g, h))
Copy the code

Debug combinatorial function

eg:

// NEVER GIVE UP --> never give up
const _ = require("lodash")

const split = _.curry((separator,  str) => _.split(str, separator))

// _.toLower

const join = _.curry((separator, arr) => _.join(arr, separator))

const f = _.flowRight(join('-'),_.toLower,split(' '))

console.log(f('NEVER GIVE UP'))

//n-e-v-e-r-,-g-i-v-e-,-u-p
Copy the code

Obviously, the current result is wrong, so how to debug, we need to combine the characteristics of combination functions to deal with, after the function that may execute the error, a special function for printing the result of the stage, and return the value passed in unchanged, as follows:

const trace = _.curry((tag, v) => { console.log(tag, V) return v}) const f = _. FlowRight (join('-'),trace(" toLower "), trace(" split ") ['NEVER', 'GIVE', 'UP'] // execute toLower and execute result: NEVER, GIVE, UPCopy the code

ToLower returns a string, which needs to be modified

const map = _.curry((fn, arr) => _.map(arr, fn))

const f = _.flowRight(join('-'),map(_.toLower),split(' '))

console.log(f('NEVER GIVE UP'))
// never-give-up
Copy the code

So we get the results we want

lodash/fp

  • The FP module in LoDash provides a friendly approach to functional programming
  • Immutable auto-curried iteratee-first data-last and other methods are provided

eg:

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

The map method in lodash/ FP

Const _ = the require (" lodash ") the console. The log (_. The map ([' 23 ', '8', '10'], parseInt)) / / errors [23, NaN, Console. log(_. Map (['23', '8', '10'], // fp module const fp = require("lodash/fp") console.log(fp.map(parseInt, ['23', '8', '10']))Copy the code

Point Free

Point free is a stylistic means of function composition

  • There is no need to specify the data to be processed
  • You just need to synthesize the operation
  • You need to define some auxiliary basic operation functions

eg:

// point free
const fp = require("lodash/fp")

// const firstLetterToUpper = fp.flowRight( fp.join(". "),fp.map(fp.first),fp.map(fp.toUpper),fp.split(" "))
const firstLetterToUpper = fp.flowRight( fp.join(". "),fp.map(fp.flowRight(fp.first,fp.toUpper)),fp.split(" "))

console.log(firstLetterToUpper("Hello world"))
Copy the code

As you can see, we merged fp.first with fp.toUpper to reduce array traversal

Functor Functor

  • Container: Contains values and deformation relationships of values (functions)
  • Functor: a special container implemented by an ordinary object that has a map method that runs a function to manipulate values (deformation relationships)
class Container { static of (value) { return new Container(value) } constructor(value) { this._value = value } map(fn) {  return Container.of(fn(this._value)) } } let f = Container.of(5).map(x => x + 1).map(x => Math.pow(x, 2)) console.log(f);Copy the code

The container class can be used as a functor, and fn passed in to the map is used to transform the functor. But what if the functor is null undefined? This is where the Maybe functor is used

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 r = MayBe.of().map(x => x.toUpperCase()) console.log(r)Copy the code

The MayBe functor can solve the problem of input value exceptions, but if map is called multiple times without being able to locate which map phase is the problem, then the Either functor is introduced

Either functor

We implement a Either functor

//either constructor class Container {static of (value) {return new Container(value)} constructor(value) {this._value = value  } map(fn) { return Container.of(fn(this._value)) } } class Left extends Container { map(fn) { return this } } class Right extends Container { map(fn) { return Right.of(fn(this._value)) } } function parseJSON(str) { try { return Right.of(JSON.parse(str)) } catch(e) { return Left.of({error: e.message}) } }Copy the code

Let’s call

let r = parseJSON("{key: a}")
// Container { _value: { error: 'Unexpected token k in JSON at position 1' } }

let r = parseJSON('{"key": "a"}')
// Container { _value: { key: 'a' } }
Copy the code

IO functor

  • IO functor_valuePhi is a function, and we’re treating the function as a value
  • IO functors can store impure operations to_valueIn, the impure operations are deferred (lazy execution) and only pure operations are wrapped
  • Leave impure operations to the caller
const fp = require("lodash/fp") class IO { static of(x ) { return new IO(() => x) } constructor(fn) { this._value = fn }  map(fn) { return new IO(fp.flowRight(fn, this._value)) } } let r = IO.of(process).map(p => p.execPath) console.log(r._value())Copy the code

R._value () finally performs an impure operation

Task asynchronous execution

Folktale is a standard functional programming library

  • andlodash,ramdaThe difference is that there are not many functional functions provided
  • Only functional processing operations such as:compose currySuch as, functorTask,Either,MayBeEtc.

Here is a simple application: read some qualifying information from a file

const fs = require("fs")
const { task } = require("folktale/concurrency/task")
const { split, find } = require("lodash/fp")

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: value => console.log(value)
    })
Copy the code

Pointed functor

The -50% functor is a functor that implements the of static method

  • ofThe method is to avoid usingnewTo create objects, and the deeper meaning isofMethod to put values in contextcontextTo put the value into the container, usemapTo process values)

Monad functor

Monad functors are functors that contain static methods of JOIN and of and are mainly used to solve functor nesting problems

implementation

const fs = require('fs') const fp = require('lodash/fp') let readFile = function (filename) { return new IO(function () { return fs.readFileSync(filename, 'utf-8') }) } let print = function (x) { return new IO(function () { return x }) } class IO { static of(x) { return new IO(function () { return x }) } constructor(fn) { this._value = fn } map(fn) { return new IO(fp.flowRight(fn, this._value)) } join() { return this._value() } flatMap(fn) { return this.map(fn).join() } } let r = ReadFile ('package.json').map(fp.toupper) // When we want to merge a function, FlatMap (print) // flatMap '.join() 'if return value is a functorCopy the code