函数式编程范式

在这里插入图片描述

相关资料

函数式编程

  • 编程范式之一,与面向过程编程、面向对象编程并列
  • 是一种编程思想
  • 函数式编程中的函数不是指程序中的函数(方法),而是数学中的映射关系(函数)

函数是一等公民

  • MDN解释
  • 函数可以储存在变量中
  • 函数作为参数
  • 函数作为返回值

函数

高阶函数

  • 函数作为参数传递给另一个函数
  • 函数作为另一个函数的返回结果

闭包

  • 从另一个作用域中调用一个函数的内部函数,并且该内部函数可以访问到其作用域下的成员属性
  • 本质:
    • 函数在执行时,会被放到一个执行栈上。等执行完毕后,该函数会从栈上移除。
    • 但,该函数中的作用域成员若被外部引用,则该成员在堆中不会被释放。
    • 因此,内部函数依然可以访问该函数的成员。
/**实例1:幂运算 */

function makePower(power) {//外部函数,接受幂值
	return function (x) {//内部函数,接受被运算的值
		//当调用到该函数时,会使用到外部函数(makePower)的成员(power),因此产生闭包
		return Math.pow(x, power)//调用power函数进行运算
	}
	//外部函数(makePower)执行完后,该函数会从执行栈上移除
	//但外部函数(makePower)中的成员(power)被内部匿名函数调用,该成员(power)不会从堆中释放。因此内部匿名函数可以访问该成员
}

//创建基本的幂函数
let power2 = makePower(2)
let power3 = makePower(3)

//对不同要求的幂,调用不同的幂函数
 console.log(power2(4)) //16
 console.log(power2(5)) //25
 console.log(power3(4)) //64
 console.log(power3(5)) //125

纯函数

  • 概念:相同的输入永远有相同的输出,类似于数学中的函数
const _ = require("lodash")

let array = [1, 2, 3, 4, 5, 6, 7, 8]
//slice函数为纯函数
console.log(array.slice(0, 3)) //[ 1, 2, 3 ]
console.log(array.slice(0, 3)) //[ 1, 2, 3 ]
console.log(array.slice(0, 3)) //[ 1, 2, 3 ]

//splice函数不为纯函数
console.log(array.splice(0, 3)) //[ 1, 2, 3 ]
console.log(array.splice(0, 3)) //[ 4, 5, 6 ]
console.log(array.splice(0, 3)) //[ 7, 8 ]

  • 作用:因输入输出始终保持对应关系,所以可以对输出结果缓存,提高程序性能问题
/**实例1:使用lodash中的memoize函数来缓存对象 */

function getArea(r) {//计算圆的面积
    console.log(`半径为:${r}`)
    return Math.PI * Math.pow(r, 2)
}
//不使用memorize时,每次调用函数(getArea)时,都会完整地执行一次
console.log(getArea(3))
//半径为:3
//28.274333882308138
console.log(getArea(3))
//半径为:3
//28.274333882308138
console.log(getArea(3))
//半径为:3
//28.274333882308138
//使用memorize时,函数(getArea)只完整地执行了一次。
let afterMemorize = _.memoize(getArea)
console.log(afterMemorize(3))
//半径为:3
//28.274333882308138
console.log(afterMemorize(3))
//28.274333882308138
console.log(afterMemorize(3))
//28.274333882308138

/**实例2:模拟lodash中的memoize
 * 利用闭包特性(被内部函数访问的成员变量不会随着函数的移除而释放),为使用memoize的纯函数进行缓存处理
 */

function memoize(fn) {
    let cache = {}//缓存变量。闭包时,该成员不会随着函数(memoize)的移除而释放
    return function () {
        let key = JSON.stringify(arguments)
        //arguments是指fn的传参数组,是一个伪数组(即,只有数组成员和其长度length的属性存在,其他的Array属性均没有)
        //该操作是为了将fn的参数作为缓存的key
        cache[key] = cache[key] || fn.apply(fn, arguments)
        //若key存在,则返回其缓存结果(cache[key])
        //若key不存在,则调用fn,并把结果缓存到cache[key]中
        return cache[key]
    }
}
function getArea(r) {//计算圆的面积
    console.log(`半径为:${r}`)
    return Math.PI * Math.pow(r, 2)
}
let afterMemorize = memoize(getArea)
console.log(afterMemorize(3))
//半径为:3
//28.274333882308138
console.log(afterMemorize(3))
//28.274333882308138
console.log(afterMemorize(3))
//28.274333882308138
  • 副作用
    • 当程序收到外部状态的影响时,可能会出现不纯函数
    • 副作用来源:
      • 配置文件
      • 数据库
      • 获取用户的输入

柯里化

  • 将多元函数转化成一元函数
  • 对函数参数的缓存
  • 让函数更灵活,让函数粒度更小
console.log(`-------------普通函数`)
//判断年龄是否在规定的范围内
const checkAge = (min, max, age) => {
    return `${age}岁范围在${min}~${max}之间:${age >= min && age <= max}`
}

//问题:对于同一范围,在调用时,每次都要传递多个重复参数
console.log(checkAge(18, 40, 56))//56岁范围在18~40之间:false
console.log(checkAge(18, 40, 34))//34岁范围在18~40之间:true
console.log(checkAge(18, 40, 40))//40岁范围在18~40之间:true

console.log(`-------------柯里化函数`)
//转换为柯里化函数(function写法)
function checkAgeCurry(min, max) {
    //柯里化函数可以接受较少的参数
    //返回一个新函数,并接受剩余的函数
    return function (age) {
        //利用闭包原理,可以使用缓存下来的min、max
        return `${age}岁范围在${min}~${max}之间:${age >= min && age <= max}`
    }
}
//转换为柯里化函数(ES6写法)
// const checkAgeCurry = (min, max) => age => (`${age}岁范围在${min}~${max}之间:${age >= min && age <= max}`)
//调用柯里化函数
//可以根据不同的范围值,创建不同的范围函数
const ageRange1 = checkAgeCurry(18, 40)
const ageRange2 = checkAgeCurry(30, 40)
//再做判断年龄时,可以使用规定好的范围函数来调用
console.log(ageRange1(56))//56岁范围在18~40之间:false
console.log(ageRange1(20))//20岁范围在18~40之间:true
console.log(ageRange1(40))//40岁范围在18~40之间:true
console.log(ageRange2(56))//56岁范围在30~40之间:false
console.log(ageRange2(20))//20岁范围在30~40之间:false
console.log(ageRange2(40))//40岁范围在30~40之间:true
  • lodash中的柯里化函数curry()使用
console.log(`-------------案例1:检查年龄是否在范围内`)
const _ =require("lodash")

//创建curry函数
const checkAgeCurry = _.curry((min, max, age) => {
    return `${age}岁范围在${min}~${max}之间:${age >= min && age <= max}`
})
//创建范围函数,返回的是一个新函数
const ageRange1 = checkAgeCurry(18, 40)
const ageRange2 = checkAgeCurry(30, 40)
//使用范围函数
console.log(ageRange1(56))//56岁范围在18~40之间:false
console.log(ageRange1(20))//20岁范围在18~40之间:true
console.log(ageRange1(40))//40岁范围在18~40之间:true
console.log(ageRange2(56))//56岁范围在30~40之间:false
console.log(ageRange2(20))//20岁范围在30~40之间:false
console.log(ageRange2(40))//40岁范围在30~40之间:true
console.log(`-------------案例2:返回符合正则表达式的值`)
const _ = require("lodash")

//创建柯里化函数
const matchReg = _.curry((reg, str) => {
    return str.match(reg)
})
//创建不同的匹配方式
const matchSpace = matchReg(/\s+/g)//是否有空格
const matchNumber = matchReg(/\d+/g)//是否有数字
//匹配单个字符
console.log("#匹配单个字符")
console.log(matchSpace('Marry_Jeckson'))//null
console.log(matchSpace('Marry Jeckson'))//[ ' ' ]
console.log(matchNumber('$twenty-two'))//null
console.log(matchNumber('$22'))//[ '22' ]
//匹配数组
console.log("#匹配数组")
let array = ['Marry_Jeckson', 'Cheliry Curry', "32", "4"]
//创建针对数组的柯里化函数
const filterCurry = _.curry((fn, array) => {
    return array.filter(fn)
})
//创建针对数组匹配方式的函数
let filterSpace = filterCurry(matchSpace)
let filterNumber = filterCurry(matchNumber)
//使用匹配
console.log(filterSpace(array))//[ 'Cheliry Curry' ]
console.log(filterNumber(array))//[ '32', '4' ]
  • 模拟_.curry()的实现
const curry = (fn) => {//柯里化函数需要传递一个函数
    //柯里化函数返回的是函数,且参数是不固定的,所以使用...语法来获取
    return function fnSub(...args) {//args是实际传递的参数,即为实参;fn是规定传递的参数,即为形参
        //在调用时,根据传递的参数数量,判断是调用fn的函数,还是返回新的函数等待剩余参数传递
        if (args.length < fn.length) {
            //实参 < 形参,需继续返回新的函数,并将当前传递的参数和原来的参数合并传递
            return (...currArgs) => {
                return fnSub(...args.concat(currArgs))
                //若此处的函数使用的是function方式,则currArgs可以使用arguments
                //若此处的函数使用的是=>方式,则arguments会去外层一直找到function方式的函数,此处,arguments==...args
            }
        } else {
            //实参 >= 形参,调用fn
            return fn(...args)
        }
    }
}
//验证
const checkAgeCurry = curry((min, max, age) => {
    return `${age}岁范围在${min}~${max}之间:${age >= min && age <= max}`
})
//创建范围函数,返回的是一个新函数
const ageRange1 = checkAgeCurry(18, 40)
const ageRange2 = checkAgeCurry(30, 40)
//使用范围函数
console.log(ageRange1(56))//56岁范围在18~40之间:false
console.log(ageRange1(20))//20岁范围在18~40之间:true
console.log(ageRange1(40))//40岁范围在18~40之间:true
console.log(ageRange2(56))//56岁范围在30~40之间:false
console.log(ageRange2(20))//20岁范围在30~40之间:false
console.log(ageRange2(40))//40岁范围在30~40之间:true

函数组合

  • 是指将过程函数合并成一个函数的过程
  • 默认是从有到左执行
  • 需满足结合律

console.log(`------------------------------\n----函数组合概念\n------------------------------`)
//创建组合函数,即将多个函数组合成一个函数
const compose = (f, g) => {
    return (x) => {//传入1个数据
        return f(g(x))//通过多个函数返回其结果
    }
}
//创建所需函数1:获取数组的第一个值
const first = (array) => {
    return array[0];
}
//创建所需函数2:反转函数
const reverse = (array) => {
    return array.reverse()
}
//使用组合函数,并得到最终值
let result = compose(first, reverse)
console.log(result([1, 2, 3, 4, 5, 6, 7, 8, 9, 100])) //100
  • lodash中的组合函数
const _ = require("lodash")
/**
 * _.flow() 从左往右执行
 * _.flowRight() 从右往左执行
 */
console.log(`------------------------------\n----lodash中的组合函数\n------------------------------`)
//创建所需的函数
const first = array => array[0];
const reverse = array => array.reverse();
const toUpper = str => str.toUpperCase();
//使用flowRight,组合成一个函数
const f = _.flowRight(toUpper, first, reverse)
//使用组合函数
console.log(f(["one", "two", "three"])) //THREE
  • 模拟lodash中的FlowRight()方法
console.log(`------------------------------\n----模拟lodash中的flowRight方法\n------------------------------`)

//模拟flowRight方法
const compose = (...fns) => {
    return (value) => {
        return fns.reverse().reduce((result, fn) => {
            return fn(result)
        }, value)
    }
}
//创建所需的函数
const first = array => array[0];
const reverse = array => array.reverse();
const toUpper = str => str.toUpperCase();
//使用flowRight,组合成一个函数
const f = compose(toUpper, first, reverse)
//使用组合函数
console.log(f(["one", "two", "three"])) //THREE
  • 函数组合需要满足结合律
const _ = require("lodash")

console.log(`------------------------------\n----函数组合要满足结合律\n------------------------------`)
//创建所需的函数
const first = array => array[0];
const reverse = array => array.reverse();
const toUpper = str => str.toUpperCase();
//满足结合律
const f1 = _.flowRight(toUpper, _.flowRight(first, reverse))
const f2 = _.flowRight(_.flowRight(toUpper, first), reverse)
//使用组合函数
console.log(f1(["one", "two", "three"])) //THREE
console.log(f2(["one", "two", "three"])) //THREE
  • 调试组合函数
const _ = require("lodash")

console.log(`------------------------------\n----调试组合函数\n------------------------------`)
//创建所需的函数
const split = _.curry((sep, str) => str.split(sep))
const join = _.curry((sep, array) => array.join(sep))
const map = _.curry((fn, array) => _.map(array, fn))
//创建调试函数
const log = _.curry((type, val) => {
    console.log(type, val)
    return val
})
//函数组合,并加入调试函数
const f = _.flowRight(join("-"), log("after map:"), map(_.toLower), log("after split:"), split(" "), log("origin:"))
//使用
console.log(f("ONE TWO THREE FOUR"))
//origin: ONE TWO THREE FOUR
//after split: ['ONE', 'TWO', 'THREE', 'FOUR']
//after map: ['one', 'two', 'three', 'four']
//one-two-three-four

Point Free(管道)

  • 是指,将数据处理的过程定义成与数据无关的合成运算
  • 也就是不会用到使用代表数据的变量
  • 基本上就是组合函数
  • 特点:
    • 不需要指明处理的数据
    • 只需要合成运算过程
    • 需要一些基本的运算函数来辅助

函子

  • 是一个特殊的容器,这个容器是由普通对象来实现
  • 该对象有map方法,用来运行一个函数对值进行处理
  • 最终返回一个包含新值的盒子(函子,或对象)
console.log(`------------------------------\n----函子概念\n------------------------------`)
class Container {//函子由普通对象实现
    constructor(value) {//构造函数,将初始值传递进来
        this._value = value
    }
    map(fn) {//对外开放,并传递一个函数来对值进行处理
        return new Container(fn(this._value))
    }
}
let result = new Container(5)
    .map(x => x + 1)
    .map(x => x * x)
console.log(result) //Container { _value: 36 }
console.log(`------------------------------\n----另一种方式调用函子\n------------------------------`)
class Container {
    constructor(value) {
        this._value = value
    }
    static of(value) {//使用静态方法来初始化函子
        return new Container(value)
    }
    map(fn) {
        return Container.of(fn(this._value))
    }
}
let result = Container.of(5)
    .map(x => x + 1)
    .map(x => x * x)
console.log(result) //Container { _value: 36 }
  • MayBe函子:对null的处理

console.log(`------------------------------\n----MayBe函子:对null的处理\n------------------------------`)
/**
 * 普通函子中,值为null或undefined时,并没有做任何处理,这会很容易导致程序报错
 */
class MayBe {
    constructor(value) {
        this._value = value
    }
    static of(value) {
        return new MayBe(value)
    }
    map(fn) {
        //执行函数之前,对值进行是否为null的判断
        return this.isNothing(this._value) ? MayBe.of(null) : MayBe.of(fn(this._value))
    }
    isNothing(value) {//创建判断值是否为空
        return value === null || value === undefined
    }
}
let result = MayBe.of("hello world")
    .map(x => x.split(" "))
    .map(x => x.reverse())
console.log(result) //{ _value: [ 'world', 'hello' ] }
let result2 = MayBe.of(null)
    .map(x => x.split(" "))
    .map(x => x.reverse())
console.log(result2) //{ _value: null }
  • Either函子:对异常进行处理
    • 类似于if…else…的处理
console.log(`------------------------------\n----Either函子:对异常进行处理\n------------------------------`)

//异常时处理的函子
class Error {
    constructor(value) {
        this._value = value
    }
    static of(value) {
        return new Error(value)
    }
    map(fn) {//相对于正常下的函子,该处不做其他的函数处理
        return this;
    }
}
//正常处理的函子
class Normal {
    constructor(value) {
        this._value = value
    }
    static of(value) {
        return new Error(value)
    }
    map(fn) {
        return Normal.of(fn(this._value));
    }
}
//配合try...catch来使用
const parseJson = (json) => {
    try {
        return Normal.of(JSON.parse(json))
    } catch (e) {
        return Error.of({ error: e.message })
    }
}
//正常时
console.log(parseJson('{"name":"123"}')) //{ _value: { name: '123' } }
//异常时
console.log(parseJson("{name:213}")) //{ _value: { error: 'Unexpected token n in JSON at position 1' } }
  • IO函子:处理操作的值为函数时
    • 处理的值为函数
    • 该值可能为不纯的函数
    • 延迟执行这个不纯的操作
    • 把不纯的操作交给调用者来处理
console.log(`------------------------------\n----IO函子:处理操作的值为函数时\n------------------------------`)
//创建函子
class IO {
    constructor(fn) {//需要处理的值为一个函数
        this._value = fn
    }
    //对于需要处理的值进行函数封装
    static of(value) {
        return new IO(() => {
            return value
        })
    }
    //将 当前的value 与 传入的fn 组合成新的函数
    map(fn) {
        return new IO(fp.flowRight(fn, this._value))
    }
}
//调用
let io = IO.of(process).map(p => p.execPath)
//value为函数,需调用value
console.log(io._value())//D:\nodejs\node.exe
  • task函子:异步执行
console.log(`------------------------------\n----task函子:异步执行\n------------------------------`)
const { curry } = require("folktale/core/lambda")
const { task } = require("folktale/concurrency/task")
const fs = require("fs")
const { split, find, includes } = require("lodash/fp")
//读取文件
const readFile = (filename) => {
    return task(resolver => {
        fs.readFile(filename, "utf-8", (err, data) => {
            if (err) resolver.reject(err)//错误使用reject来接受
            resolver.resolve(data)//正确使用resolve来接受
        })
    })
}
const log = curry(2, (type, value) => {
    console.log(type, value)
    return value
})
readFile("./package.json")
    .map(split("\n"))//将文本用回车分隔成数组
    .map(log("after \\n"))
    .map(find(x => x.includes("version")))//找到version
    .run()//运行task
    .listen({//监听事件
        onRejected: err => {
            console.log(err)
        },
        onResolved: value => {
            console.log(value)
        }
    })
// after \n[
//     '{\r',
//     '  "dependencies": {\r',
//     '    "folktale": "^2.3.2",\r',
//     '    "lodash": "^4.17.19"\r',
//     '  },\r',
//     '  "version": "2.2"\r',
//     '}'
// ]
// "version": "2.2"
  • Monad函子:简化个别函子调用
    • 简化 IO(IO()) 这种洋葱函子的调用
    • 同时具有 of 和 join 方法并遵守一些定律的函子

console.log(`------------------------------\n----Monad函子:简化个别函子调用\n------------------------------`)
const fp = require("lodash/fp")
const fs = require("fs")
/**需求:读取文件,输出文件
 * 
 */
{//使用普通IO函子处理
    console.log(`----使用普通IO函子处理`)
    class IO {
        constructor(fn) {
            this._value = fn
        }
        static of(value) {
            return new IO(() => {
                return value
            })
        }
        map(fn) {
            return new IO(fp.flowRight(fn, this._value))
        }
    }
    //读取文件
    const readFile = (filename) => {
        return new IO(() => {
            return fs.readFileSync(filename, 'utf-8')
        })
    }
    //输出文件
    const printFile = (x) => {
        return new IO(() => {
            console.log(x)
            return x
        })
    }
    //组合函数:IO(IO()),外层的IO是printFile方法的,内层的IO是readFile方法的
    let cat = fp.flowRight(printFile, readFile)
    //调用:第一个_value()是指运行printFile返回的IO函子,所以其结果是打印出console.log(x)这个代码
    //第二个_value()是指readFile返回的IO函子
    let result = cat("package.json")._value()._value()
    console.log(result)
    //IO { _value: [Function] }
    //{
    //  "dependencies": {
    //      "folktale": "^2.3.2",
    //      "lodash": "^4.17.19"
    //  },
    //  "version": "2.2"
    //}
    
}

{//使用Monad函子处理
    console.log(`----使用Monad函子处理`)
    const fp = require("lodash/fp")
    const fs = require("fs")
    //创建Monad函子
    class IO {
        constructor(fn) {
            this._value = fn
        }
        static of(value) {
            return new IO(() => {
                return value
            })
        }
        map(fn) {
            return new IO(fp.flowRight(fn, this._value))
        }
        //用于调用将当前函子保存的_value
        join() {
            return this._value()
        }
        //用于合并调用 map() 和 join() 方法
        flatMap(fn) {
            return this.map(fn).join()
        }
    }
    //创建读文件方法
    const readFile = (filename) => {
        return new IO(() => {
            return fs.readFileSync(filename, "utf-8")
        })
    }
    //创建打印文件方法
    const printFile = (x) => {
        return new IO(() => {
            console.log(x)
            return x
        })
    }
    //组合 读文件、打印文件 方法
    const cat = readFile("package.json")
        .flatMap(printFile)
        .join()
    console.log(cat)
    // {
    //     "dependencies": {
    //         "folktale": "^2.3.2",
    //         "lodash": "^4.17.19"
    //     },
    //     "version": "2.2"
    // }
    // {
    //     "dependencies": {
    //         "folktale": "^2.3.2",
    //         "lodash": "^4.17.19"
    //     },
    //     "version": "2.2"
    // }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值