泛函编程(10)-异常处理-Either
上节我们介绍了新的数据类型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的更多相关文章
- 泛函编程(9)-异常处理-Option
Option是一种新的数据类型.形象的来描述:Option就是一种特殊的List,都是把数据放在一个管子里:然后在管子内部对数据进行各种操作.所以Option的数据操作与List很相似.不同的是Opt ...
- 泛函编程(35)-泛函Stream IO:IO处理过程-IO Process
IO处理可以说是计算机技术的核心.不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果.所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果.输入数 ...
- 泛函编程(29)-泛函实用结构:Trampoline-不再怕StackOverflow
泛函编程方式其中一个特点就是普遍地使用递归算法,而且有些地方还无法避免使用递归算法.比如说flatMap就是一种推进式的递归算法,没了它就无法使用for-comprehension,那么泛函编程也就无 ...
- 泛函编程(27)-泛函编程模式-Monad Transformer
经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...
- 泛函编程(25)-泛函数据类型-Monad-Applicative
上两期我们讨论了Monad.我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型.任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以变成Mo ...
- 泛函编程(4)-深入Scala函数类
既然是泛函编程,多了解一下函数自然是免不了的了: 方法(Method)不等于函数(Function) 方法不是函数但可以转化成函数:可以手工转换或者由编译器(compiler)在适当的情况下自动转换. ...
- 泛函编程(24)-泛函数据类型-Monad, monadic programming
在上一节我们介绍了Monad.我们知道Monad是一个高度概括的抽象模型.好像创造Monad的目的是为了抽取各种数据类型的共性组件函数汇集成一套组件库从而避免重复编码.这些能对什么是Monad提供一个 ...
- 泛函编程(23)-泛函数据类型-Monad
简单来说:Monad就是泛函编程中最概括通用的数据模型(高阶数据类型).它不但涵盖了所有基础类型(primitive types)的泛函行为及操作,而且任何高阶类或者自定义类一旦具备Monad特性就可 ...
- 泛函编程(20)-泛函库设计-Further Into Parallelism
上两节我们建了一个并行运算组件库,实现了一些基本的并行运算功能.到现在这个阶段,编写并行运算函数已经可以和数学代数解题相近了:我们了解了问题需求,然后从类型匹配入手逐步产生题解.下面我们再多做几个练习 ...
- 泛函编程(8)-数据结构-Tree
上节介绍了泛函数据结构List及相关的泛函编程函数设计使用,还附带了少许多态类型(Polymorphic Type)及变形(Type Variance)的介绍.有关Polymorphism的详细介绍会 ...
随机推荐
- Node.js之绝对选择
几年前,完全放弃Asp.net,彻底脱离微软方向.Web开发,在公司团队中,一概使用Node.js.Mongodb.Git,替换Asp.net mvc.Sql server和Tfs.当时来看,这是高风 ...
- 高性能优化Web前端
高性能HTML 一.避免使用iframe iframe也叫内联frame,可将一个HTML文档嵌入另一个HTML文档中. iframe的好处是,嵌入的文档独立于父文档,通常也借此使浏览器模拟多线程.缺 ...
- [LeetCode] Best Meeting Point
Problem Description: A group of two or more people wants to meet and minimize the total travel dista ...
- ArcGIS Runtime for Android 使用异步GP服务绘制等值线
关于基于Android上ArcGIS Server GP服务的调用,已经有前辈给出了很好的例子: http://blog.csdn.net/esrichinacd/article/details/92 ...
- 设置zookeeper jvm内存
看了你的问题, 我还特意的查看了ZooKeeper的启动脚本代码.ZooKeeper启动脚本没有加任何参数,也就是使用jvm默认的. 如果想要加大ZooKeeper的JVM使用内存.可以在更改{ZK_ ...
- 那些年我们赚过的外快(POS(移动支付)接口开发)
老规矩上前戏了.在我写博文"那些年我们赚过的外快"前后算起来大大小小也接了些私活,这次是因为好久没写博客了,趁热分享一下.最近回了离老家近的二线城市成都工作,收入那是下降很多啊,刚 ...
- mysql 性能优化 配置优化
http://download.csdn.net/album/detail/1397/2
- 读书笔记_Effective_C++_条款四十七:请使用trait classes来表示类型信息
这一条款主要来讨论模板中迭代器的属性iterator_category,它可以通过类似于vector<int>::iterator::iterator_category的方式来取得. 到这 ...
- There is an internal error in the React performance measurement code.Did not expect componentDidMount timer to start while render timer is still in progress for another instance
一.There is an internal error in the React performance measurement code.Did not expect componentDidMo ...
- 对C#对象的Shallow、Deep Cloning认识【转】
好像园内比较多博客对 Shallow.Deep Cloning的翻译是深拷贝.浅拷贝,当时我懵了,这个叫法怎么怪怪的. 就好像看军情观察室,台湾评论员,导弹叫飞弹. 至于它们的区别,一张图就可以解释. ...