在先前我們提到了 compose,並且將許多單一功能的純函式,透過 compose 成一個更強大的計算函式。
但問題就來了,當兩個函式 f
跟 g
Type Signature
f:: a -> b
g:: c -> d
而進行 compose 或 pipe 時,其先決條件是 b
與 c
必須為相同型別,也就是說 f
的輸出的型別要等於 g
的輸入型別才能進行 compose.
但在現實的開發環境中有很多需要處理的情境,例如錯誤處理(error handling), 副作用(side effect) 又或是 非同步計算(asynchronous computing),我們將其稱為 Effects, 而這些 Effect 都會包覆在類似 Container 的地方,那我們要如何將 Effect (也就是 Container) 裡面的值與純函式進行 compose 呢?
舉例來說,
safe
這個函式是會傳一個 Maybe Monad 出來,但要怎麼將包在 Maybe 裡面的 a
(Maybe a
) 跟 toUpper
這個純函式進行 compose?
這就是我們今天要來探討的主題,而 Functor 就是處理這件事的 algebraic structure.
safe :: (a -> Boolean) -> a -> Maybe a
toUpper :: String -> String
Maybe Monad 可以先想成是專門處理該運算可能會沒有值的情境,這在之後的文章回有更詳細的說明
首先來介紹我們第一個 Container, Identity 其功能很簡單,就是將一個值包覆在 Identity 內
Construction
Identity :: a -> Identity a
Implementation
首先我們就先將 Identity 的基底實作出來
const Identity = val => ({
val,
inspect: () => `Identity(${val})`
})
而我們通常會使用 of 將值進行包覆在相對應的 Container 中,實作也很簡單
可能有人會疑惑,為什麼要用 of 呢? 這我們會在之後的章節提到,但現在就先記住這是一個重要的概念就好~
Identity.of = x => Identity(x);
假設現在我們要將 1 值包覆在 Identity 內,則為
Identity.of(1) // Identity(1)
示意圖
就如同文章開頭所講的,而這裡就用實例來講解
想像一下目前有一值為 1
,如果要將它引用到 add2
這個函式,只需要 add2(1)
就會得出 3
const add2 = x => x + 2;
add2(1) // 3
但包覆在 Identity 的值 ,則沒辦法引用到一般的函式中進行運算
add2(Identity.of(1)) // [object Object]2
這就是為什麼需要 functor,因為 functor 知道如何將包覆的值進行運算,也就是 compose Identity 1
這個 Effect 與 add2
這個純函式
Identity.of(1).map(add2) // Identity(3)
理解完為什麼需樣 functor 後,其實就知道 Identity 是一個 functor,讓我們來看看其定義
map
methodidentity
& composition
)Type Signature
map :: Functor f => f a ~> (a -> b) -> f b
Law
對於任何 functor u
都要符合下列兩種特性,
如果太抽象可以這樣思考 Identity 就是一種 functor,functor u 就是 Identity 1
u.map(x => x) === u
u.map(f).map(g) === u.map(x => g(f(x)))
實作 Identity 的 map
method
const Identity = val => ({
val,
map: f => {
const result = f(val); // 將函式套用在val上
return Identity(result);// 將結果包覆回 Identity
}
})
簡寫版
const Identity = val => ({
val,
map: f => Identity(f(val)),
inspect: () => `Identity(${val})`
})
那我們就用馬上來驗證一下,Identity 的 map
有沒有符合其上述特性
Identity.of(1).map(x => x) === Identity.of(1)
Identity.of(1).map(add2).map(add3) === Identity.of(1).map(x => add3(add2(x))))
大功告成!!!
未來在介紹各種不同的 ADT 的時候,可以看到幾乎每個 ADT 都是 Functor,而這章的目的就是讓大家知道為什麼會需要 Functor 以及用 Identity 這個 ADT 介紹實作方式。
感謝大家閱讀!!!
NEXT: Contravariant & Applicative