JS函數柯里化

定義

函數柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。javascript

例如,一些分析技術只能用於具備單一參數的函數。現實中的函數每每有更多的參數。爲單一參數狀況提供解決方案已經足夠了,由於能夠將具備多個參數的函數轉換爲一個單參數的函數鏈。這種轉變是如今被稱爲「柯里化」的過程。html

實現

咱們先寫一個實現加法的函數 add:java

function add (x, y) {
  return (x + y)
}
add (1, 3);//4
複製代碼

根據柯里化的條件,寫一個 curriedAdd 的函數。編程

function curriedAdd (x) {
  return function(y) {
    return x + y
  }
}
let addOne=curriedAdd(1);
addOne(3);//4
複製代碼

固然以上實現是有一些問題的:它並不通用,咱們想實現一個通用的方法而不是每次都須要針對某個問題實現 currying 化。app

此時咱們須要用到 JavaScript 中的做用域來保存上一次傳進來的參數。 對 curriedAdd 進行抽象,可能會獲得以下函數 currying :函數式編程

function currying (fn, ...args1) {
    return function (...args2) {
        return fn(...args1, ...args2)
    }
}
var increment = currying(add, 1)
increment(2) === 3
// true
複製代碼

在此實現中,currying 函數的返回值實際上是一個接收剩餘參數而且返回計算值的函數,它的返回值並無自動被 Currying化。函數

具體的說,若是須要 currying 化的函數的參數變成3個,按照這種寫法post

function add3 (x, y, z) {
  return (x + y + z)
}

var increment = currying(add3, 1)
increment(2, 3) // 6
increment(2)(3) // increment(...) is not a function
複製代碼

由於 increment(2) 沒有被 currying 化,因此會報錯。而實際上咱們但願increment(2)(3)結果等同於 increment(2, 3),這樣須要利用函數遞歸改進方法。fetch

function trueCurrying(fn, ...args) {
    if (args.length >= fn.length) {
        return fn(...args)
    }
    return function (...args2) {
        return trueCurrying(fn, ...args, ...args2)
    }
}
複製代碼

以上函數很簡短,可是這個方法已經能夠將js函數 Currying 化。然而 Currying 的概念和如何實現都不是最重要的,重點是:它可以解決編碼和開發當中怎樣的問題。ui

使用場景

先舉個例子,有這麼一大塊數據

var data = {
    result: "SUCCESS",
    interfaceVersion: "1.0.3",
    requested: "10/17/2013 15:31:20",
    lastUpdated: "10/16/2013 10:52:39",
    tasks: [
        {id: 104, complete: false,            priority: "high",
                  dueDate: "2013-11-29",      username: "Scott",
                  title: "Do something",      created: "9/22/2013"},
        {id: 105, complete: false,            priority: "medium",
                  dueDate: "2013-11-22",      username: "Lena",
                  title: "Do something else", created: "9/22/2013"},
        {id: 107, complete: true,             priority: "high",
                  dueDate: "2013-11-22",      username: "Mike",
                  title: "Fix the foo",       created: "9/22/2013"},
        {id: 108, complete: false,            priority: "low",
                  dueDate: "2013-11-15",      username: "Punam",
                  title: "Adjust the bar",    created: "9/25/2013"},
        {id: 110, complete: false,            priority: "medium",
                  dueDate: "2013-11-15",      username: "Scott",
                  title: "Rename everything", created: "10/2/2013"},
        {id: 112, complete: true,             priority: "high",
                  dueDate: "2013-11-27",      username: "Lena",
                  title: "Alter all quuxes",  created: "10/5/2013"}
        // , ...
    ]
};
複製代碼

如今咱們要處理它,找到用戶 Scott 的全部未完成任務,並按到期日期升序排列。原文給了一段這樣的方法。

getIncompleteTaskSummaries = function(membername) {
    return fetchData()
        .then(function(data) {
            return data.tasks;
        })
        .then(function(tasks) {
            var results = [];
            for (var i = 0, len = tasks.length; i < len; i++) {
                if (tasks[i].username == membername) {
                    results.push(tasks[i]);
                }
            }
            return results;
        })
        .then(function(tasks) {
            var results = [];
            for (var i = 0, len = tasks.length; i < len; i++) {
                if (!tasks[i].complete) {
                    results.push(tasks[i]);
                }
            }
            return results;
        })
        .then(function(tasks) {
            var results = [], task;
            for (var i = 0, len = tasks.length; i < len; i++) {
                task = tasks[i];
                results.push({
                    id: task.id,
                    dueDate: task.dueDate,
                    title: task.title,
                    priority: task.priority
                })
            }
            return results;
        })
        .then(function(tasks) {
            tasks.sort(function(first, second) {
                var a = first.dueDate, b = second.dueDate;
                return a < b ? -1 : a > b ? 1 : 0;
            });
            return tasks;
        });
};
getIncompleteTaskSummaries('Scott').then(r => console.log(r));
複製代碼

這段代碼很長,可是很簡單,這不就是咱們常常作的事情,固然我以爲這段代碼能夠稍微改進一下,畢竟咱們有了 ES6。

var getIncompleteTaskSummaries = function (membername) {
    return Promise.resolve(data)
        .then(data => {
            return data.tasks;
        })
        .then(tasks => {
            return tasks.filter((task) => {
                return task.username === membername
            });
        })
        .then(tasks => {
            return tasks.filter((task) => {
                return !task.complete
            });
        })
        .then(tasks => {
            return tasks.map((task) => {
                return {
                    id: task.id,
                    dueDate: task.dueDate,
                    title: task.title,
                    priority: task.priority
                }
            });
        })
        .then(tasks => {
            return tasks.sort((first, second) => {
                return first.dueDate < second.dueDate ? -1
                    : first.dueDate > second.dueDate ? 1 : 0;
            });
        });
};
getIncompleteTaskSummaries('Scott').then(r => console.log(r));
複製代碼

好了,稍微好了一些,這基本上就是咱們正常寫的代碼了。咱們能夠看到,咱們每一次都是把咱們要處理的數據加工一下,獲得的結果送到下一個函數體內加工。若是按照函數式編程的思想,這些中間量其實都是不須要的。

那麼如今咱們要用一個叫 ramda 的js組件

const R = require("ramda");
var getIncompleteTaskSummaries = function(membername) {
    return Promise.resolve(data)
        .then(R.prop('tasks'))
        .then(R.filter(R.propEq('username', membername)))
        .then(R.reject(R.propEq('complete', true)))
        .then(R.map(R.pick(['id', 'dueDate', 'title', 'priority'])))
        .then(R.sortBy(R.prop('dueDate')));
};

getIncompleteTaskSummaries('Scott').then(r => console.log(r));
複製代碼

執行這段代碼,結果和以前的同樣,可是簡潔程度不用多說了。

可是看完這個例子,仔細推敲其實就能明白柯里化到底作了什麼。若是仍是不清楚,仔細看一下 ramda 的源碼,大多都是柯里化的函數。在 ramda 中隨便挑一個命名爲 prop 的函數感覺一下:

R.prop('dueDate') 

// 第一個參數p爲'dueDate'
var prop = /*#__PURE__*/_curry2(function prop(p, obj) {
  return path([p], obj);
});

function _curry2(fn) {
  return function f2(a, b) {
    switch (arguments.length) {
      case 0:
        return f2;
      case 1:
        return _isPlaceholder(a) ? f2 : _curry1(function (_b) {
          return fn(a, _b);
        });
      default:
        return _isPlaceholder(a) && _isPlaceholder(b) ? f2 : _isPlaceholder(a) ? _curry1(function (_a) {
          return fn(_a, b);
        }) : _isPlaceholder(b) ? _curry1(function (_b) {
          return fn(a, _b);
        }) : fn(a, b);
    }
  };
}

function _isPlaceholder(a) {
       return a != null && typeof a === 'object' && a['@@functional/placeholder'] === true;
}

function _curry1(fn) {
  return function f1(a) {
    if (arguments.length === 0 || _isPlaceholder(a)) {
      return f1;
    } else {
      return fn.apply(this, arguments);
    }
  };
}

//真正的處理業務的方法
var path = /*#__PURE__*/_curry2(function path(paths, obj) {
  var val = obj;
  var idx = 0;
  while (idx < paths.length) {
    if (val == null) {
      return;
    }
    val = val[paths[idx]];
    idx += 1;
  }
  return val;
});
複製代碼

ramda 代碼都很簡單,我以爲更像教科書,你們能夠本身研究一下。

柯里化不是諱莫如深的東西,光看概念是很差理解,實際運用起來就是這麼簡單,原來還能夠這麼封裝函數。

參考

JavaScript高級程序設計

Pointfree 編程風格指南

Favoring Curry

大佬,JavaScript 柯里化,瞭解一下?

相關文章
相關標籤/搜索