點燈坊

失くすものさえない今が強くなるチャンスよ

深入淺出 Functor

Sam Xiao's Avatar 2021-07-22

Functor 是學習 FP 第一個會遇到的 Algebraic Data Type,Maybe、Either 與 Future 都是基於 Functor 後加以展開。

Version

Fantasy Land 5.0.0

Functor

overview000

學習 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

overview001

Object

import { map, inc } from 'ramda'

let data = { x: 1, y: 2 }

map (inc) (data) // ?

Object 也是 Functor,也可使用 map 將 Object 與 function 綁定改變 Object。

overview005

Ramda 的文件也明確指出 map 適用於 Object。

overview004

String

overview006

既然 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。

overview007

Ramda 很多 function 都是 Array 與 String 共用,此時 Signature 會特別加上 String,若 String 是 Functor,其 signature 大可寫成:

Functor f => f a -> f a -> f a

但 Ramda 文件並沒有這樣寫,可見 String 並不是 Functor。

overview008

不是說能使用 map 就是 Functor 嗎 ?

123 套用 map 也能得出 [2, 3, 4] 啊!

但別忘了 map 定義是 f a 變成 f b,也就是其 f 是相同的,但現在卻是 String 變 Array,已經是不同 f

overview009

這是因為 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) // ?

incnegate 都是 function,若將兩個 function 都傳入 map 會如何 ?

可發現將兩個 funciton 做 map 等效於 compose

overview010

什麼 ? 所以 function 也是 Functor ?

Functor 的定義是有 map 的 Object,也就是可以 mapping 的東西就算是 Functor,pure function 本身就是一種 mapping,因此 function 也是 Functor。

因此將 function 透過 map 傳入另一個 funtion 時,相當於兩個 function 去 compose

overview011

Ramda 的文件也明確指出 map 適用於 function,因此 function 就是 Functor。

Fantasy Land

overview002

可發現有大量 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

overview012

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

overview013

Conclusion

  • 不只 Array 可以使用 map,Object 與 Function 也可使用 map
  • 為什麼 String 不可使用 map ? 因為 String 不是 Functor
  • 若不確定 value 是不是 Functor,可使用 Functor.test 判斷

Reference

Fantasy Land Specification, Functor
Sanctuary, sanctuary-type-classes