资讯专栏INFORMATION COLUMN

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

Jonathan Shieber / 1345人阅读

摘要:提到函数式编程,就不得不提柯里化和组合。说实话,在之前的项目开发中,对柯里化和组合的运用不是太多,因为不太清楚应该在哪些情况下应该使用它们。所以在这篇文章中,我们将详细的介绍柯里化和组合的用法以及使用场景。

提到函数式编程,就不得不提柯里化和组合。说实话,在之前的项目开发中,对柯里化和组合的运用不是太多,因为不太清楚应该在哪些情况下应该使用它们。所以在这篇文章中,我们将详细的介绍柯里化和组合的用法以及使用场景。

柯里化 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://www.ucloud.cn/yun/91134.html

相关文章

  • SegmentFault 技术周刊 Vol.16 - 浅入浅出 JavaScript 函数编程

    摘要:函数式编程,一看这个词,简直就是学院派的典范。所以这期周刊,我们就重点引入的函数式编程,浅入浅出,一窥函数式编程的思想,可能让你对编程语言的理解更加融会贯通一些。但从根本上来说,函数式编程就是关于如使用通用的可复用函数进行组合编程。 showImg(https://segmentfault.com/img/bVGQuc); 函数式编程(Functional Programming),一...

    csRyan 评论0 收藏0
  • JavaScript函数编程,真香组合(一)

    摘要:组合的概念是非常直观的,并不是函数式编程独有的,在我们生活中或者前端开发中处处可见。其实我们函数式编程里面的组合也是类似,函数组合就是一种将已被分解的简单任务组织成复杂的整体过程。在函数式编程的世界中,有这样一种很流行的编程风格。 JavaScript函数式编程,真香之认识函数式编程(一) 该系列文章不是针对前端新手,需要有一定的编程经验,而且了解 JavaScript 里面作用域,闭...

    mengbo 评论0 收藏0
  • 函数编程的兴衰与当前崛起

    摘要:函数式编程逐渐被边缘化,被抛弃到学术界和非主流的场外。组合式编程的重新崛起年左右,有个巨大的变化爆发了。人们开始逐渐在私下里谈论函数式编程。箭头函数对于函数式编程的爆发起到了推动剂的作用。现在很少看到那种不用函数式编程的大型应用了。 showImg(https://segmentfault.com/img/remote/1460000009036867?w=800&h=364); 本...

    binaryTree 评论0 收藏0
  • 全本 | iKcamp翻译 | 《JavaScript 轻量级函数编程》|《你不知道的JS》姊妹篇

    摘要:本书主要探索函数式编程的核心思想。我们在中应用的仅仅是一套基本的函数式编程概念的子集。我称之为轻量级函数式编程。通常来说,关于函数式编程的书籍都热衷于拓展阅读者的知识面,并企图覆盖更多的知识点。,本书统称为函数式编程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 译者团队(排名不分先后)...

    paney129 评论0 收藏0
  • 翻译连载 | JavaScript 轻量级函数编程-第3章:管理函数的输入 |《你不知道的JS》姊

    摘要:但是,对函数式编程而言,这个行为的重要性是毋庸置疑的。关于该模式更正式的说法是偏函数严格来讲是一个减少函数参数个数的过程这里的参数个数指的是希望传入的形参的数量。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTML 最坚实的梁柱;分享,是...

    xiaowugui666 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<