函数式编程之柯里化和组合详解

提到函数式编程,就不得不提柯里化和组合。说实话,在之前的项目开辟中,对柯里化和组合的运用不是太多,由于不太清晰应当在哪些情况下应当运用它们。所以在这篇文章中,我们将细致的引见柯里化和组合的用法以及运用场景。

柯里化 Curry

起首说说什么是柯里化, 简朴来说就是部份运用, 也就是说 只通报函数的一部份参数来挪用它,让它返回一个函数去处置惩罚剩下的参数

参数复用

先来看个例子,建立一个 say 函数,打印出带有名字,前缀和问候语的一句话。

const say = (name, prefix, greeting) => `${greeting}, ${prefix} ${name}!`;

say('Tom', 'Mr', 'Hello'); // "Hello, Mr Tom"
say('James', 'Mr', 'Hello'); // "Hello, Mr James"

在上面的例子中,我们每一次挪用 say 函数都必需传入完全的三个参数,才保证准确的运转效果,不然,虽然递次照样会一般运转,然则未传入的部份会变成 undefined

应用柯里化,我们可以牢固住个中的部份参数,在挪用的时刻,这个参数就相当于已被记住了,不须要再次通报,也就是我们这里说的参数复用

const say = prefix => greeting => name => `${greeting}, ${prefix} ${name}!`;
const sayToMr = say('Mr');
const sayToMiss = say('Miss');
const greetMr = sayToMr('Hello');
const greetMiss = sayToMiss('Hi');

greetMr('Tom'); // "Hi, Miss Cindy!"
greetMiss('Cindy'); // "Hello, Mr Tom!"

这时候刻假如我们想输入雷同的问候语 Hello, 我们发明,本来的构造彷佛不太满足了呃,因而我们最先调解参数的位置。

const say = greeting => prefix => name => `${greeting}, ${prefix} ${name}!`;
const greet = say('Hello');
const greetMeiNv = greet('玉人');
const greetShuaiGe = greet('帅哥');

greetShuaiGe('Tom'); // "Hello, 帅哥 Tom!"
greetMeiNv('Cindy'); // "Hello, 玉人 Cindy!"

Note: 在运用柯里化的时刻,参数的递次很主要,可以斟酌依据
易变化的水平来分列参数,把不容易变化的参数经由过程柯里化牢固起来,将须要处置惩罚的参数放到末了一名。

耽误实行

在上面的例子中,经由过程柯里化,我们竟然多造出了 3 个函数!几乎就是函数工场嘛!然则猛地一想,假如假如是 100 个参数呢,岂非要写一百次?有无一种要领可以简朴的帮我们完成柯里化?

我要最先放书上的代码了。


function curry(fn) {
  var outerArgs = Array.prototype.slice.call(arguments, 1);
  
  return function() {
    var innerArgs = Array.prototype.slice.call(arguments),
      finalArgs = outerArgs.concat(innerArgs);
    return fn.apply(null, finalArgs);
  };
}

const say = (name, prefix, greeting) => `${greeting}, ${prefix} ${name}!`;

const curriedSay = curry(say);
curriedSay('Tom', 'Mr', 'Hello'); // "Hello, Mr Tom!"
curry(say,'Tom', 'Mr')('Hello');  // "Hello, Mr Tom!"

简朴解释一下上面的代码,起首是获得除了第一个参数 fn 以外的一切的外部传参 outerArgs,这里的 arguments 是一个长得像数组的对象,所以我们要运用 Array.proptype.slice 将其转变成真正的数组。 innerArgs 用来猎取挪用这个匿名函数时的传参。末了将外部传参 outerArgs 和内部传参 innerArgs 兼并,挪用 fn。也就是说这时候 fn 才被挪用。

就比如刷信用卡和储蓄卡,刷储蓄卡就是把你的钱立时转到他人口袋,刷信用卡是银行先帮你垫着,到下个月再把钱还给银行。总之,末了都是花本身的钱。不过如许有一个优点就是,就是可以让你养成拆分函数,并给函数优越定名的习气,以及更好的处置惩罚和笼统代码的逻辑。

运用 Ramda / Lodash 天生柯里化函数

固然,你也可以可以运用 lodash 或许 ramda 如许的库来疾速柯里化你的函数,如许可以省去许多反复造轮子的事情。

下面以运用 lodash 为例。

const say = (prefix, name, greeting) => `${greeting}, ${prefix} ${name}!`;
const curreiedSay = _.curry(say);

curreiedSay('Mr','Tom','Hello'); // "Hello, Mr Tom!"
curreiedSay('Mr')('Tom','Hello'); // "Hello, Mr Tom!"
curreiedSay('Mr')('Tom')('Hello'); // "Hello, Mr Tom!"
curreiedSay('Tom')(_,'Hello')('Mr'); // "Hello, Mr Tom!"

lodash 和 Ramda 都供应了一系列柯里化函数的包装要领,感兴趣的同砚可以翻开 lodash / ramda 官网,在 console 内里试一下。

组合 Compose

组合,望文生义,也就是把多个函数组合起来变成一个函数。

const compose = (fn1, fn2) => args => fn1(fn2(args));

const toUpperCase = value => value.toUpperCase();
const addSuffix = value => `${value} is good!`;

const format = compose(toUpperCase, addSuffix);
format('apple'); // "APPLE IS GOOD!"

上面的例子中,fn2 先实行,然后将返回值作为 fn1 的参数,所以 compose 内里的要领是从右向左实行的。就像一条流水线一样,a 流水线先把汽车组装好,然后交给 b 流水线举行喷漆,再交给 c 流水线打磨等等,末了获得一辆极新的汽车。

连系柯里化和组合 Curry + Compose

进修完柯里化和组合以后,让我们将它们连系起来运用,肯定可以碰撞出更强的火花,发生更大的威力。

说写就写。

假设有一个数组,我们希冀先对数组举行去重,然后对数组举行乞降或求积。

const unique = arr => _.uniq(arr); // 数组去重
const sum = arr => _.reduce(arr, (total, n) => total + n); // 数组元素的累加之和

const multiply = arr => _.reduce(arr, (total, n) => total * n); // 数组元素的乘积

const getTotal = fn => arr => _.flowRight(fn, unique)(arr); // 从右至左, 先去重, 再实行 fn

const arr1 = [1, 2, 3, 4, 4, 5, 5];
const arr2 = [1, 2, 2, 3, 4, 4, 5];

const getSumTotal = getTotal(sum); // 经由过程柯里化发生一个新的函数
const getMultiplyTotal = getTotal(multiply);   // 经由过程柯里化发生一个新的函数

getSumTotal(arr1); // 15
getMultiplyTotal(arr2); // 120

如今的前端社区中,函数式编程随处可见,柯里化和组合也成为了我们必需控制的妙技。在项目开辟中,可以不停的去增强演习。

    原文作者:橘子小睿
    原文地址: https://segmentfault.com/a/1190000007328944
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞