函数式编程 - 函数组合

334 阅读3分钟

函数组合

  • 纯属函数和柯里化很容易写出洋葱代码 h(g(f(x)))
    • 获取数组的最后一个元素再转换成大写字母 .toUpper(.first(_.reverse(array)))
  • 函数组合可以让我们把细粒度的函数重新组合生成一个新的函数

函数组合先了解一下 管道

a -> fn(也可以是多个管理-多个函数) -> b

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

函数组合定义

  • 函数组合(compose): 如果一个函数要经过多个函数处理才能得到最终值,这个时候嗯可以把中间过程的函数合并成一个函数

    • 函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果
    • 函数组合默认是从右到左执行
// 组合函数
function compose (f, g) {
   return function (x) {
       return f(g(x))
   }
}

lodash 中的组合函数

  • lodash中组合函数flow() 或者 flowRight(), 他们都是可以组合多个函数
  • flow() 是从左到右运行
  • flowRight() 是从右到左运行,使用的更多一些
// 模拟 lodash  中的 flowRight

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

const f = compose(toUpper, first, reverse);
console.log(f(['one', 'two', 'three']))

function compose (...args) {
   return function (value) {
       return args.reverse().reduce(function (acc, fn){
           return fn(acc);
       }, value)    
   }
}

// es6
const compose = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value);


函数的组合要满足 组合律(associativity):

  • 我们既可以把g和h组合,还可以把f和g组合,结果都是一样的。
// 结合律 (associativity)
let f = compose(f, g, h);
let associative = compose(compose(f, g), h) == compose((f, compose(g, h));
函数组合 调试
 // 举例
  // NEVER SAY DIE  --> never-say-die
  const _ = require('lodash')
  
  // _.split()
  const split = _.curry((sep, str) => _.split(str, sep))
  
  // _.toLower()
  const join = _.curry((sep, array) => _.join(array, sep))
  
  const map = _.curry((fn, array) => _.map(array, fn))
  const f = _.flowRight(join('-'), map(_.toLower), split(' '))
  
  console.log(f('NEVER SAY DIE'))

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', ' ')


// 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')


// 举例
/ NEVER SAY DIE  --> never-say-die
const fp = require('lodash/fp')

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

lodash 和 lodash/fp 模块中 map 方法的区别

const _ = require('lodash')
console.log(_.map(['23', '8', '10', parseInt))
// => [23, NaN, 2]
// 剖析
// 进制 2-36  0就是没传,默认10进制
// 1. parseInt('23', 0, array)
// 2. parseInt('8', 1, array)
// 3. parseInt('10', 2, array)

Point Free

  • "Point Free": 我们可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只要把简单的运算步骤合成到一起,在使用这种模式之前我们需要定义一些辅助的基本运算函数。

    • 不需要指明处理的数据
    • 只需要合成运算过程
    • 需要定义一些辅助的基本运算函数
const f = fp.flowRight(fp.join('-'), fp.map(_.toLower), fp.split(' '))
案例演示

// 非 Point Free 模式
// 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'))


// 把一个字符串中的首字母提取并转换成大写,使用. 作用分隔符
// world wild web ==> W. W. W

const fp = require('lodash/fp')

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

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

console.log(firstLetterToUpper('world wild web'))