关于javascript:函数式编程四函数组合

目录

  • 函数组合

    • 背景常识
    • 管道
    • Lodash中的组合函数 —— flow()/flowRight()
    • 函数组合原理模仿
    • 函数组合-结合律

      • 什么是函数组合结合律?
    • 函数组合-调试
  • FP模块

    • 体验FP模块对于组合函数的敌对
    • Lodash-map办法的小问题
  • Point Free

    • Pointfree案例

-【函数式编程总体设计】

之前讲了函数的前置常识 函数式编程(一)—— 前置常识

还有纯函数的常识 函数式编程(二)—— 纯函数

柯里化 函数式编程(三)—— 柯里化

函数组合

背景常识

  • 纯函数和柯里化很容易写出洋葱代码 h(g(f(x)))
//获取数组的最初一个元素再转换成大写字母
//先翻转数据 --> 再取第一个元素 --> 再转换成大写字母
_.toUpper(_.first(_.reverse(array)))

函数组合能够让咱们把细粒度的函数重新组合生成一个新的函数,防止写出洋葱代码

管道

a –> fn –> b

a-> f3 -> m -> f2 -> n -> f1 -> b

其实两头m、n、是什么咱们也不关怀
相似于上面的函数

fn = compose(f1, f2, f3)
b = fn(a)

函数组合

  • 函数组合 (compose):如果一个函数要通过多个函数解决能力失去最终值,这个时候能够把两头

过程的函数合并成一个函数

  • 函数组合默认是从右到左执行
// 函数组合演示
function compose(f, g) {
  return function (value) {
    return f(g(value))
  }
}

// 数组翻转函数
function reverse (array) {
  return array.reverse()
}

// 获取函数第一个元素函数
function first (array) {
  return array[0]
}

// 组合函数,获取函数最初一个元素
const last = compose(first, reverse)

console.log(last([1, 2, 3, 4])) // 4

Lodash中的组合函数 —— flow()/flowRight()

lodash 中组合函数 flow() 或者flowRight(),他们都能够组合多个函数。

  • flow() 是从左到右运行
  • flowRight() 是从右到左运行,应用的更多一些

上面实例是获取数组的最初一个元素并转化成大写字母

const _ = require('lodash')

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

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

console.log(f(['one', 'two', 'three'])) // THREE

函数组合原理模仿

下面的例子咱们来剖析一下:

入参不固定,参数都是函数,出参是一个函数,这个函数要有一个初始的参数值

function compose (...args) {
  // 返回的函数,有一个传入的初始参数即value
  return function (value) {
    // ...args是执行的函数的数组,从右向左执行那么数组要进行reverse翻转
    // reduce: 对数组中的每一个元素,去执行咱们提供的一个函数,并将其汇总成一个单个后果
    // reduce的第一个参数是一个回调函数,第二个参数是acc的初始值,这里acc的初始值就是value

    // reduce第一个参数的回调函数须要两个参数,第一个参数是汇总的一个后果,第二个参数是如果解决汇总的后果的函数并返回一个新的值
    // fn指的是数组中的每一个元素(即函数),来解决参数acc,实现之后下一个数组元素解决的是上一个数组的后果acc
    return args.reverse().reduce(function (acc, fn) {
      return fn(acc)
    }, value)
  }
}


//test
const fTest = compose(toUpper, first, reverse)
console.log(fTest(['one', 'two', 'three'])) // THREE


// ES6的写法(函数都变成箭头函数)
const compose = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value)

函数组合-结合律

什么是函数组合结合律?

上面三个状况后果一样,咱们既能够把 g 和 h 组合,还能够把 f 和 g 组合。

// 结合律(associativity) 
let f = compose(f, g, h) 
let associative = compose(compose(f, g), h) == compose(f, compose(g, h)) 
// true

上面用之前的例子再具体说一下:

const _ = require('lodash')

// 形式一
const f = _.flowRight(_.toUpper, _.first, _.reverse)
// 形式二
const f = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse)
// 形式三
const f = _.flowRight(_.toUpper, _.flowRight(_.first,  _.reverse))

// 无论下面那种写法,上面都输入THREE这个雷同的后果
console.log(f(['one', 'two', 'three'])) // THREE

函数组合-调试

如果咱们运行的后果和咱们的预期不统一,咱们怎么调试呢?咱们怎么能晓得两头运行的后果呢?

上面这个输出NEVER SAY DIE要对应输入nerver-say-die

留神:
每次把本人加的参数写后面,传入的值写前面

const _ = require('lodash')

// 这里split函数须要传入两个参数,且咱们最初调用的时候要传入字符串,所以字符串要在第二个地位传入,这里咱们须要本人封装一个split函数
// _.split(string, separator)

// 将多个参数转成一个参数,用到函数的柯里化
const split = _.curry((sep, str) => _.split(str, sep))

// 大写变小写,用到toLower(),因为这个函数只有一个参数,所以能够在函数组合中间接应用

// 这里join办法也须要两个参数,第一个参数是数组,第二个参数是分隔符,数组也是最初的时候才传递,也须要替换
const join = _.curry((sep, array) => _.join(array, sep))

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

console.log(f('NEVER SAY DIE')) //n-e-v-e-r-,-s-a-y-,-d-i-e

然而最初的后果却不是咱们想要的,那么咱们怎么调试呢?

// NEVER SAY DIE --> nerver-say-die

const _ = require('lodash')
 
const split = _.curry((sep, str) => _.split(str, sep))
const join = _.curry((sep, array) => _.join(array, sep))

// 咱们须要对两头值进行打印,并且晓得其地位,用柯里化输入一下
const log = _.curry((tag, v) => {
  console.log(tag, v)
  return v
})

// 从右往左在每个函数前面加一个log,并且传入tag的值,就能够晓得每次后果输入的是什么
const f = _.flowRight(join('-'), log('after toLower:'), _.toLower, log('after split:'), split(' '))
// 从右到左
//第一个log:after split: [ 'NEVER', 'SAY', 'DIE' ] 正确
//第二个log: after toLower: never,say,die  转化成小写字母的时候,同时转成了字符串,这里出了问题
console.log(f('NEVER SAY DIE')) //n-e-v-e-r-,-s-a-y-,-d-i-e


// 批改形式,利用数组的map办法,遍历数组的每个元素让其变成小写 
// 这里的map须要两个参数,第一个是数组,第二个是回调函数,须要柯里化
const map = _.curry((fn, array) => _.map(array, fn))

const f1 = _.flowRight(join('-'), map(_.toLower), split(' '))
console.log(f1('NEVER SAY DIE')) // never-say-die

FP模块

函数组合的时候用到很多的函数须要柯里化解决,咱们每次都解决那些函数有些麻烦,所以lodash中有一个FP模块

  • lodash 的 fp 模块提供了实用的对函数式编程敌对的办法
  • 提供了不可变 auto-curried iteratee-first data-last (函数之先,数据之后)的办法
// lodash 模块 
const _ = require('lodash')
// 数据置先,函数置后
_.map(['a', 'b', 'c'], _.toUpper) 
// => ['A', 'B', 'C'] 
_.map(['a', 'b', 'c']) 
// => ['a', 'b', 'c'] 

// 数据置先,规定置后
_.split('Hello World', ' ') 

//BUT
// lodash/fp 模块 
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')

体验FP模块对于组合函数的敌对

const fp = require('lodash/fp')

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

console.log(f('NEVER SAY DIE')) // never-say-die

Lodash-map办法的小问题

const _ = require('lodash')
const fp = require('lodash/fp')

console.log(_.map(['23', '8', '10'], parseInt)) 
// [ 23, NaN, 2 ]

_.map(['23', '8', '10'], function(...args){
  console.log(...args)
})
// _.map前面的回调函数承受有三个参数,第一个参数是遍历的数组,第二个参数是key/index,第三个参数是对应函数
// 23 0 [ '23', '8', '10' ]
// 8 1 [ '23', '8', '10' ]
// 10 2 [ '23', '8', '10' ]

// parseInt第二个参数示意进制,0默认就是10进制,1不存在,2示意2进制,所以输入是那个样子
//parseInt('23', 0, array)
//parseInt('8', 1, array)
//parseInt('10', 2, array)

// 要解决的话须要从新封装一个parseInt办法

// 而应用fp模块的map办法不存在上面的问题
console.log(fp.map(parseInt, ['23', '8', '10'])) 
// [ 23, 8, 10 ]

Point Free

是一种编程格调,具体的实现是函数的组合。

Point Free: 咱们能够把数据处理的过程定义成与数据无关的合成运算,不须要用到代表数据的那个参数,只有把简略的运算步骤合成到一起,在应用这种模式之前咱们须要定义一些辅助的根本运算函数。

  • 不须要指明解决的数据
  • 只须要合成运算过程
  • 须要定义一些辅助的根本运算函数
//Hello World => hello world

//思路:
//先将字母换成小写,而后将空格换成下划线。如果空格比拟多,要替换成一个
const fp = require('lodash/fp')

// replace办法接管三个参数
// 第一个是正则匹配pattern,第二个是匹配后替换的数据,第三个是要传的字符串
// 所以这里须要传两个参数
const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower)

console.log(f('Hello World')) //hello_world

Pointfree案例

//world wild web -->W. W. W
//思路:
//把一个字符串中的额首字母提取并转换成大写,应用. 作为分隔符
const fp = require('lodash/fp')

const firstLetterToUpper = fp.flowRight(fp.join('. '), fp.map(fp.first), fp.map(fp.toUpper), fp.split(' '))
console.log(firstLetterToUpper('world wild web')) //W. W. W

// 下面的代码进行了两次的遍历,性能较低
// 优化
const firstLetterToUpper = fp.flowRight(fp.join('. '), fp.map(fp.flowRight(fp.first, fp.toUpper)), fp.split(' '))
console.log(firstLetterToUpper('world wild web')) //W. W. W

函数式编程总体设计

  • 函数式编程(一)—— 前置常识
  • 函数式编程(二)—— 纯函数
  • 函数式编程(三)—— 柯里化

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理