泛函编程(10)-异常处理-Either

简介:

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

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


1   trait Either[+E,+A] 
2   case class Left[+E](value: E) extends Either[E,Nothing]
3   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的基本描述后开始数据类型操作函数的实现:


1       def map[B](f: A => B): Either[E,B] = this match {
 2           case Right(a) => Right(f(a))
 3           case Left(e) => Left(e)
 4       }
 5       def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = this match {
 6           case Left(e)  => Left(e)
 7           case Right(a) => f(a)
 8       }
 9       def orElse[EE >: E, AA >: A](default: Either[EE, AA]): Either[EE, AA] = this match {
10           case Left(_) => default
11           case Right(a) => Right(a)
12       }

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

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


 1       //用递归算法
 2       def map2[EE >: E, B, C](b: Either[EE, B])(f: (A,B) => C): Either[EE, C] = (this,b) match {
 3           case (Left(e),_) => Left(e)
 4           case (_, Left(e)) => Left(e)
 5           case (Right(a),Right(b)) => Right(f(a,b))
 6       }
 7       //用for comprehension
 8         def map2_1[EE >: E, B, C](b: Either[EE, B])(f: (A,B) => C): Either[EE, C] = {
 9             for {
10                 aa <- this
11                 bb <- b
12             } yield f(aa,bb)
13         }
14         //用 flatMap写
15         def map2_2[EE >: E, B, C](b: Either[EE, B])(f: (A,B) => C): Either[EE, C] = {
16             flatMap(aa => b map(bb => f(aa,bb)))
17         }

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

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

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

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


1  case class Employee(name: String, age: Int, salary: Double) 
 2   for {
 3     age <- Right(42)
 4     name <- Left("Invalid Name!")
 5     salary <- Right(10000.00)
 6   } yield Employee(name,age,salary)               //> res0: ch4.either.Either[String,ch4.either.Employee] = Left(Invalid Name!)
 7   for {
 8     age <- Right(42)
 9     name <- Right("Jonny Cash!")
10     salary <- Right(10000.00)
11   } yield Employee(name,age,salary)               //> res1: ch4.either.Either[Nothing,ch4.either.Employee] = Right(Employee(Jonny
12                                                   //|  Cash!,42,10000.0))

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

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


 1         //用递归算法,用f把元素升格成Either后再用map2把连续的两个元素连接起来
 2       def traverse[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = es match {
 3            case Nil => Right(Nil)
 4            case h :: t => (f(h) map2 traverse(t)(f))(_ :: _)
 5       }
 6       //用foldRight实现,用f把元素升格成Either后再用map2把连续的两个元素连接起来
 7       def traverse_1[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = {
 8           es.foldRight[Either[E, List[B]]](Right(Nil))((h,t) => f(h).map2(t)(_ :: _))
 9       }
10       def sequence[E,A](es: List[Either[E,A]]): Either[E,List[A]] = es match {
11           case Nil => Right(Nil)
12           case h :: t => (h map2 sequence(t))(_ :: _)
13       }
14       def sequence_1[E,A](es: List[Either[E,A]]): Either[E,List[A]] = {
15           traverse(es)(x => x)
16       }

来个实际点的例子:


1  case class Name(value: String)
 2   case class Age(value: Int)
 3   case class Person(name: Name, age: Age)
 4   def mkName(name: String): Either[String, Name] = {
 5       if (name == "" || name == null) Left("Invalid Name")
 6       else Right(Name(name))
 7   }                                               //> mkName: (name: String)ch4.either.Either[String,ch4.either.Name]
 8   def mkAge(age: Int): Either[String,Age] = {
 9       if ( age < 0 ) Left("Invalid age")
10       else Right(Age(age))
11   }                                               //> mkAge: (age: Int)ch4.either.Either[String,ch4.either.Age]
12   def mkPerson(name: String, age: Int): Either[String,Person] = {
13       mkName(name).map2(mkAge(age))(Person(_,_))
14   }                                               //> mkPerson: (name: String, age: Int)ch4.either.Either[String,ch4.either.Perso
15                                                   //| n]
16 
17   mkPerson("Tiger",18)                            //> res2: ch4.either.Either[String,ch4.either.Person] = Right(Person(Name(Tiger
18                                                   //| ),Age(18)))
19   mkPerson("Tiger",-18)                           //> res3: ch4.either.Either[String,ch4.either.Person] = Left(Invalid age)
20   mkPerson("",-1)                                 //> res4: ch4.either.Either[String,ch4.either.Person] = Left(Invalid Name)

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


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

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


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

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


相关文章
|
7月前
|
程序员 测试技术 数据库连接
PHP编程中的错误处理技巧
在PHP编程中,处理错误是至关重要的一环。本文将介绍几种常见的PHP错误类型及其处理技巧,包括语法错误、运行时错误和逻辑错误。通过学习正确的错误处理方法,可以提高代码的可靠性和可维护性,从而更好地保障程序的稳定性和安全性。
63 2
|
3月前
|
Java 数据库连接 UED
掌握Java编程中的异常处理
【9月更文挑战第18天】在Java的世界中,异常是那些不请自来的客人,它们可能在任何时候突然造访。本文将带你走进Java的异常处理机制,学习如何优雅地应对这些突如其来的“访客”。从基本的try-catch语句到更复杂的自定义异常,我们将一步步深入,确保你能够在面对异常时,不仅能够从容应对,还能从中学到宝贵的经验。让我们一起探索如何在Java代码中实现健壮的异常处理策略,保证程序的稳定运行。
|
2月前
|
Java
Java编程中的异常处理技巧
【10月更文挑战第5天】在Java的世界里,异常就像是不请自来的客人,总是在你最不经意的时候敲门。但别担心,这里我们将一起探索如何优雅地迎接这些“客人”。本文将带你了解Java的异常处理机制,教你如何用try-catch语句和finally块来确保程序的稳健运行,并分享一些实用的异常处理技巧,让你的程序更加健壮。
|
5月前
|
存储 算法 编译器
C++ 异常机制问题之Dwarf CFI是什么
C++ 异常机制问题之Dwarf CFI是什么
|
6月前
|
SQL Java 编译器
29. 【Java教程】异常处理
29. 【Java教程】异常处理
35 3
|
7月前
|
NoSQL 测试技术 C++
C++的异常处理及错误调试技巧
C++的异常处理及错误调试技巧
84 0
|
7月前
|
IDE Java 测试技术
java异常处理及错误调试技巧
java异常处理及错误调试技巧
46 0
|
Unix 程序员 C语言
C语言编程—错误处理
C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据。在发生错误时,大多数的 C 或 UNIX 函数调用返回 1 或 NULL,同时会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误。您可以在 errno.h 头文件中找到各种各样的错误代码。 所以,C 程序员可以通过检查返回值,然后根据返回值决定采取哪种适当的动作。开发人员应该在程序初始化时,把 errno 设置为 0,这是一种良好的编程习惯。0 值表示程序中没有错误。 errno、perror() 和 strerror() C 语言提供了 perror() 和
102 0
|
C++
【C++异常机制】栈解旋
【C++异常机制】栈解旋
111 0
【C++异常机制】栈解旋
|
C++
【C++异常机制】
【C++异常机制】
60 0