第1章 掌握函數(shù)輸入

本文章將介紹 partial functions, currying(strict curried & looseCurried), uncurried(將curried函數(shù)扁平化), 一元 函數(shù)(unary function), point-free等。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容