淺談函數柯里化

函數柯里化就是將接收多個參數的函數轉變爲一系列接收單一參數的函數,而且返回接收餘下參數的函數。

柯里化是什麼

柯里化, 即 Currying 的音譯。Currying 是編譯原理層面實現多參函數的一個技術。javascript

咱們在編碼的過程當中,本質工做就是將複雜的問題分解成多個可編程的小問題。java

Currying 就是將接收多個參數的函數轉變爲一系列接收單一參數的函數,而且返回接收餘下參數的函數。git

舉例來講:github

//一個接收4個參數的函數
let demo = function(a,b,c,d){
    console.log([a,b,c,d]);
}

//假設咱們有一個轉換函數 curry

//生成一個柯里化函數
let _demo = curry(demo);

_demo(1)(2)(3)(4); // [1,2,3,4]

須要注意的是,在編譯原理層面的柯里化,一次只能傳遞給函數一個參數;而咱們在編程過程當中實際使用的柯里化函數,是能夠傳遞給函數一個或多個參數。ajax

仍是剛纔的例子:編程

//一個接收4個參數的函數
let fn = function(a,b,c,d){
    console.log([a,b,c,d]);
}

//假設咱們有一個轉換函數 curry

//生成一個柯里化函數
let _fn = curry(fn);

_fn(1)(2)(3)(4); // [1,2,3,4]
_fn(1,2)(3,4); // [1,2,3,4]
_fn(1)(2,3,4); // [1,2,3,4]

當咱們知道柯里化是什麼了的時候,咱們來看看柯里化到底有什麼用?數組

用途

來看一個例子:app

// 示意而已
function ajax(type, url, data) {
    var xhr = new XMLHttpRequest();
    xhr.open(type, url, true);
    xhr.send(data);
}

// 雖然 ajax 這個函數很是通用,但在重複調用的時候參數冗餘
ajax('POST', 'www.test.com', "name=kevin")
ajax('POST', 'www.test2.com', "name=kevin")
ajax('POST', 'www.test3.com', "name=kevin")

// 利用 curry
var ajaxCurry = curry(ajax);

// 以 POST 類型請求數據
var post = ajaxCurry('POST');
post('www.test.com', "name=kevin");

// 以 POST 類型請求來自於 www.test.com 的數據
var postFromTest = post('www.test.com');
postFromTest("name=kevin");

curry 的這種用途能夠理解爲:參數複用。本質上是下降通用性,提升適用性。函數

但是即使如此,是否是依然感受沒什麼用呢?工具

再來看一個例子:

假設咱們有這樣一段數據:

var personList = [{name: 'bingshan'}, {name: 'group'}]

如今咱們須要將 personList 中的全部 name 值提取出來,放入一個變量 names 中。

一般狀況下,咱們會這樣來實現:

let names = list.map(function(item) {
  return item.name;
})

然而,使用柯里化的思想,咱們會這樣實現:

var prop = curry(function(key,obj){
    return obj[key];
})
var names = person.map(prop('name'));

看到這個例子,可能不少人會說,使用柯里化的思想以後,變得更麻煩了。

其實不是的,prop 函數只須要建立一次,之後每次獲取數組對象中的元素的時候,均可以拿來直接使用的,咱們是能夠將其看作是一個工具函數,與咱們的業務代碼無關。

那麼此時,咱們的業務代碼就只剩一行了,而且看起來比傳統書寫方式更加直觀易懂

var names = person.map(prop('name'));

實現

接下來,咱們來思考如何實現 curry 函數。

回想以前咱們對於柯里化的定義,接收一部分參數,返回一個函數接收剩餘參數,接收足夠參數後,執行原函數。

function curry(fn,...params){
    return function(...args){
        let _args = [...params,...args];
        if(_args.length >= fn.length){
            return fn.apply(this,_args);
        }else{
            return curry.call(this,fn,..._args);
        }
    }
}

驗證一下:

//一個接收4個參數的函數
let demo = function(a,b,c,d){
    console.log([a,b,c,d]);
}

let _demo = curry(demo);

_demo(1,2,3,4);         // print: [1,2,3,4]
_demo(1)(2)(3,4);       // print: [1,2,3,4]
_demo(1,2)(3,4);        // print: [1,2,3,4]
_demo(1)(2)(3)(4);      // print: [1,2,3,4]

優化

咱們以前完成的 curry 函數,依賴與原函數的形參個數,只有接受的總參數個數大於等於原函數的形參個數時,纔會調用原函數。

那麼若是咱們但願傳入的參數個數在不等於原函數的形參個數的時候,原函數依然能執行,咱們該怎麼作呢?很簡單,將 curry 函數修改一下:

function curry(fn,len = fn.length,...params){
    return function(...args){
        let _args = [...params,...args];
        if(_args.length >= len){
            return fn.apply(this,_args);
        }else{
            return curry.call(this,fn,len,..._args);
        }
    }
}

再來驗證一下:

//一個接收4個參數的函數
let demo = function(a,b,c,d){
    console.log([a,b,c,d]);
}

//接收三個參數後,執行原函數
let _demo = curry(demo,3);

_demo(1)(2)(3); // [1,2,3,undefined]

擴展

對於咱們經常使用的工具函數庫 lodash 不只提供了 curry 工具函數,還提供了參數佔位符的玩法:

import _ from 'lodash'
let demo = function(a,b,c,d){
    console.log([a,b,c,d]);
}
let _demo = _.curry(demo);
_demo(_,2)(1,_,4)(3) // [1,2,3,4]

參考連接

相關文章
相關標籤/搜索