本文章將介紹 partial functions, currying(strict curried & looseCurried), uncurried(將curried函數(shù)扁平化), 一元 函數(shù)(unary function), point-free等。
-
partial(),bind(),reverseArgs(),partialRight() -
curry(),looseCurry(),uncurry() -
identity(),constant(),unary() -
not(),when()(本章省略)
一.Partial Applications
當(dāng)部分或單獨(dú)的實(shí)參已知,傳統(tǒng)的函數(shù)要一次性傳入所有的實(shí)參,采用部分函數(shù)可以將函數(shù)拆解成更小的部分,知道所有的參數(shù)依次傳入后才執(zhí)行。同時(shí)這是一種減元的方式。
比如: ajax(url, data, cb)這個(gè)函數(shù)接收3個(gè)參數(shù),如果我們一開始只知道url, data, cb后來才能知道,我們可以寫為:
function getPerson(data, cb) {
ajax("http://some.api/person", data, cb);
}
function getOrder(data, cb) {
ajax("http://some.api/order", data, cb);
}
然后再創(chuàng)建一個(gè)函數(shù)
function getCurrentUser(cb) {
getPerson({user: CURRENT_USER_ID}, cb);
}
可以根據(jù)上面的流程寫出一個(gè)
1.工具函數(shù)partial
/**
* @param(func) fn 表示要部分化的函數(shù)
* @param presetArgs 第一次加入的參數(shù)
* @param laterArgs 后來添加的參數(shù)
*/
function partial(fn, ...presetArgs) {
return function partiallyApplied(...laterArgs) {
return fn(...presetArgs, ...laterArgs);
};
}
// ES6
const partial =
(fn, ...presetArgs) =>
(...laterArgs) =>
fn(...presetArgs, ...laterArgs);
因此可以寫為:
var getPerson = partial(ajax, "http://some.api/person")
// getPerson的結(jié)構(gòu)為
var getPerson = function partiallyApplied(...laterArgs) {
return ajax("http://some.api/person", ...laterArgs);
}
接著我們可以寫:
var getCurrentUser = partial(
ajax,
"http://some.api/person",
{user: CURRENT_USER_ID}
);
// 表示為
var getCurrentUser = function partiallyApplied(...laterArgs) {
return ajax(
"http://some.api/person",
{user: CURRENT_USER_ID}
...laterArgs
);
}
或者寫為
var getCurrentUser = partial(getPerson, {user: CURRENT_USER_ID});
// 相當(dāng)于
var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs) {
var getPerson = function innerPartiallyApplied(...innerLaterArgs) {
return ajax("http://some.api/person", ...innerLaterArgs);
}
return getPerson({user: CURRENT_USER_ID}, ...outerLaterArgs);
}
例子:
// 將所有傳入的值加起來, 但傳入的參數(shù)個(gè)數(shù)不一定
var sum = (...args) =>
args.reduce((init, cur) =>
init + cur,
0
);
// 使用 partial
var sumPart1 = partial(sum, 1, 2);
var sumPart2 = partial(sumPart1, 3, 4)
sumPart2(5); // 15
// 或者
partial(sum, 1, 4)(2, 3); // 10
2.bind()
ES5中的bind()方法有2個(gè)作用:1.設(shè)置上下文; 2.部分應(yīng)用實(shí)參(partial apply arguments)
比如利用部分應(yīng)用實(shí)參功能時(shí):
var getPerson = ajax.bind(null, "http://some.api/person")'
3.reverseArgs()將傳入的參數(shù)反過來
比如ajax(url, data, cb),變?yōu)閍jax(cb, data, url)
function reverseArgs(fn) {
return function argsReversed(...args) {
return fn(...args.reverse());
};
}
// ES6
const reverseArgs =
fn =>
(...args) =>
fn(...args.reverse());
4.partialRight()
部分參數(shù)從右邊開始, 其余的參數(shù)從左邊開始不變, 比如先傳入cb,然后傳入url, data
function partialRight(fn, ...presetArgs) {
return reverseArgs(
// 將先傳入的參數(shù)presetArgs放到后面
partial(reverseArgs(fn), ...presetArgs.reverse())
)
}
例子1:
let cache = {};
// 雖然先傳入cb
// 但是partialRight保證回調(diào)函數(shù)作為最后一個(gè)參數(shù)傳入
var cacheResult = partialRight(ajax, function onResult(obj) {
cache[obj.id] = obj;
});
// 再傳入url, data
cacheResult("http://some.api/person", {user: CURRENT_USER_ID});
例子2:
const getArr =
(...args) =>
args.reduce(
(init, cur) => init.concat([cur]),
[]
);
// 第一個(gè)參數(shù)為最右邊的參數(shù), 其余參數(shù)從左到右
partialRight(getArr, "a")("b", "c", "d"); // ["b", "c", "d", "a"]
二.currying函數(shù)
currying函數(shù)是一種特殊的partial函數(shù),比如上面的ajax(url, data, cb),curry化后可寫為ajax(url)(data)(cb)
1.strict currying
嚴(yán)格curried表示高階函數(shù)返回的函數(shù)每次只能接收一個(gè)實(shí)參
/**
* @param(func) fn 表示要curry化的函數(shù)
* @param arity 表示函數(shù)fn的元數(shù)
* @param length 為函數(shù)類型的一個(gè)只讀屬性,表示參數(shù)長度
*/
function curry(fn, arity = fn.length) {
// 首先返回一個(gè)IIEF
return (function nextCurried(prevArgs) {
// nextArg 表示返回的函數(shù)再次傳入的實(shí)參
return curried(nextArg) {
// 將所有的參數(shù)添加到args數(shù)組中
var args = prevArgs.concat([nextArg]);
if (args.length >= arity) {
return fn(...args);
} else {
return nextCurried(args);
}
}
})([]); // []表示prevArgs的初始值
}
// ES6
const curry =
(fn, arity = fn.length) =>
( nextCurried = prevArgs =>
nextArg => {
let args = prevArgs.concat([nextArg]);
if (args.length >= arity) {
return fn(...args);
} else {
return nextCurried(args);
}
}
)( [] );
例子:
var sum = (...args) =>
args.reduce((init, cur) =>
init + cur,
0
);
// 3表示curried次數(shù), 后面調(diào)用3次
curry(sum, 3)(5)(6)(7); // 18
// 當(dāng)然根據(jù)sum函數(shù)我們可以知道,每次傳入多個(gè)實(shí)參都會(huì)reduce為1個(gè)參數(shù)
// 注意看起來(1, 2, 3)為一次很多參數(shù), 但是最后經(jīng)過sum reduce都只有一個(gè)參數(shù)
// 這樣與curry只能接收一個(gè)參數(shù)并不矛盾
// 所以可以寫為
curry(sum, 4)(1, 2, 3)(6)(7)(10, 12); // 24
2.loose currying
loose currying其實(shí)和上面的strict currying很相像,但是做了上面sum一樣的處理,返回的curried函數(shù)可以接受多個(gè)參數(shù)
function looseCurry(fn, arity = fn.length) {
return ( function nextCurried(prevArgs) {
// 注意這里為nextArgs復(fù)數(shù)
return function curried(...nextArgs) {
let args = prevArgs.concat(nextArgs);
if (args.length >= arity) {
return fn(...args);
} else {
return nextCurried(args);
}
};
})([]);
}
// ES6
const looseCurry =
(fn, arity = fn.length) =>
(nextCurried = prevArgs =>
curried = (...nextArgs) => {
let args = prevArgs.concat(nextArgs);
if (args.length >= arity) {
return fn(...args);
} else {
return nextCurried(args)
}
}
)([]);
例子:
// 為了避免和嚴(yán)格curry混淆, add函數(shù)不適用reduce
function add(...args) {
let result = 0;
for (let i = 0; i < args.length; i++) {
result += args[i];
}
return result;
}
looseCurry(add, 3)(1, 2, 3); // 6
looseCurry(add, 3)(1, 2)(3); // 6
looseCurry(add, 3)(1)(2, 3); // 6
looseCurry(add, 3)(1)(2, 3)(2); // error 參數(shù)的個(gè)數(shù)超過3個(gè)
3.uncurry
將curry化的函數(shù)扁平化,例如
fn(1)(2)(3) ---> fn(1, 2, 3)
function uncurry(fn) {
return function uncurried(...args) {
var ret = fn;
for (let i = 0; i < args.length; i++) {
ret = ret(args[i]);
}
return ret;
};
}
// ES6
var uncurry =
fn =>
(...args) => {
var ret = fn;
for (let i = 0; i < args.length; i++) {
ret = ret(args[i]);
}
return ret;
};
注意:不要認(rèn)為 uncurry(curry(f)) 和 f 行為一樣, 只有當(dāng)傳入的參數(shù)和原來函數(shù)一致時(shí),uncurried 和 f行為一致, 如果傳入部分參數(shù), 將得到一個(gè)部分函數(shù)
function add(...args) {
let result = 0;
for (let i = 0; i < args.length; i++) {
result += args[i];
}
return result;
}
var curriedAdd = curry(add, 3);
var uncurriedAdd = uncurry(curriedAdd);
curriedAdd(1)(2)(3); // 6
uncurriedAdd(1, 2, 3); // 6
uncurriedAdd(1, 2)(3); // 6
三.小功能函數(shù)
1.one on one
函數(shù)只有一個(gè)參數(shù),然后原封不動(dòng)的返回
function identity(v) {
return v;
}
// ES6
const identity =
v =>
v;
看著沒什么用, 但是函數(shù)編程中這種函數(shù)卻是十分有用的。
用處1:
var words = " Now is the time for all... ".split( /\s|\b/ );
words; // ["","Now","is","the","time","for","all","...",""]
words.filter( identity );
// ["Now","is","the","time","for","all","..."]
// 強(qiáng)制轉(zhuǎn)換成true,false
用處2: 在用于參數(shù)變形中
// 當(dāng)作默認(rèn)值
function output(msg, formatFn = identity) {
msg = formatFn(msg);
return msg;
}
function upper(txt) {
return txt.toUpperCase();
}
output("hello", upper); // "HELLO"
output("hello"); // "hello"
如果output() formatFn 沒有一個(gè)默認(rèn)值我們可以使用前面的partialRight()
// 相當(dāng)于把upper賦值給formatFn
var specialOutput = partialRight(output, upper);
// 相當(dāng)于把identity賦值給formatFn
var simpleOutput = partialRight(output, identity);
specialOutput("hello"); // "HELLO"
simpleOutput("hello"); // "hello"
2.Unchange One
有一些API不讓直接傳入一個(gè)值,而是需要一個(gè)函數(shù),比如ES6中的Promise中的then()
function constant(v) {
return function value() {
return v;
}
}
// ES6
const constant =
v =>
() =>
v;
例子:
// 利用箭頭函數(shù)雖然簡潔,
// 但是函數(shù)編程的角度這樣不是很好
p1.then(foo).then(_ => p2).then(bar);
// 更好的寫法
p1.then(foo).then(constant(p2)).then(bar);
3.all for one
一元函數(shù),一個(gè)函數(shù)可以接受多個(gè)參數(shù),但是有時(shí)候我們只想要單一實(shí)參, 這對(duì)loose curried函數(shù)很常見
function unary(fn) {
return function onlyOneArg(arg) {
return fn(arg);
};
}
// ES6
const unary =
fn =>
arg =>
fn(arg);
例子:
var adder = looseCurry(add, 2); // 這個(gè)loose curry函數(shù)將接受2個(gè)參數(shù)
[1, 2, 3, 4, 5].map(adder(3));
// ["41, 2, 3, 4, 5", "61, 2, 3, 4, 5"...]
// 顯然這不是我們想要的
// 我們只想要一個(gè)參數(shù)
[1, 2, 3, 4, 5].map(unary(adder(3))); // [4, 5, 6, 7, 8]
// 還有
["1", "2"].map(parseFloat); // [1, 2]
["1", "2"].map(parseInt); // [1, NaN]
// 因?yàn)閜arseInt后面有2個(gè)參數(shù)parseInt(str, radix),
// 數(shù)組會(huì)將index作為第2個(gè)參數(shù)傳入到parseInt中,導(dǎo)致出錯(cuò)
// 可以使用unary
["1", "2"].map( unary(parseInt) ); // [1, 2]
4.not()否定
function not(predicate) {
return function negated(...args) {
return !predicate(...args);
};
}
// ES6
const not =
predicate =>
(...args) =>
!predicate(...args);
例子:
function isShortEnough(str) {
return str.length <= 5;
}
function isLongEnough(str) {
return !isShortEnough(str);
}
// 或者直接用上面定義的not函數(shù)
var isLongEnough = not(isShortEnough);
總結(jié)
本章主要學(xué)習(xí)了partial application, currying, 還有一些重要的操作,比如identity(), unary(), constant()等;另外還有一些point-free 函數(shù)樣式書寫風(fēng)格, 如not函數(shù)等。