了解 JavaScript 函数式编程 - 柯里化

了解JavaScript函数式编程目录

一、什么是 curry

curry

curry 就是咖喱同样美好的工具性的拌料让咱们的函数更加的易用、低耦合性。html

curry 的概念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。 你能够一次性地调用 curry 函数,也能够每次只传一个参数分屡次调用。git

var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);
var addTen = add(10);

increment(2);
// 3

addTen(2);
// 12
复制代码

curry 的重要性

在这个多彩的世界,有些事物对与咱们来讲并非非必须的,就像咱们早已习惯存在可是又非必须的东西:互联网,移动手机,微波炉,电梯等等。当他们不存在的时候咱们也能正常的快乐的生存下去,可是一旦拥有了之后他们的存在就变得不可或缺。就像咱们的 curry 工具同样。github

  • 咱们来建立一个普通的 curry ,for your enjoyment 吧。这里用到了 lodash 函数库,不熟悉的朋友能够看一下 lodash 的官网
var curry = require('lodash').curry;

var match = curry(function(what, str) {
  return str.match(what);
});

var replace = curry(function(what, replacement, str) {
  return str.replace(what, replacement);
});

var filter = curry(function(f, ary) {
  return ary.filter(f);
});

var map = curry(function(f, ary) {
  return ary.map(f);
});
复制代码

我在上面的代码中遵循的是一种简单,同时也很是重要的模式。即策略性地把要操做的数据(String, Array)放到最后一个参数里。到使用它们的时候你就明白这样作的缘由是什么了。npm

  • 下面咱们开始使用上面的代码,看看为何会这么去处理咱们的函数。
// 匹配空格
match(/\s+/g, "hello world");
// [ ' ' ]

match(/\s+/g)("hello world");
// [ ' ' ]

// 引出一个 hasSpace 的函数变量,暂存用
var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }

// 使用这个 hasSpace 去作一些相关的处理
hasSpaces("hello world");
// [ ' ' ]

hasSpaces("spaceless");
// null

// 如今咱们知道了它的返回值,咱们能够经过其余函数作进一步的处理。好比筛选出一个有空格的数组值
filter(hasSpaces, ["tori_spelling", "tori amos"]);
// ["tori amos"]

// 保存这个 findSpace 的函数变量
var findSpaces = filter(hasSpaces);
// function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }

// 轻松的使用吧
findSpaces(["tori_spelling", "tori amos"]);
// ["tori amos"]

var noVowels = replace(/[aeiou]/ig);
// function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }

var censored = noVowels("*");
// function(x) { return x.replace(/[aeiou]/ig, "*") }

censored("Chocolate Rain");
// 'Ch*c*l*t* R**n'
复制代码

咱们为甚要这么多繁琐的步骤,而不是一步到位? 编程

这里代表的是一种“预加载”函数的能力,经过传递一到两个参数调用函数,就能获得一个记住了这些参数的新函数。分解的使用的函数,让每一个函数更具备必定的独立性,使用导出的时候,作到纯净无污染的传递。数组

扩展咱们的 curry

curry 的用处很是普遍,就像在 hasSpacesfindSpacescensored 看到的那样,只需传给函数一些参数,就能获得一个新函数。app

  • 下面咱们用map包裹一下咱们的函数
var getChildren = function(x) {
  return x.childNodes;
};

var allTheChildren = map(getChildren);
复制代码

只传给函数一部分参数这样的操做一般被称为局部调用(partial application),由于只须要内联调用可以大量减小样板文件代码(boilerplate code)。less

当咱们谈论纯函数的时候,咱们说它们接受一个输入返回一个输出。curry 函数所作的正是这样:每传递一个参数调用函数,就返回一个新函数处理剩余的参数。这就是一个输入对应一个输出啊。ide

练习一下

  • 这里引用了 ramda,若是没有的话能够手动引入安装一下和引用。
    • npm install ramda
var _ = require('ramda');

// 练习 1(局部调用的使用)
//==============
// 经过局部调用(partial apply)移除全部参数

var words = function(str) {
  return split(' ', str);
};

// 练习 1a(组合使用函数)
//==============
// 使用 `map` 建立一个新的 `words` 函数,使之可以操做字符串数组

var sentences = undefined;


// 练习 2
//==============
// 经过局部调用(partial apply)移除全部参数

var filterQs = function(xs) {
  return filter(function(x){ return match(/q/i, x);  }, xs);
};


// 练习 3(柯里化~)
//==============
// 使用帮助函数 `_keepHighest` 重构 `max` 使之成为 curry 函数

// 无须改动:
var _keepHighest = function(x,y){ return x >= y ? x : y; };

// 重构这段代码:
var max = function(xs) {
  return reduce(function(acc, x){
    return _keepHighest(acc, x);
  }, -Infinity, xs);
};


// use curry 1:
// ============
// 包裹数组的 `slice` 函数使之成为 curry 函数
// //[1,2,3].slice(0, 2)
var slice = undefined;


// use curry 2:
// ============
// 借助 `slice` 定义一个 `take` curry 函数,该函数调用后能够取出字符串的前 n 个字符。
var take = undefined;
复制代码

这是上面的答案 Q&A,先别急着看答案,让咱们先思考一下函数式编程

总结

经过简单地传递几个参数,就能动态建立实用的新函数;并且还能带来一个额外好处,那就是保留了数学的函数定义,尽管参数不止一个。

下篇连接 了解 JavaScript 函数式编程 - 代码组合的优点

参考

相关文章
相关标签/搜索