---
title: Functional Programming 第八章 By Daniel
tags: F/E 分享, Functional Programming
###### tags: `F/E 分享`, `Functional Programming`
---
## Challenges of asynchronous code
書上提到非同步程式主要會遇到幾個問題
- The creation of temporal dependencies among your functions\
在函式之間創建時間依賴關係。
- The inevitable fall into a callback pyramid\
不可避免地陷入 callback 金字塔的情况。
- An incompatible mix of synchronous and asynchronous code\
不兼容的同步和非同步程式混合。
### temporal dependency
Temporal coupling, temporal cohesion
我想要做的事必須先等某件事完成,在同步的狀態下只要調動執行順序即可,在非同步的情況變得複雜,因為我們不確定到底何時完成。
```javascript
let students = null;
// 無法得知 getJSON 何時完成,導致該流程會有問題
getJSON('/students', function(studentObjs) {
students = studentObjs;
},
function (errorObj){
console.log(errorObj.message);
}
)
showStudents(students);
```
### Callback
callback 是非同步的一個解決方案之一(用途不只有處理非同步),白話文解釋 callback 在做的事情就是「我先跟妳說我拿到值後要做什麼(就是 Task 的概念),你準備好了幫我執行」,所以前一個範例應該改成
```javascript
getJSON('/students', function(studentObjs) { // 拿到值幫我做這件事
showStudents(studentObjs);
},
function (errorObj){ // 失敗了幫我做這件事
console.log(errorObj.message);
}
)
```
### Callback hell, Callback pyramid
Callback 似乎解決了非同步問題,但衍生出另一個問題,如果依賴鏈過長會有 Callback hell, Callback pyramid 問題。
\
假設我有 a, b, c 三件非同步的事,依賴順序是 a → b → c,那程式大概會長這樣
```javascript
a(params, (data) =>
b(data, (data2)=>
c(data2, (data3) => console.log(data3))))
```

### Nested control flow
上述都是一條鏈的流程,但實際程式更複雜,我們還有控制流(control flow)
```javascript
a(function (dataA){
if (dataA) {
b(dataA, function (dataB){
if (dataB) {
c(dataB, function (dataC){
// nested control flow
})
} else {
// ...
}
})
} else {
// ...
}
})
```
### CPS (continuation-passing style)
為了解決 Nested control flow 的問題,書上使用了一個叫 CPS 的手法,聽起來很潮的名詞,但巢狀的問題永遠都在,這是無法避免的,因為我們要的邏輯就是這樣,只是用另一種寫法把它攤平:
```javascript
function taskC(dataC){
// ...
}
function taskB(dataB){
if (dataB) {
c(dataB, taskC);
} else {
// ...
}
}
function taskA(dataA){
if (dataA) {
b(dataA, taskB);
} else {
// ...
}
}
a(taskA);
```
這樣每個 task 就只會有一層的巢狀,減少閱讀負擔,以及提升程式整潔度
CPS 就像是一直傳入 Callback 接續前一個函式所處理好的資訊,直到沒有事情要處理為止,唯一的詬病是一旦採用 CPS 整個程式撰寫風格會被迫改變。
CPS 更嚴格的定義是將所有回傳改成 callback 形式,並且是尾調用
[Continuation-passing style - Wikipedia](https://en.wikipedia.org/wiki/Continuation-passing_style)
在 express.js 也能看到 CPS 身影
[Express 4.x - API Reference (](https://expressjs.com/en/4x/api.html#app.get.method)[expressjs.com](expressjs.com)[)](https://expressjs.com/en/4x/api.html#app.get.method)
node.js std API 也是 CPS 形式
[Usage and example | Node.js v18.16.1 Documentation (](https://nodejs.org/dist/latest-v18.x/docs/api/synopsis.html)[nodejs.org](nodejs.org)[)](https://nodejs.org/dist/latest-v18.x/docs/api/synopsis.html)
小知識:Monad 的 `flapMap` 某種程度上也化簡了疊層問題,目的是為了讓我專注在邏輯本身,減少重複判斷問題
**Array**
```typescript
// 假設我們要將 [1, 2, 3] 轉成 [1, 1, 2, 2, 3, 3]
const arr = [1, 2 ,3];
// 步驟一
// Array<number> -> Array<Array<number>>
const mapedArr = arr.map((item) => [item, item])
// 步驟二 因為多包一層所以要攤平
// Array<Array<number>> -> Array<number>
const result = mapedArr.flat()
// flatMap 就是 flat 跟 map 的組合
const result2 = arr.flatMap((item) => [item, item])
```
**Option (引用 fp-ts)**
```typescript
// 我要知道一個未知的值是否是偶數,是就給我 Some<number> 不是就 None
const getNumber = (value: unknown): Option<number> =>
typeof value === 'number' ? Option.some(value) : Option.none();
const evenNumber = (value: number): Option<number> =>
value % 2 === 0 ? Option.some(value) : Option.none()
const num = 10;
const optionValue = getNumber(num)
// 步驟一
// Option<number> -> Option<Option<number>>
const optionEvenNumWrap = Option.map(evenNumber)(optionValue)
// 步驟二
// Option<Option<number>> -> Option<number>
const optionEvenNum = Option.flatten(optionEvenNumWrap)
// flatMap 就是 flat 跟 map 的組合
const optionEvenNum = Option.flatMap(evenNumber)(optionValue)
```
---
## 8\.2 First-class asynchronous behavior with promises
透過前面的優化,雖然確實解決可讀性問題,但要達到 FP 理想的樣子還有一段距離,還有幾件事情要改善:
- Using **composition** and **point-free** programming
- Flattening the nested structure into a more linear flow\
將巢狀結構展開為更線性的流程
- Abstracting the notion of temporal coupling so that you don’t need to be concerned with it\
抽象化時間耦合的概念,使您不需要擔心它
- Consolidating error handling to a single function rather than multiple error callbacks so that it’s not in the way of the code\
將錯誤處理整合到一個函數中,而不是多個錯誤 callback 函數中,以避免妨礙程式進行。
#### point-free ?
省略參數的函式,只專注動作的組合而不是我的參數是什麼
假設我要一個兩數平方相加的函式
$$
f(x,y) = x^2 + y^2
$$
```typescript
const square = (x: number) => x * x;
const add = (x: number, y: number) => x + y;
// 一般寫法
const sumOfSquare = (x: number, y: number) => {
const a = square(x)
const b = square(y)
return add(a, b)
}
// point-free
const sumOfSquare = (x: number, y: number) => add(square(x), square(y))
// 利用 Ramda
import * as R from 'ramda'
const sumOfSquare = R.useWith(add, [square, square])
```
```mermaid
flowchart TD
x --> square1[square]
y --> square2[square]
subgraph ide1 [useWith]
square1 --> add
square2 --> add
end
add --> result
```
可以發現 point-free 風格更接近數學式,只專注運算式的展示,而不是運算過程的**步驟**。
### Promise
Promise 解決了上述幾個問題
#### 將巢狀結構展開為更線性的流程
```typescript
Promise.resolve(1)
.then((x) => Promise.resolve(2 + x)) //會自動解開 Promise 傳遞給下一個 then
.then((x) => Promise.resolve(3 + x)) // 6
```
#### 抽象化時間耦合的概念,使您不需要擔心它
Promise 其實是一個簡易的**狀態機**,透過大量的撰寫非同步程式的經驗後,你可以從中看出我們主要在處理幾個問題
- 開始了不知道何時結束
- 結束了不知道成功還失敗
- 成功跟失敗的處理流程不知道安插在哪裡
```mermaid
stateDiagram-v2
[*] --> Promise
state Promise {
[*] --> pending
pending --> rejected: reject
pending --> fullfilled: fullfill
}
```

而 Promise 解決了上述問題,你只需要跟他說,什麼時候開始、成功的時候做什麼、失敗的時候做什麼,剩下的 Promise 會幫你在對的時機做完事情
#### 將錯誤處理整合到一個函數中,而不是多個錯誤 callback 函數中,以避免妨礙程式進行
```typescript
// 中途出錯會直接到 catch,而不是讓後續流程自己處理
Promise.resolve(1)
.then((x) => Promise.resolve(x + 1))
.then((x) => Promise.reject('error'))
.then((x) => Promise.resolve(x + 1))
.catch((err) => console.log(err))
```
書上小錯誤
Promise 嚴格來說並不是 Monad,他只是長得很像而已
而 fp-ts 的 [`Task`](https://gcanti.github.io/fp-ts/modules/Task.ts.html#task-interface) 雖然只是做了一層包裝但他卻是個 Monad
```typescript
interface Task<T> {
(): Promise<T>
}
```
延伸閱讀
[No, Promise is not a monad (](https://buzzdecafe.github.io/2018/04/10/no-promises-are-not-monads)[buzzdecafe.github.io](buzzdecafe.github.io)[)](https://buzzdecafe.github.io/2018/04/10/no-promises-are-not-monads)
---
## 8\.3 Lazy data generation
因為 Lazy 在 FP 有他的特殊用意,所以我將 Lazy 跟 generator function 分開來說
### Lazy Evolution
惰性求值特性是「只有在所有參數備齊時才真正執行」,所以我可以不用一次給完所有參數
Ramda 所有函式都可以做到惰性求值
```javascript
import * as R from 'ramda'
const double = (n) => n * 2
const doubleArr = R.map(double)
doubleArr([1, 2, 3, 4]) // [2, 4, 6, 8]
```
Lazy Evolution 在 Haskell 是預設行為
```haskell
double :: Int -> Int
double x = x * 2
doubleArr :: [Int] -> [Int]
doubleArr = map double
```
老師都叫我們套公式,我們都怎麼套公式
$$
f = x^2 + 2xy + y^2
$$
我們通常都會把 $x$ 跟 $y$ 湊齊了才放進公式裡計算,我們很少拿一部分的值先算一部分出來(在計算過程中會這樣做),因為這樣我們還不會得到結果,不如我們就等到所有參數到齊了在做比較省事。
$$
x=5, f = 25 + 10y + y^2
$$
#### data first、data last?
你可能會注意到許多 FP function 會把參數傳遞順序對調,某種成度上是為了讓 function 本身不會直接被 data 綁死,並且因為 curry 的關係能使 function 只傳一部分並產生另一個 function,進而提高重用性。
```javascript
import * as _ from 'lodash';
import * as R from 'ramda'
_.map([1, 2, 3], (x) => x * 2) // data first
R.map((x) => x * 2, [1, 2, 3]) // data latest
```
```javascript
import * as R from 'ramda';
import * as _ from 'lodash'
const sum = (arr) => _.reduce(arr, (acc, num) => acc + num, 0)
const sum = (arr) => arr.reduce((acc, num) => acc + num, 0)
const product = (arr) => arr.reduce((acc, num) => acc * num, 1)
const sum = R.reduce(R.add, 0)
const product = R.reduce(R.multiply, 1)
```
### 用於耗時任務的 Lazy Type
在 fp-ts 最常出現的三種 lazy type definition
```typescript
interface LazyArg<A> {
(): A
}
interface IO<A> {
(): A
}
interface Task<A> {
(): Promise<A>
}
```
你會發現三種都刻意的在值上面多掛一層看似沒有意義的空 function 上去
我們可以用兩種說法來解釋 Lazy 的目的「盡可能拖延這一切」跟「非必要時刻我不計算」
#### 非必要時刻我不計算
```typescript
const getBooleanA => (x) => () => //... 可能是耗時的運算
const getBooleanB => (x) => () => //... 可能是耗時的運算
function fn(booleanA, booleanB){
// 假如 booleanA 結果是 false,那 booleanB 就不必執行了
if(booleanA() && booleanB()){
}
}
fn(getBooleanA(1), getBooleanB(2))
```
#### 盡可能拖延這一切
```typescript
const double = (n: number): number => n * 2;
const mainIO = pipe(
IO.of(1), // 可以是任何同步操作
IO.map(double),
IO.map(double),
IO.map(double),
IO.map(double),
IO.map(double),
IO.map(double),
IO.map(double),
IO.map(double),
IO.map(double),
IO.map(double)
);
const mainTask = pipe(
Task.of(1), // 可以是任何非同步操作
Task.map(double),
Task.map(double),
Task.map(double),
Task.map(double),
Task.map(double),
Task.map(double),
Task.map(double),
Task.map(double),
Task.map(double),
Task.map(double)
);
```
以上兩者在程式開始時接未執行,你必須明確表明 `mainIO()` 或 `mainTask()` 這一切才會動作,彷彿我們在推遲這一切的發生,所以我們可以這樣理解,「functional programming 會刻意的延遲 side effect 動作的發生,只有在確切需要當下執行時才**顯式地**執行它。」,這樣可以促使開發者意識到並且避免無意義的資源浪費。
#### 隱式(implicit)、顯式(explicit)?
> #### 隱式
>
> 指的是某些操作或行為是由編譯器或運行時環境自動完成的,而不需要你明確地指定
> #### 顯式
>
> 指的是你明確地指定了某些操作或行為。
```javascript
const num = 10;
const num2 = num; // 隱式複製
import * as R from 'ramda'
const obj = {
// ...
}
const obj2 = R.clone(obj) // 顯式複製
```
Rust
```rust
let num: i32 = 20;
let num2: i32 = num; // 隱式複製
let arr: Vec<i32> = vec![1, 2, 3];
let arr2: Vec<i32> = arr.clone(); // 顯式複製
```
所以我們使用 FP 手法寫程式某種程度上像是在堆疊一個 app 的公式
什麼時候會執行?就是參數齊全的時候。
延伸閱讀
[How to deal with dirty side effects in your pure functional JavaScript (](https://jrsinclair.com/articles/2018/how-to-deal-with-dirty-side-effects-in-your-pure-functional-javascript/)[jrsinclair.com](jrsinclair.com)[)](https://jrsinclair.com/articles/2018/how-to-deal-with-dirty-side-effects-in-your-pure-functional-javascript/)
### Generator function
> Generators are functions that can be **exited** and **later re-entered**. Their context (variable bindings) will be saved across **re-entrances.**
[MDN definition](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function\*#description)
generator function 起手式
```typescript
function* fn(){
yield 10
yield 20
}
const gen = fn();
gen.next(); // { value: 10, done: false }
gen.next(); // { value: 20, done: false }
gen.next(); // { value: undefined, done: true }
```
還可以疊層
```typescript
function* fn(){
yield 10
yield 20
}
function* fn2(){
yield 1
yield fn()
yield 2
}
const gen = fn2();
gen.next() // { value: 1, done: false }
gen.next() // { value: 10, done: false }
gen.next() // { value: 20, done: false }
gen.next() // { value: 2, done: false }
gen.next() // { value: undefined, done: true }
```
平常就存在於我們看不到的地方
#### Symbol.iterator
擁有該標記型別都是由 generator function 製作的,我們常用的 [spread operation](./https!developer.mozilla.org!en-US!docs!Web!JavaScript!Reference!Operators!Spread_syntax#description%20ed9d7b93-f82d-4de5-8ecc-3746a3101204.md "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#description"), [`for…of`](./https!developer.mozilla.org!en-US!docs!Web!JavaScript!Reference!Statements!for...of%2070bf73fa-2e7c-4f0c-bd12-14b10f685712.md "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of") 就是在 call Symbol.iterator 函數。
#### 誰擁有該標記?
- [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@iterator)
- [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/@@iterator)
- [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/@@iterator)
- [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/@@iterator)
- [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/@@iterator)
#### 我們可以自己加嗎?
可以
```typescript
// 獲得一個範圍內的偶數陣列
const evenNumberList = {
start: 0,
end: 20,
*[Symbol.iterator]() {
const start = this.start % 2 === 0 ? this.start : this.start + 1;
for (let i = start; i <= this.end; i += 2) {
yield i;
}
},
};
[...evenNumberList] // [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
```
不過其實這樣寫就可以了
```typescript
import * as R from 'ramda';
const evenNumberList = R.range(0, 20).filter((i) => i % 2 === 0)
```
除非今天你有一個自己的特殊資料結構想要可以被迭代,不然平常幾乎用不到
```javascript
class Node {
constructor(val) {
this.val = val;
this.left = null;
this.right = null;
}
setLeft(node = null) {
this.left = node;
}
setRight(node = null) {
this.right = node;
}
// inorder taversal 中序遍歷
*[Symbol.iterator]() {
if (this.left) {
yield* this.left;
}
yield this.val;
if (this.right) {
yield* this.right;
}
}
}
const node1 = new Node('1');
const node2 = new Node('2');
const node3 = new Node('3');
node2.setLeft(node1);
node2.setRight(node3);
[...node2] // ['1', '2', '3']
```
```mermaid
flowchart TD
2 --- 1
2 --- 3
```
雖然用不太到,但我們還是可以了解它通常在處理什麼
- 高效率處理大型資料
- 處理無限序列
- 在兩個函數之間傳遞訊息
`async await` 語法糖是由 generator function 演變而來
[function\* - JavaScript | MDN (](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function\*#description)[mozilla.org](mozilla.org)[)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function\*#description)
> Generators in JavaScript — especially when combined with Promises — are a very powerful tool for asynchronous programming as they mitigate — if not entirely eliminate -- the problems with callbacks, such as [++Callback Hell++](http://callbackhell.com/) and [++Inversion of Control++](https://frontendmasters.com/courses/rethinking-async-js/callback-problems-inversion-of-control/). However, an even simpler solution to these problems can be achieved with [++async functions++](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function).
```javascript
const asyncDo =
(runTasks) =>
(...args) => {
const generator = runTasks(...args);
const resolve = (next) => {
if (next.done) {
return Promise.resolve(next.value);
}
return next.value.then((data) => resolve(generator.next(data)));
};
return resolve(generator.next());
};
const asyncFn = asyncDo(function* () {
const v1 = yield Promise.resolve(123);
const v2 = yield Promise.resolve(123 + v1);
return v2;
});
asyncFn().then((res) => console.log(res)); // 246
```
延伸思考
為什麼需要 `async await`?Promise 本身有什麼痛點?
延伸閱讀
<https://jrsinclair.com/articles/2022/why-would-anyone-need-javascript-generator-functions/>
iterator 不只存在於 Javascript,因為他算是一種共同設計模式,稱作 iterator pattern,實作方法可能有所不同,但 API 設計都相當接近。
還有哪裡可以見到 iterator 的身影
- [Rust iterator](https://rust-lang.tw/book-tw/ch13-02-iterators.html?highlight=iterator#iterator-%E7%89%B9%E5%BE%B5%E8%88%87-next-%E6%96%B9%E6%B3%95)
- [Scala iterator](https://docs.scala-lang.org/overviews/collections/iterators.html)
- [Python iterator](https://wiki.python.org/moin/Iterator)
- [C++ iterator](https://en.cppreference.com/w/cpp/iterator)
- [Java Iterator](https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html)
---
## 8\.4 Functional and reactive programming with RxJS
[reactive programming](https://reactivex.io/intro.html)
### RxJS 核心思想
> RxJS is a library for composing **asynchronous** and **event-based** programs by using observable sequences. It provides one core type, the [Observable](https://rxjs.dev/guide/observable), satellite types (Observer, Schedulers, Subjects) and operators inspired by `Array` methods ([`map`](https://rxjs.dev/api/index/function/map), [`filter`](https://rxjs.dev/api/index/function/filter), [`reduce`](https://rxjs.dev/api/index/function/reduce), [`every`](https://rxjs.dev/api/index/function/every), etc) to allow handling asynchronous events as collections.
簡潔一句話就是「觀察資料的變化並做出一系列反應 (react) 」
- 這個概念是否似曾相似 😏?
沒錯,就是 React、Vue 等前端框架在做的事,只是前端框架專注在反應 UI,而 Rx 則不受限,這也是為什麼當我們把 React、Vue 與 Rxjs 結合使用時會有不協調感,兩者做的事情重複了
Rx 由三個核心構成
- Observable
- Observer
- Operators
### Observable
建立一個可被觀察的對象,該對象的每次變化都會通知所有觀察者
```javascript
import { Observable } from 'rxjs';
const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
});
```
利用 Observable 這個基礎模型,依不同情境包裝而成的函式
```javascript
import { fromEvent, of } from 'rxjs';
// 監聽 document 點擊
const clicks = fromEvent(document, 'click');
of(10, 20, 30)
.subscribe({
next: value => console.log('next:', value),
error: err => console.log('error:', err),
complete: () => console.log('the end'),
});
// Outputs
// next: 10
// next: 20
// next: 30
// the end
```
<https://github.com/ReactiveX/rxjs/blob/c2cf0c4a9dfe5904d65e998b4831909c082f78f7/src/internal/observable/fromEvent.ts#LL239C25-L239C25>
\
嘗試自己利用 Observable 實作一個 `of`
```javascript
import { Observable } from 'rxjs';
const of = (...args) => {
return new Observable((subscriber) => {
for (const nextValue of args) {
subscriber.next(nextValue);
}
subscriber.complete();
});
};
```
### Observer
建立一個觀察者,分別撰寫在 **資料變化時**、**錯誤發生**以及**結束時**所要做的事
```javascript
const observer = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
```
訂閱 Observable
```javascript
import { Observable } from 'rxjs';
const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
});
const observer = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
}
observable.subscribe(observer);
```
### Operators
先將 Observable 的資訊先進行一段處理再分發給 Observer
```javascript
import { of, map, take } from 'rxjs';
of(1, 2, 3)
.pipe(map((x) => x * x), take(2))
.subscribe((v) => console.log(`value: ${v}`));
```
### 自己實作簡易模型
```javascript
class Observable {
constructor(fn = () => {}) {
this._fn = fn;
this._operators = [];
}
pipe(...fns) {
this._operators = fns;
return this;
}
subscribe(observer) {
let completed = false;
let isError = false;
const operators = this._operators;
const newObserver =
typeof observer === 'function'
? {
next: (value) => {
if (completed || isError) return;
// operator flow
const newValue = operators.reduce((acc, fn) => fn(acc), value);
observer(newValue);
},
complete: () => {
completed = true;
},
err: (error) => {
isError = true;
},
}
: {
next: (value) => {
if (completed || isError) return;
// operator flow
const newValue = operators.reduce((acc, fn) => fn(acc), value);
observer.next(newValue);
},
complete: () => {
completed = true;
observer.complete();
},
err: (error) => {
isError = true;
observer.error(error);
},
};
this._fn(newObserver);
}
}
```
```javascript
const ob = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(1);
setTimeout(() => {
subscriber.next(3);
}, 1000);
subscriber.next(2);
});
ob.subscribe({
next(v) {
console.log(v);
},
complete(v) {
console.log(v);
},
err(v) {
console.log(v);
},
});
// logs
// 1
// 1
// 2
// 3
```
## 總結
雖然這本書有些內容跟不上時代也有許多錯誤,不過我們可以透過對比新舊函式庫與糾錯的過程來鞏固我們的知識,透過這本書我們已經對 FP 有基礎的認知,再來就是實戰練習,以下是我認為比較好的練習過程
- 練習寫 pure function
- 開始分辨程式純與不純的部分
- 嘗試利用 pipe, flow, compose 組合你的 pure function
- 拆解一個複雜函式並將其組合
進階
- 將純邏輯操作與 side effect 隔離個別處理
- 嘗試使用 Either, Option 處理錯誤與空值
### 現代 JS/TS 相關 Functional Programming library
- fp-ts (把 FP 概念實現最完整的 lib)
- ramda
- lodash/fp
- Effect-ts (這整個系統被拆成很多個模組) [Welcome to Effect](https://www.effect.website/)
- [Effect/data](https://github.com/Effect-TS/data) (各種資料結構的實作)
- [Effect/io](https://github.com/Effect-TS/io) (基於 Fiber 模型概念處理 side effect)
- [Effect/scheme](https://github.com/Effect-TS/schema) (用於資料格式驗證,與 zod, yup 用途相同)
- [Effect/match](https://github.com/Effect-TS/match) (用 TS 實現的 pattern matching)
- [ts-belt](https://mobily.github.io/ts-belt/)