可應(yīng)用函子和可遍歷函子

本章將學(xué)習(xí)相關(guān)的抽象淆两,可應(yīng)用函子住闯,雖然沒有Monad強(qiáng)大瓜浸,但是更普遍(因此通用)。在尋找可應(yīng)用函子的過程中比原,也展示了如何發(fā)現(xiàn)這種抽象并利用這種方式發(fā)現(xiàn)另外一種有用的抽象插佛,可遍歷函子。這些抽象需要一些時(shí)間去融匯貫通量窘,但稍加注意的話雇寇,將會(huì)發(fā)現(xiàn)在日常函數(shù)式編程中它是不斷出現(xiàn)的。
泛化單子
至此已經(jīng)看到不同的操作蚌铜,比如sequence和traverse锨侯,它們在不同monad中被實(shí)現(xiàn)了多次。上一章泛化了這個(gè)實(shí)現(xiàn)冬殃,讓他們對任何monad F都有效:

  def sequence[A](li: List[F[A]]): F[List[A]] = 
    traverse(li){fa => fa}

  def traverse[A, B](la: List[A])(f: A => F[B]): F[List[B]] =
    la.foldLeft(unit(List[B]())){(b, a) => map2(f(a), b)(_ :: _)}

可能沒有注意到很多monad組合是可以使用unit和map2來定義的囚痴。組合traverse是一個(gè)例子,他沒有直接調(diào)用flatMap审葬,以至于我們考慮map2可以作為原語深滚。采用unit和map2作為原語,我們將獲得另外一個(gè)抽象涣觉,這個(gè)抽象被稱為可應(yīng)用函子成箫,雖然沒有monad強(qiáng)大,但也會(huì)帶來一些額外的功能旨枯。
Applicative trait
Applicative函子可以被捕捉為一個(gè)接口蹬昌,Applicative中原語包含map2和unit。

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

  def unit[A](a: => A): F[A]
  
  def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C]
  
  def map[A, B](fa: F[A])(f: A => B): F[B] = 
    map2(fa, unit(())){(a, _) => f(a)}
  
  def traverse[A, B](li: List[A])(f: A => F[B]): F[List[B]] =
    li.foldLeft(unit(List[B]())){(b, a) => map2(f(a), b){_ :: _}}
}

練習(xí) 12.1
盡可能多地將monad組合子移到Applicative組合子攀隔,只使用map2和unit函數(shù)皂贩,或者根據(jù)這兩個(gè)實(shí)現(xiàn)的其它方法。

  def sequence[A](lma: List[F[A]]): F[List[A]] =
    traverse(lma){a => a}

  def replicateM[A](n: Int, fa: F[A]): F[List[A]] =
    sequence(List.fill(n)(fa))

  def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
    map2(fa, fb)((_, _))

練習(xí) 12.2
可應(yīng)用(applicative)這個(gè)名詞源于這樣一個(gè)事實(shí)昆汹,它使用另外一套原語unit和apply函數(shù)來表達(dá)Applicative接口明刷,而非unit和Map2函數(shù),可以只用unit和apply來定義map2和map函數(shù)满粗。

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] 

  def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = {
    val fabc: F[A => B => C] = unit(f.curried)
    val fbc = apply(fabc)(fa)
    apply(fbc)(fb)
  }

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

}

練習(xí) 12.3
apply方法對實(shí)現(xiàn)map3和map4等方法也是很有用的辈末,實(shí)現(xiàn)map3和map4方法只使用unit和apply。

  def map3[A, B, C, D](fa: F[A], fb: F[B], fc: F[C])(f: (A, B, C) => D): F[D] = {
    val fabcd = unit(f.curried)
    val fbcd = apply(fabcd)(fa)
    val fcd = apply(fbcd)(fb)
    apply(fcd)(fc)
  }

  def map4[A, B, C, D, E](fa: F[A], fb: F[B], fc: F[C],
                          fd: F[D])(f: (A, B, C, D) => E): F[E] = {
    val fabcde = unit(f.curried)
    val fbcde = apply(fabcde)(fa)
    val fcde = apply(fbcde)(fb)
    val fde = apply(fcde)(fc)
    apply(fde)(fd)
  }

更進(jìn)一步可以Monad[F]成為Applicative[F]的一個(gè)子類,并使用flatMap實(shí)現(xiàn)map2:

trait Monad[F[_]] extends Applicative[F] {
  def unit[A](a: => A): F[A]

  def flatMap[A, B](a: F[A])(f: A => F[B]): F[B]

  def compose[A, B, C](f: A => F[B], g: B => F[C]): A => F[C] =
    a => join(map(f(a))(g))

  def join[A](mma: F[F[A]]): F[A] =
    flatMap(mma){ma => ma}

  override def map[A, B](a: F[A])(f: (A) => B): F[B] =
    flatMap(a)(a => unit(f(a)))

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

單子和可應(yīng)用函子的區(qū)別
雖然所有的monad都是可應(yīng)用函子挤聘,但是并不是所有的applicative都是單子轰枝。
可應(yīng)用函子的優(yōu)勢
一般來說,實(shí)現(xiàn)一個(gè)像traverse的組合子需要的假設(shè)越少越好组去。
因?yàn)锳pplicative相比Monad弱一些鞍陨,這就給了它更多的靈活性。
Applicative functor可組合从隆,但是一般Monad不可以诚撵。
練習(xí) 12.6
為Validation寫一個(gè)Applicative實(shí)例,累計(jì)失敗時(shí)的錯(cuò)誤键闺。注意缩筛,在失敗的情況下净嘀,至少會(huì)有一個(gè)error存在于列表的head吃型,其余的error累加在列表的tail霹菊。

sealed trait Validation[+E, +A] {

}

case class Failure[E](head: E, tail: Vector[E] = Vector()) extends Validation[E, Nothing]

case class Success[A](a: A) extends Validation[Nothing, A]

object Applicative {


  def apply[F[_]](implicit fa: Applicative[F]): Applicative[F] = fa


  type StringValidation[A] = Validation[String, A]

  implicit def validtionApplicative: Applicative[StringValidation] = new Applicative[StringValidation] {

    override def map2[A, B, C](fa: StringValidation[A],
                               fb: StringValidation[B])(f: (A, B) => C): StringValidation[C] = (fa, fb) match {
      case (Failure(h, t), Success(b)) => Failure(h, t)
      case (Success(a), Failure(h, t)) => Failure(h, t)
      case (Success(a), Success(b)) => Success(f(a, b))
      case (Failure(h1, t1), Failure(h2, t2)) => Failure(h2, t2 ++ Vector(h1) ++ t1)
    }

    override def unit[A](a: => A): StringValidation[A] = Success(a)
  }

  def validName(name: String): Validation[String, String] =
    if (name != "") Success(name)
    else Failure("Name can not be empty")

  def validBirthdate(birthdate: String): Validation[String, Date] =
    try {
      import java.text._
      Success(new SimpleDateFormat("yyyy-MM-dd").parse(birthdate))
    } catch {
      case _ => Failure("Birthdate must be in the form yyyy-MM-dd")
    }

  def validPhone(phoneNum: String): Validation[String, String] =
    if (phoneNum.matches("[0-9]{10}")) Success(phoneNum)
    else Failure("Phone number must be 10 digits")

  case class WebForm(name: String, birthdate: Date, phoneNum: String)

  def vaildWebForm(name: String, birthdate: String,
                   phoneNum: String)(implicit va: Applicative[StringValidation]): Validation[String, WebForm] =
    va.map3(validName(name), validBirthdate(birthdate), validPhone(phoneNum))(WebForm(_, _, _))

}

可遍歷函子
前面的內(nèi)容介紹了可應(yīng)用函子伤提,我們再次來查看traverse和sequence的函數(shù)簽名:

  def traverse[F[_], A, B](la: List[A])(f: A => F[B]): F[List[B]]
  
  def sequence[F[_], A](la: List[F[A]]): F[List[A]]

在這些函數(shù)簽名中我們看到了具體的類型構(gòu)造器List癞己,除了List還有不少數(shù)據(jù)類型也是可以折疊的台囱,那么這些數(shù)據(jù)類型是不是可遍歷的菠隆?當(dāng)然氏淑!但是為每個(gè)可遍歷的數(shù)據(jù)類型編寫專門的sequence和遍歷方法是十分繁瑣的勃蜘。需要一個(gè)新的接口,我們稱為Traverse:

trait Traverse[F[_]] extends Functor[F] {

  def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(implicit ga: Applicative[G]): G[F[B]] =
    sequence(map(fa)(f))

  def sequence[G[_], A](fga: F[G[A]])(implicit ga: Applicative[G]): G[F[A]] =
    traverse(fga)(a => a)

}

練習(xí) 12.13
對List假残、Option和Tree實(shí)現(xiàn)Traverse實(shí)例

object Traverse {

  def apply[F[_]](implicit tf: Traverse[F]): Traverse[F] = tf

  implicit val listTraverse: Traverse[List] = new Traverse[List] {
    override def map[A, B](a: List[A])(f: (A) => B): List[B] = a match {
      case Nil => Nil
      case head :: tail => f(head) :: map(tail)(f)
    }
  }

  implicit val optionTraverse: Traverse[Option] = new Traverse[Option] {
    override def map[A, B](a: Option[A])(f: (A) => B): Option[B] = a match {
      case None => None
      case Some(a1) => Some(f(a1))
    }
  }

  implicit val treeTraverse: Traverse[Tree] = new Traverse[Tree] {
    override def map[A, B](a: Tree[A])(f: (A) => B): Tree[B] =
      Tree(f(a.head), a.tail.map(t => map(t)(f)))
  }
}

case class Tree[+A](head: A, tail: List[Tree[A]])
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缭贡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辉懒,更是在濱河造成了極大的恐慌阳惹,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眶俩,死亡現(xiàn)場離奇詭異莹汤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)颠印,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門纲岭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人线罕,你說我怎么就攤上這事止潮。” “怎么了钞楼?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵喇闸,是天一觀的道長。 經(jīng)常有香客問我,道長燃乍,這世上最難降的妖魔是什么唆樊? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮橘沥,結(jié)果婚禮上窗轩,老公的妹妹穿的比我還像新娘。我一直安慰自己座咆,他們只是感情好痢艺,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著介陶,像睡著了一般堤舒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哺呜,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天舌缤,我揣著相機(jī)與錄音,去河邊找鬼某残。 笑死国撵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的玻墅。 我是一名探鬼主播介牙,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼澳厢!你這毒婦竟也來了环础?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤剩拢,失蹤者是張志新(化名)和其女友劉穎线得,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徐伐,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贯钩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了办素。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片魏保。...
    茶點(diǎn)故事閱讀 40,427評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖摸屠,靈堂內(nèi)的尸體忽然破棺而出谓罗,到底是詐尸還是另有隱情,我是刑警寧澤季二,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布檩咱,位于F島的核電站揭措,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏刻蚯。R本人自食惡果不足惜绊含,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望炊汹。 院中可真熱鬧躬充,春花似錦、人聲如沸讨便。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霸褒。三九已至伴找,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間废菱,已是汗流浹背技矮。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留殊轴,地道東北人衰倦。 一個(gè)月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像旁理,于是被迫代替她去往敵國和親樊零。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容