Generic Programming (10) - Exception Handling - Either


In the previous section we introduced the new datatype Option: a datatype used specifically to deal with exceptions that can have a consistent response when they arise. Option allows the programmer to not have to bother with what he should do with the result when an exception occurs; he just gets a None value, but that None value is of the type he expects, and he can continue to use the result in the same way he handles data of that type. Unfortunately though we only know by the None value that a particular calculation didn't yield a result, but Option doesn't provide any hints as to what exactly happened. This also prevents us from providing users with relevant information about system errors or malfunctions.

This would require us to add a new data type with extended functionality to Option, allowing it to return some exception description: either. It is conceivable that Either would return None while also including a return value to describe the exception. Then the form of this None becomes None(e). Let's start by looking at Eigher's framework design.

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]

As seen above Either needs to handle two types E and A: E for exception types and A for computation types. Like Option, Either has two states: Left means the calculation could not be completed and the return value E is a description of the exception, while Right means the calculation completed normally and returns the calculation result A. From the English interpretation, Either is either Right or Left. This situation is known as disjoint union of types.

After presenting a basic description of Either the implementation of the data type manipulation function begins.

 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       }

Still, since only one element can be stored in a tube of type Either, the implementation of the manipulation function is relatively straightforward and simple: just use type matching and a recursive algorithm.

In the following functions we can usefulness A function (A,B) => C Put twoEither[A],Either[B] combine intoEither[C]:

 1       // Using recursive algorithms
 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       // usefulnessfor 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         // usefulness flatMap write
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         }

considerationmap2 It's not complicated when: Since I only have one li usefulness Functions of lower order(A,B) =??? , I had to find a way to putEither That element in the tube is removed and calculated and stuffed into a newEither Get in the pipe.。 Above we have achievedmap,flatMap We can make usefulnessfor comprehension come true:

aa <- a: Either - through (a gap)Either Tube Removal Element

yield produces a new Either. map2_1 is a straightforward way of writing for comprehension.

Since we havemap harmonyflatMap, We could try. usefulness usefulnessEither:

 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))

You can see that in the above three actions (age,name,salary) if any one of them has an exception Left, the result will be Left.

Of course, it is still possible to perform calculations on a series of values of type Either, so the functions sequence, and traverse will always be used: the

 1         // Using recursive algorithms, usefulnessf Elevate the elements toEither post- usefulnessmap2 Joining two consecutive elements
 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       // usefulnessfoldRight realize, usefulnessf Elevate the elements toEither post- usefulnessmap2 Joining two consecutive elements
 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       }

To give a more practical example.

 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 returns Right when the input parameters are correct. Any parameter error returns Left. But if both parameters are errors only one of the hints will be returned. We can modify map2 to get all the information.

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         }

note: We must be clear about the typeE because ofString, That's how you connect the two data, Because we don't know how to connect the typeE。 Look at the make usefulness Results after the new version:

 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)

That's right, both messages are connected and returned.


Recommended>>
1、Dreams start here Another group of youngsters joined CK Towers Watson to start their dream life
2、Cloud collection transformation of social ecommerce to solve which problems of ecommerce
3、Artificial intelligence products to receive grading standards
4、Haiers smart home solutions win GTICAWARDS 2018 awards
5、Tired of watching robots Check out the Japanese bionic tentacle oh no the bionic robot snake

    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号