Functional programming

introduce

  • Why and what is functional programming
  • Features of functional programming (pure functions, Currization, function combinations, etc.)
  • Application scenarios of functional programming
  • The functional programming library Lodash

Why learn functional programming and what is functional programming

Functional programming is a very old concept, long before the birth of the first computer, the history of functional programming. So why are we learning functional programming now?

  • Functional programming is getting more attention with the popularity of React
  • Vue 3 also began to embrace functional programming
  • Functional programming can discard this
  • Better use of Tree Shaking to filter out useless code during packaging
  • Convenient for testing and parallel processing
  • There are many libraries to help us with functional development: Lodash, underscore, Ramda

[Memory: attention, vue3, this, tree shaking, parallel, library]

What is functional programming?

Functional Programming (FP), FP is one of the Programming paradigms, we often hear of procedural Programming, object-oriented Programming.

  • The way of thinking of object-oriented programming: abstracting things in the real world into classes and objects in the program world, demonstrating the relationship between things and events through encapsulation, inheritance and polymorphism
  • The way of thinking about functional programming: Abstracting things and their relationships from the real world to the program world
    • The essence of the program: according to the input through some operation to obtain the corresponding output, the program development process will involve many functions with input and output
    • X -> f ->y, y=f(x)
    • A function in functional programming is not a function (method) in a program, but a function (mapping) in mathematics, for example: y=sin(X), the relationship between X and y
    • The same input always produces the same output (pure function)
    • Functional programming is used to describe mappings between data (functions)

Memory: Abstraction, INPUT/output, data mapping

/ / the function
let num1 = 2
let num2 = 3
let sum = num1 + num2
console.log(sum)

/ / function type
function add(n1, n2){
	return n1 + n2
}
let sum = add(2.3)
console.log(sum)
Copy the code

Front knowledge

  • Functional first-class citizen
  • Higher-order functions
  • closure

The function is first-class citizen:

MDN first-class Function 1, functions can be stored in variables 2, functions as arguments 3, functions as return values

[Memory: variables, parameters, return values]

In JavaScript, a Function is just an ordinary object (via new Function ()), we can store the Function in a variable/array, and it can be used as an argument and return value to another Function, We can even construct a new Function while the program is running by using new Function (‘alert(1)’).

  • Assign a function to a variable
// 1. Assign functions to variables
let fn = function () { 
	console.log('Hello First-class Function') 
}
fn() 

// 2. An example
const BlogController = { 
    index (posts) { return Views.index(posts) }, 
    show (post) { return Views.show(post) }, 
    create (attrs) { return Db.create(attrs) }, 
    update (post, attrs) { return Db.update(post, attrs) }, 
    destroy (post) { return Db.destroy(post) } 
}
/ / optimization
const BlogController = { 
    index: Views.index, 
    show: Views.show, 
    create: Db.create, 
    update: Db.update, 
    destroy: Db.destroy
}
Copy the code
  • Functions are first-class citizens and that’s the basis for higher order functions, Currization, and so on that we’ll learn later

Higher-order functions:

What kind of higher order function
  • Heigher-order function
    • You can pass a function as an argument to another function
    • You can treat a function as the return result of another function
  • Function as argument
// 1.forEach 
function forEach (array, fn) { 
    for (let i = 0; i < array.length; i++) { 
    	fn(array[i]) 
    } 
}

// 2.filter 
function filter (array, fn) { 
	let results = [] 
    for (let i = 0; i < array.length; i++) { 
        if (fn(array[i])) { 
            results.push(array[i]) 
        } 
    }
	return results 
} 
Copy the code
  • Function as the return value
// 1.makeFn
function makeFn () { 
    let msg = 'Hello function' 
    return function () { 
        console.log(msg) 
    } 
}
const fn = makeFn() 
fn() 

// 2.once 
function once (fn) { 
    let done = false 
    return function () { 
        if(! done) { done =true 
            return fn.apply(this.arguments)}}}let pay = once(function (money) { 
	console.log(` pay:${money} RMB`)})// Will only be paid once
pay(5) 
pay(5) 
pay(5)
Copy the code

[Memory: function parameters, return results]

The meaning of using higher-order functions
  • Abstraction can help us mask the details and focus only on our goals
  • Higher-order functions are used to abstract general problems
// 1. Process oriented approach
let array = [1.2.3.4] 
for (let i = 0; i < array.length; i++) { 
	console.log(array[i]) 
}

// 2. Higher-order functions
let array = [1.2.3.4] 
forEach(array, item= > { 
	console.log(item) 
})

let r = filter(array, item= > { 
	return item % 2= = =0 
})
Copy the code

[Memory: Focus on goals, abstract general problems]

Higher-order functions are commonly used
  • forEach
  • map
  • fifilter
  • every
  • some
  • fifind/fifindIndex
  • reduce
  • sort
// 1.map
const map = (array, fn) = > { 
    let results = [] 
    for (const value of array) { 
        results.push(fn(value)) 
    }
	return results 
}

// 2.every
const every = (array, fn) = > { 
    let result = true 
    for (const value of array) {
        result = fn(value) 
        if(! result) {break}}return result 
}

// 3.some
const some = (array, fn) = > { 
    let result = false 
    for (const value of array) { 
        result = fn(value) 
        if (result) { 
            break}}return result 
} 
Copy the code

ForEach map fifilter every some fifind/fifindIndex reduce sort

closure

  • Closure: A Closure is formed by binding a function with references to its surrounding state (lexical context).
    • You can call an inner function of a function from another scope and access members of that function’s scope
// function as return value
function makeFn () { 
    let msg = 'Hello function' 
    return function () { 
        console.log(msg) 
    } 
}
const fn = makeFn() 
fn() 

// once 
function once (fn) { 
    let done = false 
    return function () { 
        if(! done) { done =true 
            return fn.apply(this.arguments)}}}let pay = once(function (money) { 
	console.log(` pay:${money} RMB`)})// Will only be paid once
pay(5) 
pay(5) 
Copy the code
  • The essence of closures: functions are placed on an execution stack at execution time and removed from the stack when the function completes execution, but scoped members on the heap cannot be freed because of external references, so internal functions can still access the members of the external function
  • Closure case
// Generates a function that computes 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)) / / 16
console.log(power3(4)) / / 64

// The first number is base salary, and the second number is merit pay
function makeSalary (x) { 
    return function (y) { 
        return x + y 
    } 
}
let salaryLevel1 = makeSalary(1500) 
let salaryLevel2 = makeSalary(2500) 
console.log(salaryLevel1(2000)) / / 3500
console.log(salaryLevel1(3000)) / / 4500
Copy the code

[Memory: Binding and nature of function and surrounding state references: execution stack, scope members on the heap cannot be freed because of external references, and internal access]

Pure functions

Pure function concept

  • Pure functions: The same input always yields the same outputWithout any observable side effects
    • A pure function is like a function in mathematics, y = f(x).
  • Lodash is a library of pure functions that provides methods for operating on arrays, numbers, objects, strings, functions, and more
  • Arrays of slice and splice are pure and impure functions, respectively
    • Slice returns the specified portion of the array without altering the original array
    • Splice operates on an array and returns that array, changing the original array
let numbers = [1.2.3.4.5] 
/ / pure functions
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) 
/ / = > []
Copy the code
  • Functional programming does not preserve the middle of the calculation, so variables are immutable (stateless)
  • We can hand off the results of one function to another

Memory: pure functions: input and output are the same, relation; lodash: libraries, methods; slice: pure, unchanged; splice: impalent; variable immutable (stateless); execution result

Lodash:
    https://lodash.com
    https://github.com/lodash/lodash/wiki/FP-Guide
    npm init -y
    npm i lodash
Copy the code

The benefits of pure functions

  • cacheable
    • Because pure functions always have the same result on the same input, the result of a pure function can be cached
const _ = require('lodash') 
function getArea (r) { 
	return Math.PI * r * r 
}
let getAreaWithMemory = _.memoize(getArea) 
console.log(getAreaWithMemory(4)) 
Copy the code
  • Memoize a memoize function of your own. This can be sped up for computationally intensive recursive calls. Such as factorials, Fibonacci arrays, etc.)
function memoize (f) { 
    let cache = {} 
    return function () { 
        let arg_str = JSON.stringify(arguments) 
        cache[arg_str] = cache[arg_str] || f.apply(f, arguments) 
        return cache[arg_str] 
    } 
} 
Copy the code
  • testable
    • Pure functions make testing easier
  • Parallel processing
    • Parallel manipulation of shared memory data in a multithreaded environment is likely to cause accidents
    • Pure functions do not need access to shared memory data, so they can run at will in parallel (Web workers)

Memory: cacheable: Same input/results; Memoize function; testable: convenient; Parallel processing: no need to access memory data under multiple threads.

Side effects

  • Pure functions: For the same input you always get the same output without any observable side effects
/ / not pure
let mini = 18 
function checkAge (age) { 
	return age >= mini 
}

// Pure (hardcoded, later can be solved by Currization)
function checkAge (age) { 
    let mini = 18 
    return age >= mini 
}
Copy the code

Side effects make a function impure (as in the example above). Pure functions return the same output based on the same input, and can have side effects if the function depends on external state and cannot guarantee the same output.

Sources of side effects:

  • The configuration file
  • The database
  • Get user input
  • .

All external interactions are possible proxy side effects, which also reduce the generality of the method and make it unsuitable for expansion and reuse. At the same time, side effects will bring security risks to the program and uncertainty to the program. However, side effects cannot be completely banned, and they should be controlled as much as possible.

Memory: dependent on external state; source: configuration file, database, user input

Haskell Brooks Curry

  • Use Coriation to solve the hard-coded problem in the previous case
function checkAge (age) { 
    let min = 18 
    return age >= min 
}
// plain pure function
function checkAge (min, age) { 
	return age >= min 
}
checkAge(18.24) 
checkAge(18.20) 
checkAge(20.30) 
/ / curry
function checkAge (min) { 
    return function (age) { 
        return age >= min 
    } 
}
/ / ES6 writing
let checkAge = min= > (age= > age >= min) 
let checkAge18 = checkAge(18) 
let checkAge20 = checkAge(20) 
checkAge18(24) 
checkAge18(20) 
Copy the code

Let checkAge = min => (age => age>= min)

  • 1. Currying:
    • When a function has more than one argument, it is called by passing some of the arguments (these arguments never change).
    • It then returns a new function that takes the remaining arguments and returns the result

[Memory: some arguments are called (unchanged), return the rest of the arguments of the new function, return the result]

The Currization function in Lodash

  • _.curry(fun)
    • Function: creates a function that takes one or more arguments to func, executes func if all required arguments 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
const _ = require('lodash') 
// The currified function
function getSum (a, b, c) { 
	return a + b + c 
}
// The currie function
let curried = _.curry(getSum) 
/ / test
curried(1.2.3) 
curried(1) (2) (3) 
curried(1.2) (3) 
Copy the code
  • Case study:
const _ = require('lodash') 
const match = _.curry(function (reg, str) { 
	return str.match(reg) 
})

const haveSpace = match(/\s+/g) 
const haveNumber = match(/\d+/g) 

console.log(haveSpace('hello world')) 
console.log(haveNumber('25 $')) 

const filter = _.curry(function (func, array) { 
	return array.filter(func) 
})

console.log(filter(haveSpace, ['John Connor'.'John_Donne'])) 

const findSpace = filter(haveSpace) 
console.log(findSpace(['John Connor'.'John_Donne'])) 
Copy the code
  • Simulate the implementation of _.curry()
function curry (func) { 
    return function curriedFn (. args) { 
        // Determine the number of arguments and parameters
        if (args.length < func.length) { 
            return function () { 
                returncurriedFn(... args.concat(Array.from(arguments)))}}// Call func with the same number of arguments
        returnfunc(... args) } }Copy the code

Curry (func) : Function: receive func; parameter: before Currization; return value: after Currization;

Summary of Corrification:

  • Corrification allows us to pass in fewer arguments to a function and get a new function that has memorized some of the fixed arguments
  • This is a cache of function parameters
  • Make functions more flexible and smaller in granularity
  • You can convert multivariate functions into unary functions, and you can combine functions to produce powerful functions

Memory: new functions with fewer parameters and fixed parameters; caching; flexible, small granularity; converting to unary functions

Function combination:

  • Pure functions and Currization are easy to write onion code h(g(f(x)).
    • Take the last element of the array and convert it to uppercase,.toUpper(.first(_.reverse(array)))
  • Function composition allows us to recombine fine-grained functions to generate a new function

The pipe

In a FN b -> ->, it can be imagined that the pipe fn is split into three pipes F1, F2 and F3. Data A obtains the result M through pipe F3, and m obtains the result N through pipe F2. B a m fn n b -> F3 -> f2 -> f1 ->

fn = compose(f1, f2, f3) 
b = fn(a) 
Copy the code

Function composition

  • Compose: If a function needs to be processed by multiple functions to get the final value, you can combine the intermediate functions into a single function
    • A function is like a pipeline of data, and a combination of functions connects these pipes, allowing data to pass through multiple pipes to form the final result
    • Function combinations are executed from right to left by default
// combine functions
function compose (f, g) { 
    return function (x) { 
        return f(g(x)) 
    } 
}

function first (arr) { 
	return arr[0]}function reverse (arr) { 
	return arr.reverse() 
}

// Run from right to left
let last = compose(first, reverse) 
console.log(last([1.2.3.4])) / / 4
Copy the code
  • Combinatorial functions in Lodash
  • Lodash combines the flow() or flowRight() functions, both of which can combine multiple functions
  • Flow () runs from left to right
  • FlowRight () runs from right to left and is used more often
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'])) 
Copy the code
  • Simulate the flowRight method for implementing LoDash
// Multiple function combinations
function compose (. fns) { 
    return function (value) { 
        return fns.reverse().reduce(function (acc, fn) { 
            return fn(acc) 
        }, value) 
    } 
}

// ES6 
const compose = (. fns) = > value= > fns.reverse().reduce((acc, fn) = > 
fn(acc), value) 
Copy the code
  • The combination of functions has to be satisfiedAssociative law(associativity) :
    • We can either combine g with H, or f with g, and it’s the same thing
// Associativity
let f = compose(f, g, h) 
let associative = compose(compose(f, g), h) == compose(f, compose(g, h)) 
// true 
Copy the code
  • So the code could also look like this
const _ = require('lodash') 

// const f = _.flowRight(_.toUpper, _.first, _.reverse) 
// const f = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse) 
const f = _.flowRight(_.toUpper, _.flowRight(_.first, _.reverse))

console.log(f(['one'.'two'.'three'])) 
// => THREE 
Copy the code

In lodash: combination function, flow()[left to right], flowRight()[right to left]; satisfy associative law; in lodash: combination function, flow()[left to right], flowRight()[right to left]; satisfy associative law.

debugging

  • How do I debug composite functions
const f = _.flowRight(_.toUpper, _.first, _.reverse) 
console.log(f(['one'.'two'.'three'])) 

const _ = require('lodash') 

const trace = _.curry((tag, v) = > { 
    console.log(tag, v) 
    return v 
})
const split = _.curry((sep, str) = > _.split(str, sep)) 
const join = _.curry((sep, array) = > _.join(array, sep)) 
const map = _.curry((fn, array) = > _.map(array, fn)) 

const f = _.flowRight(join(The '-'), trace('after the map'), map(_.toLower), 
trace('after the split), split(' ')) 
console.log(f('NEVER SAY DIE'))
Copy the code
  • lodash/fp
    • Lodash’s FP module provides a practical approach to functional programming friendly
    • Immutable auto-curried iteratee-first data-last method is provided
/ / 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')


const fp = require('lodash/fp') 

const f = fp.flowRight(fp.join(The '-'), fp.map(_.toLower), fp.split(' ')) 
console.log(f('NEVER SAY DIE')) 
Copy the code

Point Free

Point Free: We can define the process of data processing as a composite operation unrelated to data, without using the parameter representing data, as long as the simple operation steps are combined together. Before using this mode, we need to define some auxiliary basic operation functions.

  • 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
const f = fp.flowRight(fp.join(The '-'), fp.map(_.toLower), fp.split(' '))
Copy the code
  • Case presentation
// Non-point Free mode
// Hello World => hello_world 
function f (word) { 
return word.toLowerCase().replace(/\s+/g.'_'); 
}

// Point Free 
const fp = require('lodash/fp') 

const f = fp.flowRight(fp.replace(/\s+/g.'_'), fp.toLower) 

console.log(f('Hello World')) 
Copy the code
  • Using the Point Free pattern, extract and capitalize the first letter of the word
const fp = require('lodash/fp') 

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

console.log(firstLetterToUpper('world wild web')) 
// => W. W. W 
Copy the code

Functor (Functor)

Why do we learn functors

So far we’ve covered some of the basics of functional programming, but we haven’t shown you how to keep side effects under control, exception handling, asynchronous operations, and so on in functional programming.

What is a Functor

  • Container: contains values and deformation relations of values (the deformation relations are functions)
  • 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

// A container that wraps a value
class Container { 
    // of static methods that create objects without the new keyword
    static of (value) { 
        return new Container(value) 
    }
    
    constructor (value) { 
        this._value = value 
    }
    
    // the map method, passing in the deformation relation, maps 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)
Copy the code
  • conclusion
    • The operations of functional programming do not operate directly on values, but are performed by functors
    • A functor is an object that implements the Map contract
    • We can think of a functor as a box that encapsulates a value
    • To process the values in the box, we need to pass a function (pure function) to the box’s map method to process the values
    • Function to process the value
    • The final map method returns a box containing the new value (functor)
  • In Functor if we pass null or undefined
// value if accidentally passed a null value (side effect)
Container.of(null) 
	.map(x= > x.toUpperCase()) 
// TypeError: Cannot read property 'toUpperCase' of null
Copy the code

MayBe functor

  • We may encounter many errors in the process of programming, and we need to deal with these errors accordingly
  • The MayBe functor is used to deal with external null cases.
class MayBe { 
    static of (value) {
        return new MayBe(value) 
    }
    constructor (value) { 
        this._value = value 
    }
    // Return a null functor if the null value is distorted
    map (fn) { 
        return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value)) 
    }
    isNothing () { 
        return this._value === null || this._value === undefined}}// Pass in a specific value
MayBe.of('Hello World') 
	.map(x= > x.toUpperCase()) 
// When null is passed
MayBe.of(null) 
	.map(x= > x.toUpperCase()) 
// => MayBe { _value: null }
Copy the code
  • In the MayBe functor, it is difficult to confirm which step produces the null value problem, as shown in the following example:
MayBe.of('hello world') 
    .map(x= > x.toUpperCase()) 
    .map(x= > null) 
    .map(x= > x.split(' ')) 
    // => MayBe { _value: null }
Copy the code

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
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)) 
    } 
} 

Copy the code
  • Either is used to handle exceptions
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) 
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 the execution of the impure operation (lazy execution), and wrap the current operation

As a pure

  • Leave impure operations 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 incoming 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()) 
Copy the code

Task asynchronous execution

  • The implementation of asynchronous tasks is too complex and we use Tasks in Folktale to demonstrate this
  • Folktale is a standard functional programming library
    • Unlike Lodash and Ramda, it doesn’t offer many features
    • It provides only a few functionally handled operations, such as compose, Curry, etc., some functor tasks, Either, MayBe, etc
const { compose, curry } = require('folktale/core/lambda') 
const { toUpper, first } = require('lodash/fp') 
// The first argument is the number of arguments passed to the function
let f = curry(2.function (x, y) { 
	console.log(x + y) 
})
f(3.4) 
f(3) (4) 
// Function combination
let f = compose(toUpper, first) 
f(['one'.'two']) 
Copy the code
  • Task asynchronous execution
    • Folktale (2.3.2) Tasks in 2.x are quite different from tasks in 1.0, which is much closer to what we are demonstrating here

    functor

    • This is illustrated in 2.3.2
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) 
    } 
}) 
Copy the code

Pointed functor

  • The Conservative functor is a functor that implements the of static method
  • The “of” method is used to avoid using new to create objects. The deeper meaning is that the “of” method is used to put values into Context (putting values into containers and using maps to process values).
class Container { 
    static of (value) { 
        return newThe Container (value)}... } Contanier.of(2) 
	.map(x= > x + 5) 
Copy the code

Monad

  • When using IO functors, if we write the following code:
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() { 
        console.log(x) 
        return x 
    }) 
}

// IO(IO(x)) 
let cat = fp.flowRight(print, readFile) 
/ / call
let r = cat('package.json')._value()._value() 
console.log(r) 

Copy the code
  • The Monad functor is a Pointed functor that can be flattened, IO(IO(x))
  • A functor is a Monad if it has both join and of methods and obeies some laws
const fp = require('lodash/fp') 
// IO Monad 
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) 
    .flatMap(print) 
    .join() 

Copy the code

The appendix

Monad Functors, Applicatives, And Monads In Pictures