# Functor 與 JS/TS ###### tags: `Functional Programming` `note` 用數學角度出發去看 **Functor** 的定義什麼,我們怎麼用 `Typescript` 實現它,以及 `Option`、`List` 幫我們做了什麼。  ## 定義 根據[維基百科](https://en.wikipedia.org/wiki/Functor)描述「Functor 是範疇之間的映射」,而且要視為 Functor 要達成幾項條件: 先假設有 $C$ 與 $D$ 兩個範疇,而 functor $F$ 是 $C$ 與 $D$ 之間的映射 - $C$ 範疇中的每個 object $X$ 都與 $D$ 之中每個 $F(X)$ 相關聯 - 將 $C$ 中的每個 morphism $f: X \to Y$ 關聯到 $D$ 中的 morphism $F(f): F(X) \to F(Y)$ 並且達成以下兩個條件: - 對於 $C$ 中的每個 $X$,$F(id_x)=id_{F(x)}$ - 對於 $C$ 中的 $f:X \to Y$ 和 $g:Y \to Z$ 等所有 morphism ,$F(g\circ f)=F(g)\circ F(f)$  其他關於 Functor 定義的參考連結 - [Functor and Natural Transformation](https://categorytheory.gitlab.io/functor_and_natural_transformation.html) - [What is a Functor? Definition and Examples, Part 1](https://www.math3ma.com/blog/what-is-a-functor-part-1) 每篇文章對 Functor 定義可能有些許不同,但你能找出他們之間的共同點,而那個共通點就是 Functor 的公認定義。 ### 建立一個 Functor 我們需要一個從範疇 $C$ 轉到範疇 $D$ 的 function,而範疇 $C$ 在程式中通常代表了所有原始型別與自定義型別之間的關係,範疇 $D$ 表示另一個空間與範疇 $C$ 互相對應。 先定義一個 Functor ```typescript interface Functor<T>{ value:T } // X -> F(X) function of<T>(value:T): Functor<T> { return { value: value } } ``` `of` 就是將範疇 $C$ 的 object 帶到範疇 $D$ 的一個函式。 ### map / fmap 但是當我們今天在範疇 $C$ 有個 morphism 是 $f:X\to Y$,那範疇 $D$ 也要有相應的 morphism,但總不可能 $C$ 有多少個 morphism,$D$ 也跟著建多少個 morphism,所以我們 $D$ 需要一個 `map` 函式將 $C$ 的 morphism 映射過去。 ```typescript interface Functor<T>{ value:T map<U>:(fn: (v:T) => U): Functor<U>; } // X -> F(X) function of<T>(value:T): Functor<T> { return { value: value map: (fn)=> of(fn(value)) } } ``` 我們來實際運行看看是否有達成 Functor 定義 $X\to F(X)$ ```typescript // X const x: number = 10; // F(X) const fx: Functor<number> = of(x); ``` $f:X\to Y$ -> $F(f:X\to Y)$ ```typescript const f = (x: number): string => x.toString(); const fx: Functor<number> = of(10); const fy: Functor<string> = fx.map(f); ``` $F(g\circ f)=F(g)\circ F(f)$ ```typescript const f = (x) => x * x; // number -> number const g = (y) => y.toString(); // number -> string const fx: Functor<number> = of(10); const fz1: Functor<string> = fx.map((x) => g(f(x))); const fz2: Functor<string> = fx.map(f).map(g); ``` `fz1` 跟 `fz2` 結果是相同的 ### 建完了 Functor 然後呢? 我們做了一個 Functor,但是看不出來有什麼用。確實沒有什麼用,就只是把值帶入 $D$ 範疇裡而已,不過我們可以對 $D$ 動手腳,並且只要我們有達成條件,那就還是 Functor。 --- ## 創造一個「有」跟「沒有」的世界 既然我們要看出 Functor 的實際效果,那我們需要加點魔法,我們在寫程式時常常需要判斷欄位或者值存不存在,確定存在才往下執行。 ```typescript function fn(value: number | undefined): numbner | undefined { if(value !== undefined){ return value * 2 + 50; } return undefined; } ``` 看起來還好,不過當量多起來的時候你會變得很煩躁,每次都要檢查值存不存在,難道我不能只說我要怎麼算,是不是空的無所謂,空的就自動幫我跳過。 ### Option / Maybe **Option** 跟 **Maybe** 就是在解決這個問題,讓我們來實作一個 `Option` ```typescript interface IOption<T> { map: <U>(fn: (value: T) => U) => Option<U>; } class Some<T> implements IOption<T> { value: T; constructor(value: T) { this.value = value; } map<U>(fn: (value: T) => U): Some<U> { return new Some(fn(this.value)) } } class None implements IOption<undefined> { value: undefined; constructor(value?: any){ this.value = undefined; } map<U>(fn: (value: any) => U): None { return this; } } type Option<T> = Some<T> | None; function of<T>(value: T): Option<T>{ if(value === undefined) return new None(); return new Some(value); } ``` 上方只是個範例,實作方法以及 `null` 該不該認定為空值都取決於開發者本身。 那我們把原本的範例修改一下: ```typescript function fn(value: Option<number>): Option<number>{ return value.map((v) => v * 2 + 50); } ``` 而在傳進去之前將一個不確定是不是 `number` 的值用 `of` 轉成 `Option<number>`,一切一如往常,運算式照算並且不需要一直判斷,只有真正要用到這個值時你才要確認他到底存不存在。 所以我們在 `of` 跟 `map` 上做了一些調整,如果以單純 Type 來看,還是達成了 Functor 的條件 - $X\to F(X)$ ```typescript of = T -> Option<T> ``` - $f:X\to Y$ -> $F(f:X\to Y)$ ```typescript const f = (x: number): string => x.toString(); const optionX: Option<number> = of(10); const optionY: Option<string> = optionX.map(f); ``` - $F(g\circ f)=F(g)\circ F(f)$ ```typescript const f = (x) => x * x; // number -> number const g = (y) => y.toString(); // number -> string const optionX: Option<number> = of(10); const optionZ1: Option<string> = optionX.map((x) => g(f(x))); const optionZ2: Option<string> = optionX.map(f).map(g); ``` `optionZ1` 與 `optionZ2` 結果會相同 非常神奇對吧,我們把判斷 **有** 跟 **沒有** 這件事隱藏起來了,而另一個常見的 Functor 都藏匿在我們日常開發中,那就是 **Array** / **List**。 --- ## Array / List,把 for loop 隱藏起來 在日常開發中我們常需要對整個 Array 每個值作一些運算,所以最原始的方法都是用 for loop。 ```typescript function fn(arr: Array<number>): Array<string>{ const newArr = []; for(num of arr){ newArr.push(num.toString()); } return newArr; } ``` 好像也還好,但是當今天你有更多樣化的需求時怎麼辦?可能是時間轉換、取物件裡某個欄位等等,那你可能每次都要重新寫一個 for loop function 處理這件事,非常的麻煩且無意義。 在 JS 中其實已經有 `map` method 讓你用了,那我們來看是否都達成了 Functor 的條件 - $X\to F(X)$ ```typescript const arr: Array<number> = Array.of(1); // 或任何其他創建 Array 的方式 ``` - $f:X\to Y$ -> $F(f:X\to Y)$ ```typescript const f = (x: number): string => x.toString(); const arrX: Array<number> = Array.of(1, 2, 3); const arrY: Array<string> = arrX.map(f); ``` - $F(g\circ f)=F(g)\circ F(f)$ ```typescript const f = (x) => x * x; // number -> number const g = (y) => y.toString(); // number -> string const arrX: Array<number> = of(1, 2, 3); const arrZ1: Array<string> = arrX.map((x) => g(f(x))); const arrZ2: Array<string> = arrX.map(f).map(g); ``` `arrZ1` 與 `arrZ2` 結果會相同 我們把枯燥乏味的 for loop 隱藏起來了! ## 結語 我們從數學上的定義與在 FP 語言中常出現的 Functor type 做了對應,以了解數學與程式之間如何配合,以及這些 Functor 解決了我們日常開發的哪些問題,甚至 `Either` 也可以自己試著了解並實作。
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.