iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 11
3

https://ithelp.ithome.com.tw/upload/images/20200911/20106426QIhLuw9NfR.jpg
FP 原則就是把 Function 作抽象化並極小化,Higher-order function 也就是抽象函式的一種,抽象式內容不包含執行步驟的細節,而是在 high level 的角度描述解決方法,例如以下有兩種煮蔬菜湯的描述

1. 一般思維

將一根胡蘿蔔、一根馬鈴薯的外皮洗乾淨,不用去皮,用刀開始切,刀尖指向11點鐘方向,切的時候刀不動,只轉動食材,切出來的塊狀就是滾刀塊。需注意的是,切滾刀塊時,左手一定要拿穩食材,隨著持菜刀右手切塊後再慢慢滾動。再來拿少許洗乾淨香菜一起放進鍋中,水的量要至少淹過食材然後小火煮 10 分鐘;乾香菇 10g 也直接放進去;接著開始切 30g 高麗菜,用左手手指微彎頂住刀背,右手微斜下刀將高麗菜先切一長片,必須將長片邊做底這樣高麗菜站得穩。將菜轉向後,以剛剛切長片處擺在砧板上,用手固定不滑動後。同樣再用左手手指微彎頂住刀背,右手握刀以微斜下刀的方式切圓剖面切片。再將切下的原形薄片整理好成一疊,再下刀切絲,完成絲後放入小火煮 10 分鐘。 蓋上鍋蓋,大火煮滾後,調成小火至軟爛就可以成為一人份的蔬菜湯。

2. 抽象思維

一人份蔬菜湯食材: 一根胡蘿蔔跟一根馬鈴薯不用去皮、10g 乾香菇、30g 高麗菜切絲、少許香菜。

步驟: 所有食材洗淨後,胡蘿蔔、馬鈴薯滾刀切塊、少許香菜一起入鍋燉煮 10 分鐘,再陸續加入乾香菇、切絲高麗菜再煮 10 分鐘後蓋上鍋蓋燉煮至軟爛。

相信兩種都可以煮成蔬菜湯,但第二種清楚簡潔多了,當然你要知道一些煮飯相關詞彙例如

燉煮、滾刀切塊、切絲

可以把這些詞彙可以看作是 Higher-order function
https://ithelp.ithome.com.tw/upload/images/20200911/20106426X8mV4DBxRY.jpg

換個實務上例子,以下哪個比較不容易出錯 ?

// 1)
let total = 0, count = 1;
while (count <= 10) {
  total += count;
  count += 1;
}
console.log(total);
// 2)
console.log(sum(range(1, 10)));

答案顯而易見是 2),並不是因為 2 比較短,因為假如包含 sumrenage 的 function 程式碼很有可能比 1) 還長,但重點是 Higher-order function(之後文章會盡量用簡稱 HOF) 直接用相對應的抽象函示名稱 ( sumrenage) 表達解決了什麼事

抽象化: 不是只有此功能可以用,他可以運用在所有地方,
大概知道概念以後,還是要來解釋一些專有名詞在程式上的定義了

First-class function

javaScript 的 function 是 first-class citizens,所以我們可以

1. Assign functions to variables

把函式存進變數

2. Pass functions as arguments to other functions

把函式當作參數傳入另一個函式

3. Return functions from other functions

在函式裡面回傳函式

直接來看範例比較好理解

/* 1. Assign functions to variables ---- */
const fisrtClassFunction1 = function() {}


/* 2. Pass functions as arguments to other functions --- */
const fisrtClassFunction2 = **callback** => {
  console.log("HOF...");
  callback();
}

function callbackFunction () {
  console.log("callbackFunction...");
}

// 除了 number、string、object、array 等等可以當參數外,
// function 也可以當成參數傳入 function
fisrtClassFunction2(callbackFunction);  



/* 3. Return functions from other functions ---- */
const fisrtClassFunction3 = function() {
  return function() {
		console.log("callbackFunction...");
	}
}
const myFistClassFunction = fisrtClassFunction3(); // return a function
myFistClassFunction(); 

最後一個範例其實也可以改成 es6 語法更清楚

// use ES6 arrow function instead
const fisrtClassFunction3 = () => () => {
		console.log("callbackFunction...");
}

const myFistClassFunction = fisrtClassFunction3(); // return a function
myFistClassFunction(); 

注意 First-class function 的後兩個特性,因為只要符合其一就可以稱作 Higher-order function

Higher-order function (HOF)

A function that takes a function as an argument, or returns a function as a result
wiki

只要符合下列其中一項就是 Higher order function

1. Takes one or more functions as arguments

把函式當作參數傳入另一個函式,ex .map().reduce().filter() 就是很好的例子,還有以上例子 fisrtClassFunction2

// 把 x => x + 1 函式當作參數傳入 map 函式
[1,2,3].map(x => x + 1); // => [2, 3, 4]

2. Returns a function

在函式裡面回傳函式,像之前的例子 fisrtClassFunction3

3. or both

來看一個簡單的例子吧

讓們來想想假如今天要輸出 1 - 10 怎麼寫

for (let i = 0; i < 10; i++) {
  console.log(i);
}

這樣數字就被寫死了,是不是可以抽象化成"做某件事 N 次"

function repeatLog(n) {
  for (let i = 0; i < n; i++) {
    console.log(i);
  }
}

看起來不錯,但若不想只是 logging 而是想做別的事呢

function repeat(n, action) {
  for (let i = 0; i < n; i++) {
    action(i);
  }
}

repeat(3, console.log);
// → 0
// → 1
// → 2

也可以像一下這樣做,直接傳入一個匿名函式

let labels = [];
repeat(5, i => {
  labels.push(`Unit ${i + 1}`);
});
console.log(labels);
// → ["Unit 1", "Unit 2", "Unit 3", "Unit 4", "Unit 5"]

這種把函式當成參數傳入另一個函式就是 HOF

Higher-order functions 實務上的用途

Utilities

通常會放在 utilities 裡面提供專案需要時引入,這一包基本上可以直接整包複製到別的專案裡,不應該有 Side Effect 跟任何 dependency 在裡面

Curried function / Composition

創建 Curried function 用來搭配 function composition 或重覆利用,Composition 也是 HOF ,他的無限組合用起來真的蠻猛的


參考文章

如有錯誤或需要改進的地方,拜託跟我說。
我會以最快速度修改,感謝您

上一篇
[練習] 玩轉 pure/impure function
下一篇
實作經典 HOF 之 filter、map、reduce
系列文
Functional Programming in JS30

尚未有邦友留言

立即登入留言