iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 3
4
Software Development

Functional Programming in JS系列 第 3

Buzz Word 1 : Declarative vs. Imperative

https://ithelp.ithome.com.tw/upload/images/20200904/20106426bGnG71Oiho.jpg
前一篇算是站在一個 High Level 角度大略說明 FP 好處,這一篇開始會有一系列 Buzz Words for FP 的介紹,這些專有名詞也算是讓新手卻步的門檻,但其實沒有這麼難,我覺得比較像是用不同角度去解釋類似的事情。

Declarative 宣告式 vs. Imperative 命令式

Functional programming 是 Declarative Paradigm 的代表,邏輯為用較為抽象的程式碼,理解程式碼想要達到怎樣的目標,Function 之間不會互相共用 state 狀態,而 Imperative 代表 OOP 比較容易寫出狀態互相依賴的程式碼。
https://ithelp.ithome.com.tw/upload/images/20200903/20106426z5UUcte6kF.jpg

Imperative is How to do

Imperative programming is a programming paradigm that uses statements that change a program's state. 

著重在 HOW,具體表達程式碼該做什麼才能達到目標,比較像我以前撰寫程式的思維,程式一步一步按著順序照著你給他指示執行。Imperative 比較常運用 Statement ,也就是 if , while , for , switch 等。

  • Example: OOP
  • Stateful
  • Can mutate state
  • 常會有 Side Effect 發生

Declarative is What to do

Declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow.

著重在該做什麼 WHAT ,並採取抽象化流程。Declarative 比較常運用表達式 expression,表達式特色是單純運算並一定會有返回值

  • Example: FP
  • Stateless: 你只要專心於當前算式,結束後再也不用管他
  • Constants, immutability
  • No Side Effect

舉一個例子,將 Array 加總

// Imperative is How to do
var array = [3,2,1]
var total = 0
for( var i = 0 ; i <= array.length ; i ++ ){
    total += array[i]
}

可以看到上面是一個運算的過程,著重 "如何" 達到預期結果!

// Declarative is What to do
var array = [3,2,1]
array.reduce( function( previous, current ){ 
    return previous + current 
})

Declarative 則是可以直接知道做了 "什麼",恩,看不太出來 Declarative 的好處啊?
我再舉一個更複雜的例子,今天有一個函式 doStuff ,input 是一個字串 'Welcome to FP world ',需要

  • 轉小寫、去掉空格 welcome to fp world
  • 輸出個別字串長度 > 3 字元的字串 welcome world
  • 反轉 world welcome
// Imperative is How to do
// 'Welcome to FP world'
const doStuff = str => {
  const lower = str.toLowerCase()  // 'welcome to fp world'
  const words = lower.split('') // ['welcome', 'to', 'fp', 'world']
 
  words.reverse() // ['world', 'fp', 'to', 'welcome']
 
  for(let i in words) {
    words[i] = words[i].trim()
  } // ['world', 'fp', 'to', 'welcome']
 
  let keepers = []
 
  for(let i in words) {
    if(words[i].length > 3) {
      keepers.push(words[i])
    }  // ['world', 'welcome']
  }
 
  return keepers.join(' ') // 'world welcome'
}

doStuff('Welcome to FP world')

以上可以看到他照著需求一步一步達到目標,中間也是用到很多 statement 跟共用 state (eg. keepers)。

// Declarative is What to do
// 'Welcome to FP world'
const doStuff = _.compose(
  join(' '),
  _.filter(x => x.length > 3),
  reverse,
  _.map(trim),
  split(' '),
  toLowerCase
)

換成 FP 就變得相當易讀。

Note. 感謝 Mike良葛格為什麼要學 Functional Programming? 留言中幫我釐清,並不是要屏棄 OOP 直接投入 FP,而是清楚他們的優缺點後,再套用到合適的專案,甚至也可以擷取各自優點兩個一起用。

來對照一下吧

這個表格是參考這邊,用不同面向來比較兩種 paradigm,自己覺得蠻有幫助所以就附註在這裡。

BASIS FP OOP
Data Uses immutable data. Uses mutable data.
Model Follow a declarative programming model. Follow an imperative programming model.
Execution The statements can be executed in any order. The statements should be executed in particular order.
Iteration Recursion is used for iterative data. Loops are used for iterative data.
Element The basic elements are Variables and Functions. The basic elements are objects and methods.
Use Often be used when there are few things with more operations. Often be used when there are many things with few operations.

參考文章

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

上一篇
為什麼要學 Functional Programming?
下一篇
Do Everything with Function
系列文
Functional Programming in JS30

2 則留言

1
listennn08
iT邦高手 8 級 ‧ 2020-09-03 08:54:56

例子的寫法不太對

const words = lower.split(' ') // ['welcome', 'to', 'fp', 'world ']
這裡會是產出 ['welcome', 'to', 'fp', 'world', '']

另外筆誤的地方

return keepers.join('-') // 'world welcome'

寫文章辛苦了/images/emoticon/emoticon32.gif

hannahpun iT邦新手 5 級 ‧ 2020-09-03 10:54:25 檢舉

真的是筆誤(汗),已更新,謝謝

5
良葛格
iT邦新手 3 級 ‧ 2020-09-04 08:01:08

Declarative is What to do 建議改為 Declarative is What's xxx ... 也就是宣告 xxx 是什麼。

類似地,「Declarative 則是可以直接知道做了 "什麼"」建議改為「Declarative 則是定義 xxx 是 "什麼"」

將 Array 加總的例子,建議可以是:

  • 命令式
function sum(array) {
    // 做加總必須累加
    let total = 0
    for(let i = 0 ; i < array.length ; i ++ ){
        total += array[i]
    }
    return total;
}

console.log(sum([3, 2, 1]));
  • 函數式
function sum(array) {
    // 空陣列,總和就是 0
    if(array.length === 0) {
        return 0;
    }
    
    let head = array[0];
    let tail = array.slice(1);
    // 總和就是陣列第一項的值與餘下陣列的總和
    return head + sum(tail);
}

console.log(sum([3, 2, 1]));

reduce 本身在 FP 中,算是比較高階的抽象了,以上例來說,若你將 sunhead + sum(tail) 部份,想辦法再抽離出來,就會朝著 reduce 的方向重構,因此 reduce 的運用通常會封裝為函式,突顯其意圖,例如:

function sum(array) {
    return array.reduce((accumulator, elem) => accumulator + elem);
}

console.log(sum([3, 2, 1]));

這段範例:

// Imperative is How to do
// 'Welcome to FP world'
const doStuff = str => {
      ...
}

doStuff('Welcome to FP world')

改一下註解部份:

// Imperative is How to do stuff
// 'Welcome to FP world'
const doStuff = str => {
  ...
}

doStuff('Welcome to FP world')

這段範例:

// Declarative is What to do
// 'Welcome to FP world'
const doStuff = _.compose(
  join(' '),
  _.filter(x => x.length > 3),
  reverse,
  _.map(trim),
  split(' '),
  toLowerCase
)

建議改一下註解與命名:

// Declarative is What's stuff
// 'Welcome to FP world'
const stuff = _.compose(
  join(' '),
  _.filter(x => x.length > 3),
  reverse,
  _.map(trim),
  split(' '),
  toLowerCase
)

雖然只是寫作上的一個技巧,不過應該可以更突顯你想表達的意思。

我要留言

立即登入留言