Functor 是學習 FP 第一個會遇到的 Algebraic Data Type,Maybe、Either 與 Future 都是基於 Functor 後加以展開。
Version
Fantasy Land 5.0.0
Functor
學習 Ramda 時,第一個要學的就是 map
,但其 type signature 卻給你一個大大的 Functor,為什麼不是 Array 呢 ?
Functor 是能使用 map
的 type,號稱 mappable,白話就是能使用 map
的 type。
fantasy-land/map :: Functor f => f a ~> (a -> b) -> f b
Functor 必須包含map
,可將 Functor 與一般 function 綁定後並回傳新 Functor
- 根據 Fantasy Land 定義,支援
map
的 Object 就是 Functor,這也是為什麼 Sanctuary 對於map
的 type signature 會出現Functor
原因 map
使我們能將 Functor 與一般 function 綁定並回傳新 Functor
Array
import { map, inc } from 'ramda'
let data = [1, 2, 3]
map (inc) (data) // ?
最典型的 Functor 就是 Array,我們可使用 map
將 Array 與 function 綁定改變 Array。
map
Functor f => (a -> b) -> f a -> f b
將 Functor 與 一般 function 綁定改變 Functor
(a -> b)
:一般 function
f a
:data 為 Functor
f b
:回傳新 Functor
Object
import { map, inc } from 'ramda'
let data = { x: 1, y: 2 }
map (inc) (data) // ?
Object 也是 Functor,也可使用 map
將 Object 與 function 綁定改變 Object。
Ramda 的文件也明確指出 map
適用於 Object。
String
既然 Array 是 Functor,而 String 本質是 Array Char,所以 String 也是 Functor 嗎 ?
注意 map
的定義,其 callback 為 a -> b
,結果為 f b
。
當然 b
可以是 a
,因此就是:
Functor f => (a -> a) -> f a -> f a
這就是 String,但若 callback 為 a -> b
時,String 就不適用,因此 String 不是 Functor。
Ramda 很多 function 都是 Array 與 String 共用,此時 Signature 會特別加上 String
,若 String 是 Functor,其 signature 大可寫成:
Functor f => f a -> f a -> f a
但 Ramda 文件並沒有這樣寫,可見 String 並不是 Functor。
不是說能使用 map
就是 Functor 嗎 ?
將 123
套用 map
也能得出 [2, 3, 4]
啊!
但別忘了 map
定義是 f a
變成 f b
,也就是其 f
是相同的,但現在卻是 String 變 Array,已經是不同 f
。
這是因為 ECMAScript 的 String 可視為 Array Char,因此 123
被視為 ['1', '2', '3']
操作,如此 f
仍是相同的。
這只能證明 Array 是 Functor,但 String 不是 Functor。
Function
import { map, inc, negate } from 'ramda'
import { compose } from 'sanctuary'
let data = 2
map (negate) (inc) (data) // ?
compose (negate) (inc) (data) // ?
inc
與 negate
都是 function,若將兩個 function 都傳入 map
會如何 ?
可發現將兩個 funciton 做 map
等效於 compose
。
什麼 ? 所以 function 也是 Functor ?
Functor 的定義是有 map
的 Object,也就是可以 mapping 的東西就算是 Functor,pure function 本身就是一種 mapping,因此 function 也是 Functor。
因此將 function 透過 map
傳入另一個 funtion 時,相當於兩個 function 去 compose
。
Ramda 的文件也明確指出 map
適用於 function,因此 function 就是 Functor。
Fantasy Land
可發現有大量 ADT 都必須支援 Functor 這個 typeclass,也就是這些 ADT 都必須實現 map
,如 Maybe、Either 與 Future 也都提供 map
可用。
Functor.test
Primitive
import { Functor } from 'sanctuary-type-classes'
Functor.test (1) // ?
Functor.test ('') // ?
Functor.test (true) // ?
Functor.test ({}) // ?
Functor.test ([]) // ?
Functor.test (() => {}) // ?
Sanctuary 提供了 Functor.test
可判斷 value 是否為 Functor。
- Number、String 與 Boolean 皆非 Functor
- Object、Array 與 Function 皆為 Functor
ADT
import { Just, Right, Pair } from 'sanctuary'
import { resolve } from 'fluture'
import { Functor } from 'sanctuary-type-classes'
Functor.test (Pair (1) (2)) // ?
Functor.test (Just (1)) // ?
Functor.test (Right (1)) // ?
Functor.test (resolve (1)) // ?
- Pair、Maybe、Either 與 Future 皆為 Functor
Conclusion
- 不只 Array 可以使用
map
,Object 與 Function 也可使用map
- 為什麼 String 不可使用
map
? 因為 String 不是 Functor - 若不確定 value 是不是 Functor,可使用
Functor.test
判斷
Reference
Fantasy Land Specification, Functor
Sanctuary, sanctuary-type-classes