iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0

今天要介紹一個功能沒有 Monad 這麼強,但比較泛用的抽象介面,Applicative Functors

Day 26 - Monads (1) 中,我們有寫過 traversesequence 方法,前者能使用一次迭代就把 List[A] 轉成 Monad with List[B],而後者是把很多個 Monad 轉成一個 Monad,

def sequence[A](fas: List[F[A]]): F[List[A]] =
  traverse(fas)(fs => fs)

def traverse[A, B](as: List[A])(f: A => F[B]): F[List[B]] =
  as.foldRight(unit(List[B]()))((a, acc) => f(a).map2(acc)(_ :: _))

traverse 的實現用到了 Monad 的 unitmap2,而 Monad 的 map2 則是使用 flatMap 來實現,

def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] =
  fa.flatMap(a => fb.map(b => f(a, b)))

其實有挺多的組合器方法只會用到 unit 和 map2 來實現,且在某些資料結構下 map2 可以不透過 flatMap 來實現,

Monad 的核心方法是 flatMap 和 unit,map2 是眾多延伸出的組合器方法之一,既然如此,我們也可以把 map2unit 做為介面的核心方法來用,此介面就被稱為 Applicative Functors

Applicative Functor 介面

Applicative 一樣繼承至 Functor,代表著所有的 Applicative 都是 Functor,然後用 map2 實現 Functor 的 map 方法,除了把上面講到的 traverse 和 sequence 搬過來以外,其它只用到 map2 和 unit 的組合器方法可以一併搬過來。

trait Applicative[F[_]] extends Functor[F]:

  def unit[A](a: => A): F[A]

  extension[A] (fa: F[A])
    def map2[B, C](fb: F[B])(f: (A, B) => C): F[C]

    def map[B](f: A => B): F[B] =
      fa.map2(unit(()))((a, _) => f(a))

  def traverse[A, B](as: List[A])(f: A => F[B]): F[List[B]] =
    as.foldRight(unit(List[B]()))((a, acc) => f(a).map2(acc)(_ :: _))

  def sequence[A](fas: List[F[A]]): F[List[A]] =
    traverse(fas)(fa => fa)

Exercise D28-1

實作 replicateM 和 product 方法吧。

def replicateM[A](n: Int, fa: F[A]): F[List[A]]

extension[A] (fa: F[A])
	def product[B](fb: F[B]): F[(A, B)]

另一種 Applicative Functor 核心方法

另外一種核心方法組合是 unitapply,然後 map2 就可以用 apply 來實現,

trait Applicative[F[_]] extends Functor[F]:

  def unit[A](a: => A): F[A]

  def apply[A, B](fab: F[A => B])(fa: F[A]): F[B]

  extension[A] (fa: F[A])
    def map2[B, C](fb: F[B])(f: (A, B) => C): F[C] =
      apply(apply(unit(f.curried))(fa))(fb)

apply 跟 map2 之間可以相互實現,我們就延用核心方法為 map2unit 的版本,然後用 map2 實現 apply。

def apply[A, B](fab: F[A => B])(fa: F[A]): F[B] =
  fab.map2(fa)(_(_))

Curry (鞣製),就是將接受多個參數的函式,改為接受單一參數的方法,例如 f: (A, B) => C,當調用 f.curried 後會得到 A => B => C 的方法 (function literal)。

scala> val f: (Int, Double) => Double = _ * _

scala> f(5, 2.0)
val res1: Double = 10.0

scala> val curriedF = f.curried

scala> curriedF(5)(2.0)
val res2: Double = 10.0

scala> val partialF = curriedF(5)
val partialF: Double => Double = scala.Function2$$Lambda$1492/0x000000084077f840@e60c5a

scala> partialF(2.0)
val res3: Double = 10.0

Exercise D28-2

有了 apply 方法我們可依樣晝葫蘆輕易實現 map3, map4 ,試著用 unit、apply 和 curried 來實現 map3 吧。

  extension[A] (fa: F[A])
    def map3[B, C, D](fb: F[B], fc: F[C])(f: (A, B, C) => D): F[D]

明天聊聊 Applicative Functors 跟 Monad 的差異性。


Day 28 - Exercise answer


上一篇
Monads (2)
下一篇
Applicative Functors (2)
系列文
用 Scala 3 寫的 Functional Programming 會長什麼樣子?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言