瞭解 JavaScript 函數語言程式設計 - 程式碼組合的優勢

Pandaaa發表於2019-05-04

上篇文章 瞭解 JavaScript 函數語言程式設計 - 柯里化

程式碼組合

瞭解 JavaScript 函數語言程式設計 - 程式碼組合的優勢

養殖程式碼

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

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

fg 都是函式,x 是在它們之間通過“管道”傳輸的值。這裡得注意一下 compose 函式是組合程式碼思想中最重要的一環,下面?會經常用到。大家可以提前 mark 一下

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));
};
複製程式碼

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

瞭解 JavaScript 函數語言程式設計 - 程式碼組合的優勢 - 可以拆解,組合成其他的樂高模型 瞭解 JavaScript 函數語言程式設計 - 程式碼組合的優勢
  • 不可拆解,出廠的時候已經設計好了。

瞭解組合程式碼

讓程式碼從右向左執行,而不是由內而外執行,我覺得可以稱之為“左派”(消音~)。我們來看一個順序很重要的例子:

var head = function(x) { return x[0]; };
var reverse = reduce(function(acc, x){ return [x].concat(acc); }, []);
var last = compose(head, reverse);

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

上面就是一個反轉陣列的操作,這裡我們看到一個組合函式的執行順利,我們可以主動的操作函式進行從左到右的方式,但是從右向左的執行順序更加符合數學上的含義。基本的高中數學知識

// 結合律(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

參考

相關文章