用大白話介紹柯里化函數

最近在學習函數式編程,看到柯里化函數這個東東,原覺得是個新的概念,沒想到一查,居然老早就存在了,且已經成熟運用多年,深感慚愧啊,我到如今都還沒接觸過。javascript

官方解釋

先是查看了柯里化的介紹java

在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數且返回結果的新函數的技術。編程

這句話聽起來有點拗口啊,反正我沒能徹底理解,不過不要緊,咱們能夠查看網上大牛們的解釋,通過一頓搜索,算是屢清楚了頭緒。segmentfault

個人理解

咱們先來看個栗子,這是一個求和的函數,求4個參數的和,那麼咱們在使用這個函數的時候,就要先知道每一個參數的具體的值,而後才能調用這個函數。數組

function add(a, b, c, d) {
    return a + b + c + d;
};
console.log(add(2,3,4,5))  // 14
複製代碼

而假如,咱們想讓調用更加靈活一些,好比改爲下面這種調用方式app

console.log(add(2)(3)(4)(5))  // 14
複製代碼

能夠看到,這種調用方式有個好處,不須要等待全部參數明確後在執行具體方法,當在參數不知足的狀況下,每次調用返回的是函數,好比 add(2) 返回的是一個函數,返回的函數不斷的接受新的參數,直到知足預約好的4個參數時,便輸出結果。 而 add(2,3,4,5) => add(2)(3)(4)(5) 這一個過程即是函數柯里化的過程。這樣子解釋,不知道大夥好理解不?函數式編程

talk is cheap , show me the code,ok 那咱們看下具體的實現,先寫一個函數將 add() 轉換成柯里化函數函數

/** * 轉換函數 * @param {*} fn 目標函數 * @param {...any} args 其餘參數 */
const createCurry = (fn, ...args) => {
    // 獲取目標函數的參數個數
    let length = fn.length;
    return (...rest) => {
        // 將已有的參數和新的參數合併
        let allArgs = args.slice(0);
        allArgs.push(...rest);
        // 若參數個數已經知足目標函數的參數要求,則執行目標函數。不然繼續返回轉換函數
        if (allArgs.length < length) {
            return createCurry.call(this, fn, ...allArgs)
        } else {
            return fn.apply(this, allArgs)
        }

    }
}
function add(a, b, c, d) {
    return a + b + c + d;
};
複製代碼

使用以下:學習

const curryAdd = createCurry(add,2);
const sum = curryAdd(3)(4)(5);    

console.log(sum) // 14
複製代碼

轉換柯里化函數有個關鍵的點,那就是要明確觸發條件,好比說上面的栗子中,咱們的觸發條件是參數的個數,根據參數的個數來區分返回的是函數仍是具體的結果。ui

柯里化函數的特色

經過上面的栗子咱們能夠看出柯里化函數有這麼幾個特色:

  1. 參數複用
  2. 業務解耦,調用時機靈活
  3. 延遲執行,部分求值

繼續舉栗子

光說理論太枯燥,那麼咱們把上面的栗子在稍微擴展下,把add函數改爲不定參數,就是說,我能夠傳n個參數,求這n個參數的和,調用以下

const curryAdd = createCurry(add);
const sum = curryAdd(3)(4)(5); // 12 
const sum = curryAdd(3)(4)(5)(6); // 18 
複製代碼

OK,首先咱們要對add函數作下修改

/** * 不定參數求和 * @param {...any} arg */
function add(...arg){
    return arg.reduce((result,value)=>{ return result += value },0)
}
複製代碼

那麼這個函數使用就變成 add(1,2,3),但咱們的目標是這樣的 add(1)(2)(3) ,在寫轉換函數以前,咱們先要肯定咱們的觸發條件,我想到的條件是:當新傳入的參數個數爲 0 的時候,就是沒有參數,就執行目標函數,不然返回函數。那麼柯里化函數的調用方式須要稍微改爲以下

add(1)(2)(3)();
複製代碼

具體的轉換函數和調用以下

const createCurry = (fn, ...args) => {
    return (...rest) => {
        let allArgs = args.slice(0);
        allArgs.push(...rest)
        // 觸發條件
        if (rest.length !== 0) {
            return createCurry.call(this, fn, ...allArgs)
        } else {
            return fn.apply(this, allArgs)
        }

    }
}
const curryAdd = createCurry1(add);
console.log(curryAdd(1)(2)(3)(4)()) // 10
複製代碼

柯里化究竟是什麼?

經過上面兩個小栗子,大夥應該有個大體概念了,因此柯里化是一種設計思想,主動掌握了函數的控制權,根據咱們的須要,設定相應的觸發機制。有句話,讓代碼編寫代碼,柯里化算是有那麼點意思吧,函數生成函數,函數去調用函數,咱們只要編寫原子性的函數和設定好條件。

再來兩個栗子

數組處理

寫個通用的數組處理柯里化函數,currying的第一個參數是目標函數,currying直接返回函數,沒有特定的觸發條件,在使用mapSQ([1, 2, 3, 4, 5]) 會將數組和currying的第二個參數一塊兒傳給目標函數,即currying第一個參數,詳細以下:

function currying(fn) {
    var slice = Array.prototype.slice,
        __args = slice.call(arguments, 1);
    return function () {
        var __inargs = slice.call(arguments);
        return fn.apply(null, __args.concat(__inargs));
    };
}

function square(i) {
    return i * i;
}
function double(i) {
    return i *= 2;
}

function map(handeler, list) {
    return list.map((value) => {
        return handeler(value)
    });
}

var mapSQ = currying(map, square); // 平方
mapSQ([1, 2, 3, 4, 5]); //[1, 4, 9, 16, 25]

var mapTwo = currying(map, double); // 兩倍
mapSQ([1, 2, 3, 4, 5]); //[2,4,6,8,10]

複製代碼

以參數長度爲條件的轉換函數

這個栗子是在 30secondsofcode 上看到,我以爲寫得很是精簡直觀,給你們分享下

const curry = (fn, arity = fn.length, ...args) => arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);

curry(Math.pow)(2)(10); // 1024
curry(Math.min, 3)(10)(50)(2); // 2
複製代碼

官方寫成了一行,閱讀可能不太方便,我改爲通俗版,加了些註釋

/** * 生成柯里化函數,以參數長度達標做爲觸發條件 * @param {*} fn 目標函數 * @param {*} arity 目標函數參數個數 * @param {...any} args 調用傳入的參數 */
const curry = (fn, arity = fn.length, ...args) => {
    if (arity <= args.length) {
        return fn(...args)
    } else {
        return curry.bind(null, fn, arity, ...args);
    }
}
複製代碼

小結

函數柯里化能夠給咱們帶來不少想象,能夠將耦合的業務邏輯拆解,使得函數編程更加純粹。不過我我的以爲柯里化函數要是太複雜,對大大下降代碼的可閱讀性和可維護性,因此柯里化雖然看着高大上,但仍是不能濫用。

參考資料

baike.baidu.com/item/柯里化

segmentfault.com/a/119000001…

相關文章
相關標籤/搜索