本文章将介绍 partial functions, currying(strict curried & looseCurried), uncurried(将curried函数扁平化), 一元 函数(unary function), point-free等。
-
partial()
,bind()
,reverseArgs()
,partialRight()
-
curry()
,looseCurry()
,uncurry()
-
identity()
,constant()
,unary()
-
not()
,when()
(本章省略)
一.Partial Applications
当部分或单独的实参已知,传统的函数要一次性传入所有的实参,采用部分函数可以将函数拆解成更小的部分,知道所有的参数依次传入后才执行。同时这是一种减元的方式。
比如: ajax(url, data, cb)这个函数接收3个参数,如果我们一开始只知道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);
}
然后再创建一个函数
function getCurrentUser(cb) {
getPerson({user: CURRENT_USER_ID}, cb);
}
可以根据上面的流程写出一个
1.工具函数partial
/**
* @param(func) fn 表示要部分化的函数
* @param presetArgs 第一次加入的参数
* @param laterArgs 后来添加的参数
*/
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的结构为
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});
// 相当于
var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs) {
var getPerson = function innerPartiallyApplied(...innerLaterArgs) {
return ajax("http://some.api/person", ...innerLaterArgs);
}
return getPerson({user: CURRENT_USER_ID}, ...outerLaterArgs);
}
例子:
// 将所有传入的值加起来, 但传入的参数个数不一定
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个作用:1.设置上下文; 2.部分应用实参(partial apply arguments)
比如利用部分应用实参功能时:
var getPerson = ajax.bind(null, "http://some.api/person")'
3.reverseArgs()将传入的参数反过来
比如ajax(url, data, cb),变为ajax(cb, data, url)
function reverseArgs(fn) {
return function argsReversed(...args) {
return fn(...args.reverse());
};
}
// ES6
const reverseArgs =
fn =>
(...args) =>
fn(...args.reverse());
4.partialRight()
部分参数从右边开始, 其余的参数从左边开始不变, 比如先传入cb,然后传入url, data
function partialRight(fn, ...presetArgs) {
return reverseArgs(
// 将先传入的参数presetArgs放到后面
partial(reverseArgs(fn), ...presetArgs.reverse())
)
}
例子1:
let cache = {};
// 虽然先传入cb
// 但是partialRight保证回调函数作为最后一个参数传入
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]),
[]
);
// 第一个参数为最右边的参数, 其余参数从左到右
partialRight(getArr, "a")("b", "c", "d"); // ["b", "c", "d", "a"]
二.currying函数
currying函数是一种特殊的partial函数,比如上面的ajax(url, data, cb),curry化后可写为ajax(url)(data)(cb)
1.strict currying
严格curried表示高阶函数返回的函数每次只能接收一个实参
/**
* @param(func) fn 表示要curry化的函数
* @param arity 表示函数fn的元数
* @param length 为函数类型的一个只读属性,表示参数长度
*/
function curry(fn, arity = fn.length) {
// 首先返回一个IIEF
return (function nextCurried(prevArgs) {
// nextArg 表示返回的函数再次传入的实参
return curried(nextArg) {
// 将所有的参数添加到args数组中
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次数, 后面调用3次
curry(sum, 3)(5)(6)(7); // 18
// 当然根据sum函数我们可以知道,每次传入多个实参都会reduce为1个参数
// 注意看起来(1, 2, 3)为一次很多参数, 但是最后经过sum reduce都只有一个参数
// 这样与curry只能接收一个参数并不矛盾
// 所以可以写为
curry(sum, 4)(1, 2, 3)(6)(7)(10, 12); // 24
2.loose currying
loose currying其实和上面的strict currying很相像,但是做了上面sum一样的处理,返回的curried函数可以接受多个参数
function looseCurry(fn, arity = fn.length) {
return ( function nextCurried(prevArgs) {
// 注意这里为nextArgs复数
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)
}
}
)([]);
例子:
// 为了避免和严格curry混淆, add函数不适用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 参数的个数超过3个
3.uncurry
将curry化的函数扁平化,例如
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;
};
注意:不要认为 uncurry(curry(f))
和 f
行为一样, 只有当传入的参数和原来函数一致时,uncurried 和 f行为一致, 如果传入部分参数, 将得到一个部分函数
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
三.小功能函数
1.one on one
函数只有一个参数,然后原封不动的返回
function identity(v) {
return v;
}
// ES6
const identity =
v =>
v;
看着没什么用, 但是函数编程中这种函数却是十分有用的。
用处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","..."]
// 强制转换成true,false
用处2: 在用于参数变形中
// 当作默认值
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 没有一个默认值我们可以使用前面的partialRight()
// 相当于把upper赋值给formatFn
var specialOutput = partialRight(output, upper);
// 相当于把identity赋值给formatFn
var simpleOutput = partialRight(output, identity);
specialOutput("hello"); // "HELLO"
simpleOutput("hello"); // "hello"
2.Unchange One
有一些API不让直接传入一个值,而是需要一个函数,比如ES6中的Promise中的then()
function constant(v) {
return function value() {
return v;
}
}
// ES6
const constant =
v =>
() =>
v;
例子:
// 利用箭头函数虽然简洁,
// 但是函数编程的角度这样不是很好
p1.then(foo).then(_ => p2).then(bar);
// 更好的写法
p1.then(foo).then(constant(p2)).then(bar);
3.all for one
一元函数,一个函数可以接受多个参数,但是有时候我们只想要单一实参, 这对loose curried函数很常见
function unary(fn) {
return function onlyOneArg(arg) {
return fn(arg);
};
}
// ES6
const unary =
fn =>
arg =>
fn(arg);
例子:
var adder = looseCurry(add, 2); // 这个loose curry函数将接受2个参数
[1, 2, 3, 4, 5].map(adder(3));
// ["41, 2, 3, 4, 5", "61, 2, 3, 4, 5"...]
// 显然这不是我们想要的
// 我们只想要一个参数
[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]
// 因为parseInt后面有2个参数parseInt(str, radix),
// 数组会将index作为第2个参数传入到parseInt中,导致出错
// 可以使用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函数
var isLongEnough = not(isShortEnough);
总结
本章主要学习了partial application, currying, 还有一些重要的操作,比如identity(), unary(), constant()等;另外还有一些point-free 函数样式书写风格, 如not函数等。