Functors

讓我們繼續從之前的程式中抽象出更高層的東西吧!今天要講的介面是 Functor

Functors – map 的抽象介面

Functional programming 下每個主題的 library 都有 map 這個 function,讓你能透過一個 function 將型態 A 轉成 型態 B,

Monoids 的 Foldable 結構一樣,map 動作其實也不管你操作的是什麼資料型態,所以我們可以把這個動作抽象成 Functor 這個介面,

Functor 也是來自於數學,它是 Category Theory 中的映射 (mapping) 。

trait Functor[F[_]]:
  extension[A] (fa: F[A])
    def map[B](f: A => B): F[B]

Functor[F[_]] 型態建構子的相關說明請參考 能自由組合的解析器 Library (1)

extension 的說明請參考 純粹的 functional 狀態 (2)

也跟 Monoids 中的 Foldable 一樣,我們用 F[_] 來表示型態建構子,然後也用 List 來實作看看吧。

object Functor:
  given listFunctor: Functor[List] with
    extension[A] (as: List[A])
      def map[B](f: A => B): List[B] = as.map(f)

有了 Functor,其實我們可以從中延伸很多操作,這裡要介紹其中之二,distribute 和 codistribute;

如果我們有 F[(A, B)],F 是個 Functor,我們可以 distribute (分散) 這個 Functor 成 (F[A], F[B])

  extension[A, B] (fab: F[(A, B)])
    def distribute: (F[A], F[B]) =
      (fab.map(_(0)), fab.map(_(1)))

舉個具體一點的例子就是 List[(A, B)],如果我們 distribute 它,我們會得到 2 個相同長度的 List[A]List[B],這個操作在 List 中被稱為 unzip,所以我們就是用 distribute function 來一般化 unzip,讓所有 Functor 都能做到相同的事情;

在來是跟 distribute 做的事有點相反,稱為 codistribute,

  extension[A, B] (e: Either[F[A], F[B]])
    def codistribute: F[Either[A, B]] =
      e match
        case Left(fa) => fa.map(Left(_))
        case Right(fb) => fb.map(Right(_))

最後完整的 Functor 程式如下:

trait Functor[F[_]]:
  extension[A] (fa: F[A])
    def map[B](f: A => B): F[B]

  extension[A, B] (fab: F[(A, B)])
    def distribute: (F[A], F[B]) =
      (fab.map(_(0)), fab.map(_(1)))

  extension[A, B] (e: Either[F[A], F[B]])
    def codistribute: F[Either[A, B]] =
      e match
        case Left(fa) => fa.map(Left(_))
        case Right(fb) => fb.map(Right(_))

Functor 定律

當我們在設計像 Functor 這種抽象介面時,我們不只要考慮它該有什麼 function,還要想這個介面的定律是什麼,讓所有實作都能遵守,除此之外還有 2 個重要的點:

  1. 定律能幫助介面在形成語意層時,其代數性質在每個獨立實例下是合理的,舉例來說 Monoid[A]Monoid[B] 的結果 Monoid[(A, B)],因為 Monoid 定律的關係,我們可以直接知道 Monoid[(A, B)] 也是具有結合律的,我們不需要了解 A 和 B 來得出這個結論。
  2. 我們通常依賴定律去撰寫從抽象介面中派生出去的組合器,我們之後會在看到這個的例子。

Functor 的定律跟 Purely Function 的平行化 (2) 中的 Par.map 一樣,

map(x)(a => a) == x

實作 map 其實就是隱含著 x 的結構在操作後會相同,且不會產生奇怪的副作用,例如從 List 移除第一個值、把 Some 變成 None 等等。

tshine73
tshine73
文章: 51

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *