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

瞭解JavaScript函數式編程目錄

代碼組合

養殖代碼

組合函數看起來像是在搭積木。你就是一個孩子,能夠隨意選擇兩個積木(函數),讓它們拼接(結合)一下,拼接成一個新的玩具(函數)。組合的用法以下:html

var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
};
複製代碼

fg 都是函數,x 是在它們之間經過「管道」傳輸的值。這裏得注意一下 compose 函數是組合代碼思想中最重要的一環,下面👇會常常用到。你們能夠提早 mark 一下git

var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);

shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"
複製代碼

就像這樣咱們把兩個函數組合以後返回一個新的函數。編程

對比組合函數

思考一下: 組合的函數和一個完整流程的函數有什麼區別 數組

var shout = function(x){
  return exclaim(toUpperCase(x));
};
複製代碼

能夠看到組合的函數就像樂高玩具同樣能夠自由的組合成其餘的可用的完整的模型樂高建模(完整功能的函數),可是一個像上方同樣完整功能,不可拆卸對的函數的。它就像一個已經完成的手辦。架構

- 能夠拆解,組合成其餘的樂高模型
  • 不可拆解,出廠的時候已經設計好了。

瞭解組合代碼

讓代碼從右向左運行,而不是由內而外運行,我以爲能夠稱之爲「左派」(消音~)。咱們來看一個順序很重要的例子:app

var curry = require("lodash").curry;
var reduce = curry(function(f, init, arr){
    return arr.reduce(f, init);
});

// 感謝掘友(@thsing772)提示,這裏勘誤一下 reduce 函數,應該是須要先 curry 處理一下,才能以下使用。
var head = function(x) { return x[0]; };
var reverse = reduce(function(acc, x){ return [x].concat(acc); }, []);
// 固然你也能夠不使用 curry
// var reverse = x => x.reduce(function (arr,x){return [x].concat(ar)}[]);
var last = compose(head, reverse);

last(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'uppercut'
複製代碼

上面就是一個反轉數組的操做,這裏咱們看到一個組合函數的執行順利,咱們能夠主動的操做函數進行從左到右的方式,可是從右向左的執行順序更加符合數學上的含義。基本的高中數學知識ide

// 結合律(associativity)
var associative = compose(f, compose(g, h)) == compose(compose(f, g), h);
// true

compose(toUpperCase, compose(head, reverse));

// 或者
compose(compose(toUpperCase, head), reverse);
複製代碼

結合律的好處

結合律的好處是任何一個函數分組均可以被拆解開來,而後再以他們本身的組合打包在一塊兒,組合成新的函數。curry 就是咱們的工具包。函數式編程

  • 下面用到了上面 compose 、head、reverse 函數
var loudLastUpper = compose(exclaim, toUpperCase, head, reverse);

// 或
var last = compose(head, reverse);
var loudLastUpper = compose(exclaim, toUpperCase, last);

// 或
var last = compose(head, reverse);
var angry = compose(exclaim, toUpperCase);
var loudLastUpper = compose(angry, last);

// 更多變種...
複製代碼

pointfree 空數據模式

pointfree 模式是,no data 模式的意思。這裏有一句來自《Love Story》70 年代的電影的臺詞--「Love means never having to say you're sorry」。函數

  • 咱們的 pointfree 模式就是「Pointfree style means never to say your data」。
  • 下面咱們就可使用柯里化、組合代碼中實現如下 pointfree style
// 非 pointfree,由於提到了數據:word
var snakeCase = function (word) {
  return word.toLowerCase().replace(/\s+/ig, '_');
};

// pointfree
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);

// 不明白爲何看看最上面的 compose 函數,而後在 控制檯試試 snakeCase 函數

snakeCase('Hello World')
// hello_world
複製代碼

在 pointfree 版本中,不須要 word 參數就能構造函數;而在非 pointfree 的版本中,必需要有 word 才能進行一切操做。工具

pointfree 模式可以幫助咱們減小沒必要要的命名,讓代碼保持簡潔和通用。

組合代碼的常見問題 debug~

組合的一個常見錯誤是,在沒有局部調用以前,就組合相似 map 這樣接受兩個參數的函數。

// 下面部分函數來自於上面的 #### 結合律的好處

// 錯誤作法:咱們傳給了 `angry` 一個數組,根本不知道最後傳給 `map` 的是什麼東西。
var latin = compose(map, angry, reverse);

latin(["frog", "eyes"]);
// error


// 正確作法:每一個函數都接受一個實際參數。
var latin = compose(map(angry), reverse);

latin(["frog", "eyes"]);
// ["EYES!", "FROG!"])
複製代碼

使用 trace(追蹤) 來跟蹤你的函數

var trace = curry(function(tag, x){
  console.log(tag, x);
  return x;
});

var dasherize = compose(join('-'), toLower, split(' '), replace(/\s{2,}/ig, ' '));

dasherize('The world is a vampire');
// TypeError: Cannot read property 'apply' of undefined

--------------

// 看到報錯了,來 trace 一下
var dasherize = compose(join('-'), toLower, trace("after split"), split(' '), replace(/\s{2,}/ig, ' '));
// after split [ 'The', 'world', 'is', 'a', 'vampire' ]

-------------

// tolower 的參數徐亞的是一個數組,因此這裏咱們 fix 一下咱們的代碼
var dasherize = compose(join('-'), map(toLower), split(' '), replace(/\s{2,}/ig, ' '));

dasherize('The world is a vampire');

// 'the-world-is-a-vampire'
複製代碼

trace 的好處就是能夠直接定位到函數調用的地方,容許咱們在某個特定的點去觀察咱們的函數數據

總結

咱們能夠認爲哦組合是高於其餘全部原則的設計原則,這是由於組合讓咱們的代碼簡單而富有可讀性。另外範疇學將在應用架構、模擬反作用和保證正確性方面扮演重要角色。

衍生知識點:一些命名方式

name demo
CamelCase(小駝峯) personId
PascalCase(帕斯卡/大駝峯) PersonId
SnakeCase(下橫線) person_id
KebabCase(中橫線) person-id

參考

相關文章
相關標籤/搜索