# 是純的!函式編程設計
###### tags: `fp`
## 聽眾調查
那在正式開始之前,我們先來調查一下。
- 沒有聽過函式編程,請在聊天室打 1。
- 有聽過函式編程但是沒有用過,請在聊天室打 2。
- 有用過函式編程,請在聊天室打 3。
那我想來問問各位,為什麼想要來聽這門課呢?
- 沒了解過想試試看
- 感覺很值得學習
- 看起來很炫
**我自己也覺得很炫炮拉!**
## 動機:說一段故事
而阿隆佐,一個宅,
覺得這樣的生活完全沒有必要,
比起去參加什麼茶會,
他覺得一個人的時候,工作效率會更高。
儘管如此,像這樣的一個宅男,
還是結交了一些不錯的朋友,
像是 艾倫,約翰 等等。
他們都對抽象數學 (aka 魔法) 很有興趣,
且他們的問題都有一個共同點,
他們都在嘗試解決關於計算方面的問題。
如果我們有一台擁有無限計算能力的機器,
那他可以用來解決什麼問題?
他可不可以自動地解決問題?
是不是有些問題他解決不了?如果有的話是什麼?
如果這些機器採用不同的設計,他們的算力相同嗎?
於是阿隆佐,宅男兼大魔法師,
設計了即便放到現在都鮮為人知的黑魔法,λ-calculus。
這套魔法威力十分強大,
幾乎任何一個可被計算的問題,都可以用它來解決,
但也因為這套古老的黑魔法實在太過超前,
當時並沒有受到太多人關注。
反觀他的學生艾倫圖靈,設計出的圖靈機,
被約翰馮諾伊曼應用在人類第一台電子計算機上。
巨大的光芒輾壓過了黑魔法,
原本阿隆佐的鉅作,
也大概會像古今眾多不得志的人們一樣,
消失在歷史的洪流之中。
二十年後,在 MIT 任教的約翰,
跟往常一樣在圖書館內做他的讀書狂,
但今天,他在角落發現了一本生灰的魔法書,
大受啟發的他應用了書中的概念,
製作了一套至今都還在用的高階語言,lisp,
並在人工智慧上獲得了巨大的成就,
而事實上,人工智慧就是約翰提出的概念。
基於 lisp 這位老祖宗的成功,
眾多應用了 λ-calculus 的語言也在日後被設計了出來,
它的設計彷彿打一開始就預判了當今時代會遭遇到的問題,
我相信未來,函式編程的概念只會更加廣泛被應用在各種場合上。
## 他的解決了什麼問題
### 單元測試
理想上,我應該只需要檢查函式 傳入值 跟 回傳值,
當我的傳入值跟回傳值符合預期,
表示這個函式符合我的期待。
如果我們總是需要額外檢查其他的部分,
像是執行後導致其他物件的狀態改變,
那我們需要考慮的因素就會增加,
測試就會更加困難,
越困難的東西就會越容易出錯,
測試就會越來越失去它的意義。
測試的主要目的是為了建立我們對於這段程式的信心,
如果測試項目會出現各式各樣的**例外**以及**意外**,
那這樣的測試結果又要如何帶給我們信心呢?
### 除錯
你曾有過花費一整天甚至一週就為了解決一個 bug 嗎?
自從我開始實踐函式編程後,除錯就沒有超過 1 小時。
為什麼函式編程這麼容易除錯,
因為錯誤百分之百可以被重現,
函式編程的程式碼沒有除了 input 跟 output 之外的事情需要判斷,
我們只需要檢查從哪個部分開始,
數值不符合我們的預期,
並沿著 call stack 往下追朔到根源就行了。
### 並發
不需要做任何的改動,所有的函式編程程序從一開始就可以並發。
因為本來就不用 lock,也就從來不需要擔心 dead lock 或是競速。
函式編程單一線程內都不能修改數據了,
就不用說多線程之間。
這使得我們可以毫無壓力的添加線程,
這樣的先天條件讓函式編程能在現今多核運算的時代如魚得水,
在未來,函式編程的設計只會越來越廣泛被應用,
這是必然的現象。
## 特色
### First Class Function
讓函式可以向一般的變數一樣被宣告跟傳遞。
> 在沒有支援一級函式的語言,
> 你需要 `class` 並建立他的 instance
> 才有辦法使用 `bar` 這段程式碼。
```java
class Foo {
void bar() {
}
}
Foo foo = new Foo();
foo.bar();
```
> 在有支援一級函式的語言,
> 函式就像宣告 `class` `String` `Number` 一樣,
> 你可以直接建立並執行它。
```typescript
function bar() {
}
bar()
```
> 你可以直接將函式當成參數,
> 就跟在傳遞 `String` `Number` 一樣
```typescript
function foo() {
console.log('hello foo')
}
function bar(fn) {
fn()
console.log('hello bar')
}
bar(foo);
```
補充 callback 跟 sort
### Pure Function
沒有副作用。
> 我們呼叫 `bar` 程式碼,
> 卻異動了 `foo` instance 的 `num`
```java
class Foo {
int num = 0;
void bar() {
num += 1;
}
}
Foo foo = new Foo();
System.out.println(foo.num); // 0
foo.bar();
System.out.println(foo.num); // 1
```
```typescript
function bar(num) {
return num + 1
}
const num = 0
console.log(num) // 0
const res = bar(num)
console.log(num) // 0
console.log(res) // 1
```
### Immutability
資料在傳遞的或是在計算的過程中,
都沒有可能被改變。
可變的操作
```typescript
const user = {
hasLogin: false
}
function login(user) {
user.hasLogin = true
}
console.log(user) // { hasLogin: false }
login(user)
console.log(user) // { hasLogin: true }
```
不可變性的操作
```typescript
const user = {
hasLogin: false
}
function login(user) {
return {
hasLogin: true
}
}
console.log(user) // { hasLogin: false }
const hasLoginUser = login(user)
console.log(hasLoginUser) // { hasLogin: true }
console.log(user) // { hasLogin: false }
```
可變性操作,異動了原本的物件屬性,
而不可變性操作,則是生成了一個新的物件。
## 觀念的延展
> 函式編程眾多的特性跟概念,很多是延展出來的,
彼此環環相扣,就如同數學一樣。
First Class Function
-> Closure
-> Higher Order Function
-> Curring
-> Compose
## 腦筋急轉彎
## 課程
### 共學課
### 專題演講
## 附錄
### 什麼是 side effect 副作用
在編程中,副作用是指對程序或系統狀態的任何更改,而這些更改不在正在執行的函數或方法中。
副作用可以包括對變量、數據結構、外部資源(如數據庫或文件系統)的更改,
甚至對其他函數的行為造成更改。
編程中的副作用的例子包括:
- 寫入文件或數據庫
- 修改變量或數據結構
- 在控制台上輸出或記錄輸出
- 發送網絡請求
- 拋出異常
- 觸發中斷或信號
一般來說,副作用可以使代碼更難理解和測試,因為它們引入了依賴關係和程序不同部分之間的潛在交互。為了減少副作用的影響,一些編程范式(如函數編程)強調使用沒有副作用的純函數。