不用異常來處理錯誤-Validated

在上一章節(jié)中我們介紹了Either的實現(xiàn)巴柿,在使用Either來校驗輸入的例子中我們提到了Either的一個缺陷,那就是Either只能收集一次錯誤信息死遭,收集完就返回錯誤信息了广恢, 這里存在的問題就是當(dāng)我們需要多個輸入項進行校驗的時候,每次都只能返回一個錯誤信息呀潭。想想一個表單的校驗钉迷,假如用戶有許多的字段都是錯誤的,沒有提交只能返回一個錯誤信息钠署,那用戶需要不斷的提交試錯糠聪,這樣是非常不友好的。參照Either谐鼎,我們來實現(xiàn)一種新的數(shù)據(jù)結(jié)構(gòu):

package org.fp.scala.datastruct

trait Validated[+E, +A] {

  def map[B](f: A => B): Validated[E, B] = this match {
    case Invalid(v) => Invalid(v)
    case Valid(a) => Valid(f(a))
  }

  def flatMap[EE >: E, B](f: A => Validated[EE, B]): Validated[EE, B] = this match {
    case Invalid(v) => Invalid(v)
    case Valid(a) => f(a)
  }

  def orElse[EE >: E, B >: A](default: Validated[EE, B]): Validated[EE, B] = this match {
    case Invalid(_) => default
    case _ => this
  }

  def map2[EE >: E, B, C](vb: Validated[EE, B])(f: (A, B) => C): Validated[EE, C] =
    (this, vb) match {
      case (Invalid(v), Valid(_)) => Invalid(v)
      case (Valid(_), Invalid(v)) => Invalid(v)
      case (Invalid(v1), Invalid(v2)) => Invalid(v1 ++ v2)
      case (Valid(a), Valid(b)) => Valid(f(a, b))
    }
}

case class Invalid[+E](value: List[E]) extends Validated[E, Nothing]

case class Valid[+A](value: A) extends Validated[Nothing, A]

可以看到Validated的結(jié)構(gòu)和Either結(jié)構(gòu)基本是類似的舰蟆,只是表示錯誤的時候用的是Invalid[+E](value: List[E]),他表明Invalid可以收集和傳遞無限個錯誤。再看一看其中基本方法的實現(xiàn)身害,其中其他的基本方法的實現(xiàn)都是類似的味悄,只有map2方法不一樣,新的map2方法再遇到兩個Validated都是Invalid類型的時候塌鸯,可以將Invalid中的錯誤信息組合起來傍菇。既然實現(xiàn)了map2我們完全可以使用map2來實現(xiàn)map3, 下面來看下代碼:

  def map3[EE >: E, B, C, D](vb: Validated[EE, B])(vc: Validated[EE, C])(f: (A, B, C) => D): Validated[EE, D] =
    map2(vb)((_, _)).map2(vc) {
      case ((a, b), c) => f(a, b, c)
    }

我們再來看下traverse和sequence的實現(xiàn):

object Validated {

  def traverse[E, A, B](li: List[A])(f: A => Validated[E, B]): Validated[E, List[B]] =
    li match {
      case Nil => Valid(List())
      case Cons(h, t) => f(h).map2(traverse(t)(f))(Cons(_, _))
    }

  def traverse1[E, A, B](li: List[A])(f: A => Validated[E, B]): Validated[E, List[B]] =
    li.foldRight(Valid(List()): Validated[E, List[B]]) ((a, vlb) => f(a).map2(vlb)(Cons(_, _)))

  def sequence[E, A](li: List[Validated[E, A]]): Validated[E, List[A]] =
    traverse(li)(x => x)
}

可以看到Validated類型中traverse和sequence的實現(xiàn)是類似的,但是由于map2的實現(xiàn)不同界赔,他產(chǎn)生的效果是完全不一樣的丢习, 我們再來看下之前關(guān)于校驗的例子:

  case class Student(firstName: Name, lastName: Name, age: Age)

  case class Name(value: String)

  case class Age(value: Int)

  def parseName(name: String): Validated[String, Name] = {
    if (name == null || name.isEmpty) Invalid(List("name is null or empty"))
    else Valid(Name(name))
  }

  def parseAge(age: Int): Validated[String, Age] = {
    if (age < 0 || age > 50) Invalid(List("age is not between [0, 50]"))
    else Valid(Age(age))
  }

  def parseStudent(firstName:String, secondName: String, age: Int): Validated[String, Student] =
    parseName(firstName).map3(parseName(secondName))(parseAge(age))(Student)

這一次parseStudent方法的實現(xiàn),我們利用了map3方法淮悼, 現(xiàn)在來測試一下:

    def mkString(li: List[String], s: String): String =
      li.foldRight("")(_ + s + _)

    parseStudent(null, "", -1) match {
      case Invalid(v) => println(mkString(v, "\n"))
      case Valid(s) => println("student: " + s)
    }

scala>name is null or empty
scala>name is null or empty
scala>age is not between [0, 50]

可以看到使用了新的數(shù)據(jù)類型Validated咐低, parseStudent方法可以校驗出所有的錯誤類型

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市袜腥,隨后出現(xiàn)的幾起案子见擦,更是在濱河造成了極大的恐慌,老刑警劉巖羹令,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲤屡,死亡現(xiàn)場離奇詭異,居然都是意外死亡福侈,警方通過查閱死者的電腦和手機酒来,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肪凛,“玉大人堰汉,你說我怎么就攤上這事∥扒剑” “怎么了翘鸭?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長戳葵。 經(jīng)常有香客問我就乓,道長,這世上最難降的妖魔是什么拱烁? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任生蚁,我火速辦了婚禮,結(jié)果婚禮上邻梆,老公的妹妹穿的比我還像新娘守伸。我一直安慰自己,他們只是感情好浦妄,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布尼摹。 她就那樣靜靜地躺著见芹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蠢涝。 梳的紋絲不亂的頭發(fā)上玄呛,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天,我揣著相機與錄音和二,去河邊找鬼徘铝。 笑死,一個胖子當(dāng)著我的面吹牛惯吕,可吹牛的內(nèi)容都是我干的惕它。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼废登,長吁一口氣:“原來是場噩夢啊……” “哼淹魄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起堡距,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤甲锡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后羽戒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缤沦,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年易稠,在試婚紗的時候發(fā)現(xiàn)自己被綠了缸废。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡缩多,死狀恐怖呆奕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衬吆,我是刑警寧澤,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布绳泉,位于F島的核電站逊抡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏零酪。R本人自食惡果不足惜冒嫡,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望四苇。 院中可真熱鬧孝凌,春花似錦、人聲如沸月腋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至片拍,卻和暖如春煌集,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捌省。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工苫纤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纲缓。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓卷拘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親祝高。 傳聞我的和親對象是個殘疾皇子栗弟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349