上节我们介绍了新的数据类型Option:一个专门对付异常情况出现时可以有一致反应所使用的数据类型。Option可以使编程人员不必理会出现异常后应该如何处理结果,他只是获得了一个None值,但这个None值与他所期待的类型是一致的,他可以继续用处理这种类型数据的方法使用这个结果。不过遗憾的是我们通过None值只能知道某个计算没能得出结果,但到底发生了什么事Option并没有提供任何提示。这样我们也就无法向用户提供贴切的系统错误或着操作失误信息了。

这样我们就需要在Option的基础上添加一个扩展功能的新数据类型,让它可以返回一些异常描述:Either。可以想象Either在返回None的同时还要包含一个返回值,用来描述异常。那么这个None的形式就变成了None(e)了。我们先看看Eigher的框架设计:

   trait Either[+E,+A]
case class Left[+E](value: E) extends Either[E,Nothing]
case class Right[+A](value: A) extends Either[Nothing,A]

以上可见Either需要处理两个类型E和A:E代表异常类型,A代表计算类型。与Option一样,Either也有两种状态:Left代表无法完成计算,返回值E是对异常情况的描述、Right则代表计算正常完成,返回计算结果A。从英文解释,Either不是Right就是Left。这种情况被称为类型的“不联合性”(disjoint union)。

提出了Either的基本描述后开始数据类型操作函数的实现:

       def map[B](f: A => B): Either[E,B] = this match {
case Right(a) => Right(f(a))
case Left(e) => Left(e)
}
def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = this match {
case Left(e) => Left(e)
case Right(a) => f(a)
}
def orElse[EE >: E, AA >: A](default: Either[EE, AA]): Either[EE, AA] = this match {
case Left(_) => default
case Right(a) => Right(a)
}

还是由于Either这种类型的管子里只能存一个元素,所以操作函数的实现比较直接简单:用类型匹配和递归算法就行了。

在以下的函数中我们可以用一个函数 (A,B) => C 把两个Either[A],Either[B]组合成Either[C]:

       //用递归算法
def map2[EE >: E, B, C](b: Either[EE, B])(f: (A,B) => C): Either[EE, C] = (this,b) match {
case (Left(e),_) => Left(e)
case (_, Left(e)) => Left(e)
case (Right(a),Right(b)) => Right(f(a,b))
}
//用for comprehension
def map2_1[EE >: E, B, C](b: Either[EE, B])(f: (A,B) => C): Either[EE, C] = {
for {
aa <- this
bb <- b
} yield f(aa,bb)
}
//用 flatMap写
def map2_2[EE >: E, B, C](b: Either[EE, B])(f: (A,B) => C): Either[EE, C] = {
flatMap(aa => b map(bb => f(aa,bb)))
}

考虑map2时并不复杂:由于我只有一个利用低阶的函数(A,B) =??? ,我必须想办法把Either管子里的那个元素取出来计算完后塞到一个新的Either管子里去。以上我们已经实现了map,flatMap我们可以使用for comprehension来实现:

aa <- a: Either - 从Either管子取出元素

yield 产生新的Either。map2_1是for comprehension的直接写法。

由于我们有map和flatMap,我们可以试着用用Either:

  case class Employee(name: String, age: Int, salary: Double)
for {
age <- Right(42)
name <- Left("Invalid Name!")
salary <- Right(10000.00)
} yield Employee(name,age,salary) //> res0: ch4.either.Either[String,ch4.either.Employee] = Left(Invalid Name!)
for {
age <- Right(42)
name <- Right("Jonny Cash!")
salary <- Right(10000.00)
} yield Employee(name,age,salary) //> res1: ch4.either.Either[Nothing,ch4.either.Employee] = Right(Employee(Jonny
//| Cash!,42,10000.0))

可以看出在以上三个动作中(age,name,salary)如果其中任何一个出现了异常Left,结果就会是Left了。

当然,我们还是有可能对一个系列的Either类型值进行计算的,所以sequence,traverse这两个函数总是会用到的:

         //用递归算法,用f把元素升格成Either后再用map2把连续的两个元素连接起来
def traverse[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = es match {
case Nil => Right(Nil)
case h :: t => (f(h) map2 traverse(t)(f))(_ :: _)
}
//用foldRight实现,用f把元素升格成Either后再用map2把连续的两个元素连接起来
def traverse_1[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = {
es.foldRight[Either[E, List[B]]](Right(Nil))((h,t) => f(h).map2(t)(_ :: _))
}
def sequence[E,A](es: List[Either[E,A]]): Either[E,List[A]] = es match {
case Nil => Right(Nil)
case h :: t => (h map2 sequence(t))(_ :: _)
}
def sequence_1[E,A](es: List[Either[E,A]]): Either[E,List[A]] = {
traverse(es)(x => x)
}

来个实际点的例子:

  case class Name(value: String)
case class Age(value: Int)
case class Person(name: Name, age: Age)
def mkName(name: String): Either[String, Name] = {
if (name == "" || name == null) Left("Invalid Name")
else Right(Name(name))
} //> mkName: (name: String)ch4.either.Either[String,ch4.either.Name]
def mkAge(age: Int): Either[String,Age] = {
if ( age < 0 ) Left("Invalid age")
else Right(Age(age))
} //> mkAge: (age: Int)ch4.either.Either[String,ch4.either.Age]
def mkPerson(name: String, age: Int): Either[String,Person] = {
mkName(name).map2(mkAge(age))(Person(_,_))
} //> mkPerson: (name: String, age: Int)ch4.either.Either[String,ch4.either.Perso
//| n] mkPerson("Tiger",18) //> res2: ch4.either.Either[String,ch4.either.Person] = Right(Person(Name(Tiger
//| ),Age(18)))
mkPerson("Tiger",-18) //> res3: ch4.either.Either[String,ch4.either.Person] = Left(Invalid age)
mkPerson("",-1) //> res4: ch4.either.Either[String,ch4.either.Person] = Left(Invalid Name)

mkPerson输入参数正确时返回Right。任何参数错误返回Left。不过如果两个参数都是错误的话就只能返回其中一个提示信息了。我们可以修改map2来获取全部信息:

         def map2_s[B, C](b: Either[String, B])(f: (A,B) => C): Either[String, C] = (this,b) match {
case (Left(e),Left(ee)) => Left(e+ee)
case (_, Left(e)) => Left(e)
case (Left(e:String), _) => Left(e)
case (Right(a),Right(b)) => Right(f(a,b)) }

注意:我们必须明确类型E为String,这样才能把两个数据接起来,因为我们不知道如何连接类型E。看看使用新版本后的结果:

   def mkPerson(name: String, age: Int): Either[String,Person] = {
mkName(name).map2_s(mkAge(age))(Person(_,_))
} //> mkPerson: (name: String, age: Int)ch4.either.Either[String,ch4.either.Perso
//| n] mkPerson("Tiger",18) //> res2: ch4.either.Either[String,ch4.either.Person] = Right(Person(Name(Tiger
//| ),Age(18)))
mkPerson("Tiger",-18) //> res3: ch4.either.Either[String,ch4.either.Person] = Left(Invalid age)
mkPerson("",-1) //> res4: ch4.either.Either[String,ch4.either.Person] = Left(Invalid NameInvali
//| d age)

没错,两个信息都连接起来返回了。

泛函编程(10)-异常处理-Either的更多相关文章

  1. 泛函编程(9)-异常处理-Option

    Option是一种新的数据类型.形象的来描述:Option就是一种特殊的List,都是把数据放在一个管子里:然后在管子内部对数据进行各种操作.所以Option的数据操作与List很相似.不同的是Opt ...

  2. 泛函编程(35)-泛函Stream IO:IO处理过程-IO Process

    IO处理可以说是计算机技术的核心.不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果.所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果.输入数 ...

  3. 泛函编程(29)-泛函实用结构:Trampoline-不再怕StackOverflow

    泛函编程方式其中一个特点就是普遍地使用递归算法,而且有些地方还无法避免使用递归算法.比如说flatMap就是一种推进式的递归算法,没了它就无法使用for-comprehension,那么泛函编程也就无 ...

  4. 泛函编程(27)-泛函编程模式-Monad Transformer

    经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...

  5. 泛函编程(25)-泛函数据类型-Monad-Applicative

    上两期我们讨论了Monad.我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型.任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以变成Mo ...

  6. 泛函编程(4)-深入Scala函数类

    既然是泛函编程,多了解一下函数自然是免不了的了: 方法(Method)不等于函数(Function) 方法不是函数但可以转化成函数:可以手工转换或者由编译器(compiler)在适当的情况下自动转换. ...

  7. 泛函编程(24)-泛函数据类型-Monad, monadic programming

    在上一节我们介绍了Monad.我们知道Monad是一个高度概括的抽象模型.好像创造Monad的目的是为了抽取各种数据类型的共性组件函数汇集成一套组件库从而避免重复编码.这些能对什么是Monad提供一个 ...

  8. 泛函编程(23)-泛函数据类型-Monad

    简单来说:Monad就是泛函编程中最概括通用的数据模型(高阶数据类型).它不但涵盖了所有基础类型(primitive types)的泛函行为及操作,而且任何高阶类或者自定义类一旦具备Monad特性就可 ...

  9. 泛函编程(20)-泛函库设计-Further Into Parallelism

    上两节我们建了一个并行运算组件库,实现了一些基本的并行运算功能.到现在这个阶段,编写并行运算函数已经可以和数学代数解题相近了:我们了解了问题需求,然后从类型匹配入手逐步产生题解.下面我们再多做几个练习 ...

  10. 泛函编程(8)-数据结构-Tree

    上节介绍了泛函数据结构List及相关的泛函编程函数设计使用,还附带了少许多态类型(Polymorphic Type)及变形(Type Variance)的介绍.有关Polymorphism的详细介绍会 ...

随机推荐

  1. 自动化测试管理平台ATMS(V2.0.1_8.12)下载

    自动化测试管理平台ATMS(V2.0.1_8.12)下载: http://automationqa.com/forum.php?mod=viewthread&tid=2701&from ...

  2. AX7 VM can not starting

    这个问题跟很多人遇到的一样,VM下载,一开始使用正常,特然某个时候,就挂了,直接引用老外的描述吧 http://community.dynamics.com/ax/f/33/p/203984/5296 ...

  3. Android关于Theme.AppCompat相关问题的深入分析(转)

    http://www.jianshu.com/p/6ad7864e005e 先来看这样一个错误: No resource found that matches the given name '@sty ...

  4. linux_脚本应用

    linux下三个有用的 Python 脚本 2010年4月29日   import os, sys  def pyc_clean(dir):      findcmd = 'find %s -name ...

  5. 十一、EnterpriseFrameWork框架的分层与系统业务的结合

    上章详细讲了EnterpriseFrameWork框架中的每个分层,这都是从技术层面来说明,也就是我们知道怎么来建一个控制器或一个业务对象,但开发过程中应该建一个什么样的控制器或业务对象了?本章的主要 ...

  6. Ext.js中的tip事件实际使用

    Ext.onReady(function () { // Init the singleton. Any tag-based quick tips will start working. Ext.ti ...

  7. 基于jQuery点击加载动画按钮特效

    分享一款基于jQuery点击加载动画按钮特效.这是一款基于jQuery+CSS3实现的鼠标点击按钮加载动画特效代码.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div ...

  8. call方法和new对象的关系

    call只能改变this的指向,而使用new对象不仅会自动调用call方法改变这个对象的this指向,而且还会继承构造函数的原型. var fn = function(a){ this.a = a; ...

  9. [Design Patterns] 4. Creation Pattern

    设计模式是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结,使用设计模式的目的是提高代码的可重用性,让代码更容易被他人理解,并保证代码可靠性.它是代码编制真正实现工程化. 四个关键元素 ...

  10. Understanding CMS GC Logs--转载

    原文地址:https://blogs.oracle.com/poonam/entry/understanding_cms_gc_logs Understanding CMS GC Logs By Po ...