Generic Programming (9)-Exception Handling-Option
Optionis a new data type。 Image to describe:Option It's a special kind ofList, It's all about putting data in a tube; Then various operations are performed on the data inside the tube。 consequentlyOption The data manipulation withList very similar。 The difference is thatOption can hold at most one element in the tube of, In this regardOption The data manipulation of theList Much simpler., owing to enable usefulness It is not necessary to pay attention to the position of the data elements、 sequential。Option merely have two states: Contains an element of any type or is empty。 Or so it seems.: anOption Instances contain 0 perhaps 1 individual element;None Rep is empty,Some(x) Represents an element containing an arbitrary typex。 harmonyList The two states of the:Nil andCons It's very similar.。 it's worth noting that..., This concept of being empty is similar to the concept ofjava ofnull happen to have Fundamental differences:None perhapsNil value of all have The explicit type whilenull then it could be any type of data。 (located) atjava In programming we usually need to attach some separate programs to check、 processnull happen to, but (not)None perhapsNil represents the state of a type data, It is possible to directly make usefulness。
Since Option is highly similar to List, let's try transposing the List's data type design over to.
1 trait Option[+A] 2 case object None extends Option[Nothing] 3 case class Some[+A](value: A) extends Option[A]
This is just likeList It's the same.。 definitely, The structure is the same, however owing toOption Up to have An element, that which have The operator functions of the。
So why add a data type? And what is Option used for?
Let's take a super simple java example to demonstrate.
1 java code 2 double divide(double x, double y) 3 { 4 double ratio; 5 try { 6 z = x / y; 7 } catch (Exception e) { 8 //bala bala ,,, 9 return ????? 10 } 11 return ratio; 12 }
While writing this java program a question immediately popped up: what should this function return if an error occurs? The function asserts that divide returns double, but we can't return any double values after an arithmetic error, any double values are incorrect. The only option is to solve it through Exception Handling. Does that mean that all users of this function will have to add a piece of code of their own to handle the exception? Then each user must write so.
1 java code 2 try { 3 r = divide(x,y); 4 //bala bala ... 5 } catch (Exception e) { 6 //bala bala ,,, 7 // bala bala ... 8 }
This is barely enough to continue programming, But in the end, the program went to shit., Added a lot of unnecessary code, It also bloats the rectification process, Makes it more difficult for programmers to read and understand。 This of the general function programmingOption Data types have been added to solve just such a problem。 in case The above question usefulnessScala If you write it:
1 def divide(x: Double, y: Double): Option[Double] = { 2 try { 3 Some(x/y) 4 } catch { 5 case e: Exception => None 6 } 7 }
firstly, not usefulness No more headaches about what value to return: If there's a problem, just returnNone。 make use of usefulness The person must start withOption Take the values out of this tube first, It looks like an additional procedure.。 This is actuallyOOP harmony Differences between generalized programming concepts: The style of generic programming is to do data reading in some tube, not have You have to take it out first.。 See how to make usefulness The above function, right?:
1 r = divide(3.3, 5.0) getOrElse raio(...)
It's much simpler, isn't it?。 So let's focus on this belowOption The realization of the bar。 Since the resemblance only have An element of theList, Then there's no need for some complicated left-right folding algorithm.:
1 trait Option[+A] { 2 def map[B](f: A => B): Option[B] = this match { 3 case None => None 4 case Some(a) => Some(f(a)) 5 } 6 def flatMap[B](f: A => Option[B]): Option[B] = this match { 7 case None => None 8 case Some(a) => f(a) 9 } 10 def filter(f: A => Boolean): Option[A] = this match { 11 case Some(a) if (f(a)) => this 12 case _ => None 13 } 14 def getOrElse[B >: A](default: => B): B = this match { 15 case None => default 16 case Some(a) => a 17 } 18 def orElse[B >: A](ob: => Option[B]): Option[B] = this match { 19 case None => ob 20 case _ => this 21 } 22 }
note: upper[B >: A] is the typeB is the typeA parent class, combined with+A deformity,Option[B] evenOption[A] parent class: in caseA beApple, or soB It can beFruit, Then the default value type above could beFruit, Or...Option[Fruit] finish。=> B Indicates input parametersB It's a delayed calculation., means that inside the function the real reference(refrence) It is only calculated for this parameter when。
The following is illustrated by some use cases.
1 // Add up in the tube. The result is still retained in the tube 2 Some(2) map {_ + 3} //> res0: ch4.exx.Option[Int] = Some(5) 3 val none = None: Option[Int] //> none : ch4.exx.Option[Int] = None 4 //You can use None directly without an exception 5 none map {_ + 3} //> res1: ch4.exx.Option[Int] = None 6 7 // Add up in the tube. The result is still retained in the tube 8 Some(2) flatMap { x => Some(x + 3)} //> res2: ch4.exx.Option[Int] = Some(5) 9 //You can just use None without an exception 10 none flatMap { x => Some(x + 3)} //> res3: ch4.exx.Option[Int] = None 11 12 Some(2) getOrElse 5 //> res4: Int = 2 13 none getOrElse 5 //> res5: Int = 5 14 Some(2) orElse Some(5) //> res6: ch4.exx.Option[Int] = Some(2) 15 none orElse Some(5) //> res7: ch4.exx.Option[Int] = Some(5)
Examples of combinations of internal functions of Option.
1 def flatMap_1[B](f: A => Option[B]): Option[B] = { 2 map(f) getOrElse None 3 // map(f) >>>> Option[Option[B]] 4 // in case Option[B] = X >>>> getOrElse Option[X] = X = Option[B] 5 } 6 def orElse_1[B >: A](ob: => Option[B]): Option[B] = { 7 map(Some(_)) getOrElse None 8 //this[Option[A]] Some(_) >>>> Option[A] 9 //map(Some(_)) >>>> Option[Option[A]] 10 } 11 def filter_1(f: A => Boolean): Option[A] = { 12 flatMap(a => if(f(a)) Some(a) else None) 13 }
Option Data types allow the programmer to disregard function exceptions, may usefulness Simple syntax focused on performing function combinations(function composition)。 popularize usefulnessOption into a significant style of generic programming。Scala It's aJVM programming language, thus in usefulnessScala Programming may be tuned usefulness amplejava library function。 So how do we make sure that we are tuning usefulness current havejava library without compromising the generalized programming style?? Do we need to make usefulnessjava function when usefulnessnull harmonyException and inScala hit the nail on the head usefulnessOption this (Cantonese)? The answer is no.! With the combination of functions through generic programming we can combine functions without changing thejava The implementation of the source code for thejava library function“ upgrade”(lifting)。 In fact, our current style requirements in generic programming are in tune usefulness When a function, This function has to be able to acceptOption Type incoming parameters and returnOption type value。 usefulness The function type to express this is: classifier for objects with a handle A => B Such function programming“ upgrade” become Option[A] => Option[B] Such a function:
1 def lift[A,B](f: A => B): (Option[A] => Option[B]) = _ map f
Woo, That's amazing.。 Let's start with the type matching analysis:map(f) >>> Option[B]。 This placeholder _ Here it represents the input parameters, even this >>>>>> Opption[A]。 So type matching。 In fact this function expression first specifies that the final generated result function is: Give one.Option, Return aOption, This is not a typical function text(lambda function) Description?:oa => oa map f >>> _ map f 。
Let's stick with the simple divide example above:divide(x,y) takes two input arguments, and we can recreate a simpler, one-input-argument example:9 divided by anydouble y.
1 def divide9(y: Double): Double ={ 2 9 / y 3 } //> divide9: (y: Double)Double
It's just a simple A => B, We can try to make usefulness:
divide9(2.0) //> res0: Double = 4.5 divide9(3.3) //> res1: Double = 2.7272727272727275
Pass in a Double parameter, returns a Double value.
Try again after you "upscale" divide9.
1 val lifted = lift[Double,Double](divide9) //> lifted : ch4.exx.Option[Double] => ch4.exx.Option[Double] = <function1> 2 lifted(Some(2.0)) //> res2: ch4.exx.Option[Double] = Some(4.5) 3 lifted(None) //> res3: ch4.exx.Option[Double] = None
divide9 ascends to lifted, passes lifted an Option, returns an Option. Exactly the result we were hoping for. Try a little more complicated: two- and three-parameter function ascending.
1 // usefulnessfor comprehension Two parameters 2 def lift2[A,B,C](f:(A,B) => C):(Option[A],Option[B]) => Option[C] = { 3 (oa: Option[A], ob: Option[B]) => for { 4 aa <- oa 5 bb <- ob 6 } yield f(aa,bb) 7 } 8 // usefulness flatMap style Three parameters 9 def lift3[A,B,C,D](f:(A,B,C) => D):(Option[A],Option[B],Option[C]) => Option[D] ={ 10 (oa: Option[A], ob: Option[B], oc: Option[C]) => 11 oa.flatMap(aa => ob.flatMap(bb => oc.map ( cc => f(aa,bb,cc) ))) 12 }
Test use results.
1 def divide(x: Double,y: Double): Double ={ 2 x / y 3 } //> divide: (x: Double, y: Double)Double 4 val lifted2 = lift2(divide) //> lifted2 : (ch4.exx.Option[Double], ch4.exx.Option[Double]) => ch4.exx.Opti 5 //| on[Double] = <function2> 6 lifted2(Some(9),Some(2.0)) //> res2: ch4.exx.Option[Double] = Some(4.5) 7 8 def divThenMul(x: Double, y: Double, z: Double): Double = { 9 x / y * z 10 } //> divThenMul: (x: Double, y: Double, z: Double)Double 11 val lifted3 = lift3(divThenMul) //> lifted3 : (ch4.exx.Option[Double], ch4.exx.Option[Double], ch4.exx.Option[ 12 //| Double]) => ch4.exx.Option[Double] = <function3> 13 lifted3(Some(9.0),Some(2.0),Some(5)) //> res3: ch4.exx.Option[Double] = Some(22.5)
This shows the elegant but powerful properties of generalized programming function combinations.
Here's a look at function composition for Option: map2 combines two Options inside the Option pipeline with a function f:.
1 def map2[A,B,C](a: Option[A], b: Option[B])(f: (A,B) => C): Option[C] = (a,b) match { 2 case (None, _) => None 3 case (_, None) => None 4 case (Some(x),Some(y)) => Some(f(x,y)) 5 } 6 // owing toOption have map harmony flatMap, Can be used for comprehensiob 7 def map2_2[A,B,C](a: Option[A], b: Option[B])(f: (A,B) => C): Option[C] = { 8 for { 9 aa <- a 10 bb <- b 11 } yield f(aa,bb) 12 } 13 // abovefor comprehension can be transformed intoflatMap harmonyMap as follows: 14 def map2_1[A,B,C](a: Option[A], b: Option[B])(f: (A,B) => C): Option[C] = { 15 a flatMap(aa => b map(bb => f(aa,bb))) 16 }
Based on the implementation of the map and flatMap functions, the above shows the usage of for syntatic sugar. The following example operates on an Option inside a List, List[Option[A]]. Since List is involved, there may be a folding algorithm involved.
Here's an example: classifier for objects with a handleList[Option[A]] converted intoOption[List[A]], Data demonstration:List(Some("Hello"),Some("World")) change into Some(List("Hello","World")。 oncelist It containsNone value then returnsNone:List(Some("Hello"),None,Some("World")) Directly intoNone:
1 def sequence[A](a: List[Option[A]]): Option[List[A]] = a match { 2 case Nil => Some(Nil) 3 case h :: t => h flatMap(hh => sequence(t) map(hh :: _)) 4 } 5 def sequence_1[A](a: List[Option[A]]): Option[List[A]] = { 6 a.foldRight[Option[List[A]]](Some(Nil))((x,y) => map2(x,y)(_ :: _)) 7 }
The above uses map2: a function that combines two Options. This time an operator function to create a List is provided. Test the results.
1 val lo = List(Some("Hello"),Some("World"),Some("!")) 2 //> lo : List[ch4.exx.Some[String]] = List(Some(Hello), Some(World), Some(!)) 3 val lwn = List(Some("Hello"),None,Some("World"),Some("!")) 4 //> lwn : List[Product with Serializable with ch4.exx.Option[String]] = List(S 5 //| ome(Hello), None, Some(World), Some(!)) 6 7 8 sequence(lo) //> res0: ch4.exx.Option[List[String]] = Some(List(Hello, World, !)) 9 sequence(lwn) //> res1: ch4.exx.Option[List[String]] = None
For cases involving Lists, another function, traverse, is also worth noting. Here is the design of the traverse.
1 // using recursive methods 2 def traverse[A,B](as: List[A])(f: A => Option[B]): Option[List[B]] = { 3 as match { 4 case Nil => Some(Nil) 5 case h :: t => map2(f(h),traverse(t)(f))(_ :: _) 6 } 7 } 8 // Fold with rightfoldRight 9 def traverse_1[A,B](as: List[A])(f: A => Option[B]): Option[List[B]] = { 10 as.foldRight[Option[List[B]]](Some(Nil))((h,t) => map2(f(h),t)(_ :: _)) 11 }
The function of traverse acts on all elements in List as using the function f and then generates Option[List[B]]. Look at the results of using.
1 val list = List("Hello","","World","!") //> list : List[String] = List(Hello, "", World, !) 2 traverse(list)( a => Some(a) ) //> res0: ch4.exx.Option[List[String]] = Some(List(Hello, , World, !))
OK, that's it for Option's introduction.