瞭解 JavaScript 函數式編程 - 柯里化

瞭解JavaScript函數式編程目錄

一、什麼是 curry

curry

curry 就是咖喱同樣美好的工具性的拌料讓咱們的函數更加的易用、低耦合性。html

curry 的概念很簡單:只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數。 你能夠一次性地調用 curry 函數,也能夠每次只傳一個參數分屢次調用。git

var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);
var addTen = add(10);

increment(2);
// 3

addTen(2);
// 12
複製代碼

curry 的重要性

在這個多彩的世界,有些事物對與咱們來講並非非必須的,就像咱們早已習慣存在可是又非必須的東西:互聯網,移動手機,微波爐,電梯等等。當他們不存在的時候咱們也能正常的快樂的生存下去,可是一旦擁有了之後他們的存在就變得不可或缺。就像咱們的 curry 工具同樣。github

  • 咱們來建立一個普通的 curry ,for your enjoyment 吧。這裏用到了 lodash 函數庫,不熟悉的朋友能夠看一下 lodash 的官網
var curry = require('lodash').curry;

var match = curry(function(what, str) {
  return str.match(what);
});

var replace = curry(function(what, replacement, str) {
  return str.replace(what, replacement);
});

var filter = curry(function(f, ary) {
  return ary.filter(f);
});

var map = curry(function(f, ary) {
  return ary.map(f);
});
複製代碼

我在上面的代碼中遵循的是一種簡單,同時也很是重要的模式。即策略性地把要操做的數據(String, Array)放到最後一個參數裏。到使用它們的時候你就明白這樣作的緣由是什麼了。npm

  • 下面咱們開始使用上面的代碼,看看爲何會這麼去處理咱們的函數。
// 匹配空格
match(/\s+/g, "hello world");
// [ ' ' ]

match(/\s+/g)("hello world");
// [ ' ' ]

// 引出一個 hasSpace 的函數變量,暫存用
var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }

// 使用這個 hasSpace 去作一些相關的處理
hasSpaces("hello world");
// [ ' ' ]

hasSpaces("spaceless");
// null

// 如今咱們知道了它的返回值,咱們能夠經過其餘函數作進一步的處理。好比篩選出一個有空格的數組值
filter(hasSpaces, ["tori_spelling", "tori amos"]);
// ["tori amos"]

// 保存這個 findSpace 的函數變量
var findSpaces = filter(hasSpaces);
// function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }

// 輕鬆的使用吧
findSpaces(["tori_spelling", "tori amos"]);
// ["tori amos"]

var noVowels = replace(/[aeiou]/ig);
// function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }

var censored = noVowels("*");
// function(x) { return x.replace(/[aeiou]/ig, "*") }

censored("Chocolate Rain");
// 'Ch*c*l*t* R**n'
複製代碼

咱們爲甚要這麼多繁瑣的步驟,而不是一步到位? 編程

這裏代表的是一種「預加載」函數的能力,經過傳遞一到兩個參數調用函數,就能獲得一個記住了這些參數的新函數。分解的使用的函數,讓每一個函數更具備必定的獨立性,使用導出的時候,作到純淨無污染的傳遞。數組

擴展咱們的 curry

curry 的用處很是普遍,就像在 hasSpacesfindSpacescensored 看到的那樣,只需傳給函數一些參數,就能獲得一個新函數。app

  • 下面咱們用map包裹一下咱們的函數
var getChildren = function(x) {
  return x.childNodes;
};

var allTheChildren = map(getChildren);
複製代碼

只傳給函數一部分參數這樣的操做一般被稱爲局部調用(partial application),由於只須要內聯調用可以大量減小樣板文件代碼(boilerplate code)。less

當咱們談論純函數的時候,咱們說它們接受一個輸入返回一個輸出。curry 函數所作的正是這樣:每傳遞一個參數調用函數,就返回一個新函數處理剩餘的參數。這就是一個輸入對應一個輸出啊。ide

練習一下

  • 這裏引用了 ramda,若是沒有的話能夠手動引入安裝一下和引用。
    • npm install ramda
var _ = require('ramda');

// 練習 1(局部調用的使用)
//==============
// 經過局部調用(partial apply)移除全部參數

var words = function(str) {
  return split(' ', str);
};

// 練習 1a(組合使用函數)
//==============
// 使用 `map` 建立一個新的 `words` 函數,使之可以操做字符串數組

var sentences = undefined;


// 練習 2
//==============
// 經過局部調用(partial apply)移除全部參數

var filterQs = function(xs) {
  return filter(function(x){ return match(/q/i, x);  }, xs);
};


// 練習 3(柯里化~)
//==============
// 使用幫助函數 `_keepHighest` 重構 `max` 使之成爲 curry 函數

// 無須改動:
var _keepHighest = function(x,y){ return x >= y ? x : y; };

// 重構這段代碼:
var max = function(xs) {
  return reduce(function(acc, x){
    return _keepHighest(acc, x);
  }, -Infinity, xs);
};


// use curry 1:
// ============
// 包裹數組的 `slice` 函數使之成爲 curry 函數
// //[1,2,3].slice(0, 2)
var slice = undefined;


// use curry 2:
// ============
// 藉助 `slice` 定義一個 `take` curry 函數,該函數調用後能夠取出字符串的前 n 個字符。
var take = undefined;
複製代碼

這是上面的答案 Q&A,先別急着看答案,讓咱們先思考一下函數式編程

總結

經過簡單地傳遞幾個參數,就能動態建立實用的新函數;並且還能帶來一個額外好處,那就是保留了數學的函數定義,儘管參數不止一個。

下篇連接 瞭解 JavaScript 函數式編程 - 代碼組合的優點

參考

相關文章
相關標籤/搜索