第1章 掌握函数输入

本文章将介绍 partial functions, currying(strict curried & looseCurried), uncurried(将curried函数扁平化), 一元 函数(unary function), point-free等。

  1. partial(), bind(), reverseArgs(), partialRight()
  2. curry(), looseCurry(), uncurry()
  3. identity(), constant(), unary()
  4. 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函数等。

你可能感兴趣的:(第1章 掌握函数输入)