javascript函数式编程-------函数组合

函数组合

函数组合的概念: 如果一个函数要经过多个函数处理才能得到最终的值,这个时候我们可以把中间这些过程函数合并成一个新的函数。

函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果

函数组合默认是从右到左执行

进行函数组合时尽量使用只要一个参数的纯函数

基本的函数组合

我们在使用纯函数和柯里化时很容易写出洋葱代码,h(g(f(x))),也就是一层包一层的代码,比如我们要获取数组的最后一个元素,然后在转换成大写字母。

先调用数组对象的reverse方法反转数组,然后调用first方法获取数组第一个元素,再调用toUpper方法将获取的第一个元素转为大写。

const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
const array = ['a', 'b', 'c', 'd'];
let end = toupper(first(reverse(array)));
console.log(end)

可以发现上面这些方法的调用就是一层包一层的,这就是洋葱代码,这是最基本的函数组合,我们将其封装一下。

比如上面的例子需要调用reverse,first,toUpper三个函数,我们可以通过组合,将这三个函数合并成一个,得到test函数,调用的时候仍旧传入array数组,处理的结果是不变的。函数组合其实就相当于隐藏掉了多个函数调用的中间结果,比如reverse传递给first,first传递给toUpper。

const array = ['a', 'b', 'c', 'd'];
function compose(fn1,fn2,fn3){
    return function(...args){
        return fn1(fn2(fn3(...args)))
    }
}
const test = compose(toupper,first,reverse);
console.log(test(array ))

函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果。

多个函数的合并

接下来,实现多个函数的合并,模拟一下组合函数的原理, 一般函数组合的习惯写法,进行组合的函数从右向左执行。我们要对它进行一个反转,这里给args进行一个reverse处理,反转之后我们要依次调用里面的函数,并且前一个函数的返回值需要是下一个函数的参数。

function compose (...args) {
  return function (value) {
    return args.reverse().reduce(function (acc, fn) {
      return fn(acc)
    }, value)
  }
}
const test = compose(toupper,first,reverse);
console.log(test(array))

函数组合要满足的特点

函数的组合要满足结合律 (associativity): 我们既可以把 g 和 h 组合,还可以把 f 和 g 组合,结果都是一样的

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

可以发现我们无论先结合前两个还是先结合后两个,得到的结果都是相同的,这就是结合律,和数学中的结合律是一样的。

函数组合的调试

当我们使用函数组合的时候,如果我们执行的结果跟我们预期的不一致,这个时候我们应该如何调试呢?

比如说下面的代码,当我们想知道reverse执行的结果是什么时候。我们可以在reverse函数前面追加一个log函数,把他打印出来看一下

现在我们来实现一个函数,把  NEVER SAY DIE     变成      never-say-die  。  思路是

      1.先用 split 函数用空格把字符串切割成为数组--->[NEVER,SAY,DIE],

      2.再用 map 函数把数组每一项变为小写,[ never , say , die ],

      3.最后用 join 函数用 - 把数组合并成字符串  "never-say-die"。

在函数组合的时候我们需要的是只有一个参数的纯函数, 下面split 和 join 2个函数都是需要2个参数的纯函数,所以用函数的柯里化把它们转化成只需要一个参数的纯函数

function curry (func) {
  return function curriedFn(...args) {
    // 判断实参和形参的个数
    if (args.length < func.length) {
      return function () {
        return curriedFn(...args.concat(Array.from(arguments)))
      }
    }
    return func(...args)
  }
};

const split = curry(function(step,arr){
  return arr.split(step)
})
const map = function(arr){
  return arr.map(function(item){
    return item.toLowerCase()
  })
}
const join = curry(function(step,str){
  return str.join(step)
})

再来写一个调试打印函数trace,用函数的柯里化把trace函数转化成只需要一个参数的纯函数,它的第一个参数tag是描述当前结果是哪一个函数执行完毕后的打印结果(哪一条管道结束后的打印信息)

const trace = curry((tag, v) => {
  console.log(tag, v)
  return v
})

接下来开始函数组合

const f = compose(join('-'), trace('map 之后'), map, trace('split 之后'), split(' '));
console.log(f('NEVER SAY DIE'))

好了调试的方式就完成了,上一下完整的代码

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

function curry (func) {
  return function curriedFn(...args) {
    // 判断实参和形参的个数
    if (args.length < func.length) {
      return function () {
        return curriedFn(...args.concat(Array.from(arguments)))
      }
    }
    return func(...args)
  }
};

const trace = curry((tag, v) => {
  console.log(tag, v)
  return v
})
const split = curry(function(step,arr){
  return arr.split(step)
})
const map = function(arr){
  return arr.map(function(item){
    return item.toLowerCase()
  })
}
const join = curry(function(step,str){
  return str.join(step)
})

const f = compose(join('-'), trace('map 之后'), map, trace('split 之后'), split(' '))

console.log(f('NEVER SAY DIE'))

pointerFree编程风格

它的具体实现就是上面所说的函数组合,我们完全可以把数据处理的过程,定义成一种与参数无关的合成运算。不需要用到代表数据的那个参数,只要把一些简单的运算步骤合成在一起即可。

不使用所要处理的值,只合成运算过程。中文可以译作"无值"风格。

1.不需要指明处理的数据;

2.只需要合成运算过程;

3.需要定义一些辅助的基本运算函数

 

使用函数组合在处理问题的时候,其实就是一种PointFree模式,比如下面的这个案例,在这个案例中我们先把一些基本的运算合成为一个函数,而在这个过程中是没有指明要处理的数据的,这就是PointFree模式。

看一个例子:还是要把Hello World转换为hello_world这样的形式;

按照我们传统的思维方式,我们会先定义一个函数,来接收一个我们要处理的数据,接着我们在这个函数里面对我们的数据进行处理,得到我们想要的结果,这是非PointFree模式。

function f (word) {
    return word.toLowerCase().replace(/\s+/, '_');
}
f('Hello World')

而我们如果使用PointFree模式来解决这个问题的话,我们首先会定义一些基本的运算函数,然后把他们何成为一个新的函数,而在合成的过程中我们不需要指明我们需要处理的数据。

const fp = require('lodash/fp')
const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower)
console.log(f('Hello World'))

再来一个例子:把单词中的首字母提取并转换成大写

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

那我们来回顾一下函数式编程的核心,其实就是把运算过程抽象成函数。PointFree模式就是把我们抽象出来的函数再合成为一个新的函数,而这个合成的过程其实又是一个抽象的过程。在这个抽象的过程中我们依然是不需要关心数据的。

上面面我们使用PointFree模式来实现一下上面的案例。在这里进行函数组合时用到了lodash函数库;lodash 是一个非常有用的库,前面用到的柯里化方法就是lodash中的curry,上面我们模拟的compose组合方法也用lodash库的flowRight方法代替;

lodash库里还有fp模块,它提供了实用的 对函数式编程友好的方法,已经被柯里化后的方法!,如果一个方法的参数是函数的话,它会要求函数优先,数据滞后 。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值