今天要介紹一個功能沒有 Monad 這麼強,但比較泛用的抽象介面,Applicative Functors,
在 Day 26 - Monads (1) 中,我們有寫過 traverse
和 sequence
方法,前者能使用一次迭代就把 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 的 unit
和 map2
,而 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 是眾多延伸出的組合器方法之一,既然如此,我們也可以把 map2
和 unit
做為介面的核心方法來用,此介面就被稱為 Applicative Functors。
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)]
另外一種核心方法組合是 unit
和 apply
,然後 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 之間可以相互實現,我們就延用核心方法為 map2
和 unit
的版本,然後用 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 的差異性。