函數柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。javascript
例如,一些分析技術只能用於具備單一參數的函數。現實中的函數每每有更多的參數。爲單一參數狀況提供解決方案已經足夠了,由於能夠將具備多個參數的函數轉換爲一個單參數的函數鏈。這種轉變是如今被稱爲「柯里化」的過程。html
咱們先寫一個實現加法的函數 add:java
function add (x, y) {
return (x + y)
}
add (1, 3);//4
複製代碼
根據柯里化的條件,寫一個 curriedAdd 的函數。編程
function curriedAdd (x) {
return function(y) {
return x + y
}
}
let addOne=curriedAdd(1);
addOne(3);//4
複製代碼
固然以上實現是有一些問題的:它並不通用,咱們想實現一個通用的方法而不是每次都須要針對某個問題實現 currying 化。app
此時咱們須要用到 JavaScript 中的做用域來保存上一次傳進來的參數。 對 curriedAdd 進行抽象,可能會獲得以下函數 currying :函數式編程
function currying (fn, ...args1) {
return function (...args2) {
return fn(...args1, ...args2)
}
}
var increment = currying(add, 1)
increment(2) === 3
// true
複製代碼
在此實現中,currying 函數的返回值實際上是一個接收剩餘參數而且返回計算值的函數,它的返回值並無自動被 Currying化。函數
具體的說,若是須要 currying 化的函數的參數變成3個,按照這種寫法post
function add3 (x, y, z) {
return (x + y + z)
}
var increment = currying(add3, 1)
increment(2, 3) // 6
increment(2)(3) // increment(...) is not a function
複製代碼
由於 increment(2) 沒有被 currying 化,因此會報錯。而實際上咱們但願increment(2)(3)結果等同於 increment(2, 3),這樣須要利用函數遞歸改進方法。fetch
function trueCurrying(fn, ...args) {
if (args.length >= fn.length) {
return fn(...args)
}
return function (...args2) {
return trueCurrying(fn, ...args, ...args2)
}
}
複製代碼
以上函數很簡短,可是這個方法已經能夠將js函數 Currying 化。然而 Currying 的概念和如何實現都不是最重要的,重點是:它可以解決編碼和開發當中怎樣的問題。ui
先舉個例子,有這麼一大塊數據
var data = {
result: "SUCCESS",
interfaceVersion: "1.0.3",
requested: "10/17/2013 15:31:20",
lastUpdated: "10/16/2013 10:52:39",
tasks: [
{id: 104, complete: false, priority: "high",
dueDate: "2013-11-29", username: "Scott",
title: "Do something", created: "9/22/2013"},
{id: 105, complete: false, priority: "medium",
dueDate: "2013-11-22", username: "Lena",
title: "Do something else", created: "9/22/2013"},
{id: 107, complete: true, priority: "high",
dueDate: "2013-11-22", username: "Mike",
title: "Fix the foo", created: "9/22/2013"},
{id: 108, complete: false, priority: "low",
dueDate: "2013-11-15", username: "Punam",
title: "Adjust the bar", created: "9/25/2013"},
{id: 110, complete: false, priority: "medium",
dueDate: "2013-11-15", username: "Scott",
title: "Rename everything", created: "10/2/2013"},
{id: 112, complete: true, priority: "high",
dueDate: "2013-11-27", username: "Lena",
title: "Alter all quuxes", created: "10/5/2013"}
// , ...
]
};
複製代碼
如今咱們要處理它,找到用戶 Scott 的全部未完成任務,並按到期日期升序排列。原文給了一段這樣的方法。
getIncompleteTaskSummaries = function(membername) {
return fetchData()
.then(function(data) {
return data.tasks;
})
.then(function(tasks) {
var results = [];
for (var i = 0, len = tasks.length; i < len; i++) {
if (tasks[i].username == membername) {
results.push(tasks[i]);
}
}
return results;
})
.then(function(tasks) {
var results = [];
for (var i = 0, len = tasks.length; i < len; i++) {
if (!tasks[i].complete) {
results.push(tasks[i]);
}
}
return results;
})
.then(function(tasks) {
var results = [], task;
for (var i = 0, len = tasks.length; i < len; i++) {
task = tasks[i];
results.push({
id: task.id,
dueDate: task.dueDate,
title: task.title,
priority: task.priority
})
}
return results;
})
.then(function(tasks) {
tasks.sort(function(first, second) {
var a = first.dueDate, b = second.dueDate;
return a < b ? -1 : a > b ? 1 : 0;
});
return tasks;
});
};
getIncompleteTaskSummaries('Scott').then(r => console.log(r));
複製代碼
這段代碼很長,可是很簡單,這不就是咱們常常作的事情,固然我以爲這段代碼能夠稍微改進一下,畢竟咱們有了 ES6。
var getIncompleteTaskSummaries = function (membername) {
return Promise.resolve(data)
.then(data => {
return data.tasks;
})
.then(tasks => {
return tasks.filter((task) => {
return task.username === membername
});
})
.then(tasks => {
return tasks.filter((task) => {
return !task.complete
});
})
.then(tasks => {
return tasks.map((task) => {
return {
id: task.id,
dueDate: task.dueDate,
title: task.title,
priority: task.priority
}
});
})
.then(tasks => {
return tasks.sort((first, second) => {
return first.dueDate < second.dueDate ? -1
: first.dueDate > second.dueDate ? 1 : 0;
});
});
};
getIncompleteTaskSummaries('Scott').then(r => console.log(r));
複製代碼
好了,稍微好了一些,這基本上就是咱們正常寫的代碼了。咱們能夠看到,咱們每一次都是把咱們要處理的數據加工一下,獲得的結果送到下一個函數體內加工。若是按照函數式編程的思想,這些中間量其實都是不須要的。
那麼如今咱們要用一個叫 ramda 的js組件
const R = require("ramda");
var getIncompleteTaskSummaries = function(membername) {
return Promise.resolve(data)
.then(R.prop('tasks'))
.then(R.filter(R.propEq('username', membername)))
.then(R.reject(R.propEq('complete', true)))
.then(R.map(R.pick(['id', 'dueDate', 'title', 'priority'])))
.then(R.sortBy(R.prop('dueDate')));
};
getIncompleteTaskSummaries('Scott').then(r => console.log(r));
複製代碼
執行這段代碼,結果和以前的同樣,可是簡潔程度不用多說了。
可是看完這個例子,仔細推敲其實就能明白柯里化到底作了什麼。若是仍是不清楚,仔細看一下 ramda 的源碼,大多都是柯里化的函數。在 ramda 中隨便挑一個命名爲 prop 的函數感覺一下:
R.prop('dueDate')
// 第一個參數p爲'dueDate'
var prop = /*#__PURE__*/_curry2(function prop(p, obj) {
return path([p], obj);
});
function _curry2(fn) {
return function f2(a, b) {
switch (arguments.length) {
case 0:
return f2;
case 1:
return _isPlaceholder(a) ? f2 : _curry1(function (_b) {
return fn(a, _b);
});
default:
return _isPlaceholder(a) && _isPlaceholder(b) ? f2 : _isPlaceholder(a) ? _curry1(function (_a) {
return fn(_a, b);
}) : _isPlaceholder(b) ? _curry1(function (_b) {
return fn(a, _b);
}) : fn(a, b);
}
};
}
function _isPlaceholder(a) {
return a != null && typeof a === 'object' && a['@@functional/placeholder'] === true;
}
function _curry1(fn) {
return function f1(a) {
if (arguments.length === 0 || _isPlaceholder(a)) {
return f1;
} else {
return fn.apply(this, arguments);
}
};
}
//真正的處理業務的方法
var path = /*#__PURE__*/_curry2(function path(paths, obj) {
var val = obj;
var idx = 0;
while (idx < paths.length) {
if (val == null) {
return;
}
val = val[paths[idx]];
idx += 1;
}
return val;
});
複製代碼
ramda 代碼都很簡單,我以爲更像教科書,你們能夠本身研究一下。
柯里化不是諱莫如深的東西,光看概念是很差理解,實際運用起來就是這麼簡單,原來還能夠這麼封裝函數。
JavaScript高級程序設計