Jade Dungeon

Scala函数式编程

异常处理

异常处理的优点:

  • 把错误处理逻辑整合集中在一起。

异常处理的缺点:

  • 破坏了引用透明,并引入了对上下文的依赖。所以替代模型的简单代换不再适用。 业界最基本的共识是异常处理应该只用于处理错误而不是流程控制。
  • 无法检查异常类型是否安全,编译器也不会强制函数的调用者处理被调用函数可能抛出 的异常。编写代码时没有处理的异常赶到程序运行时才被发现。
  • 高阶函数以函数用为参数,但是不适用对参数函数可能抛出的异常进行处理。 例:f(g())g()可能抛出各种异常,f()中应该如何处理。

用返回值类型代替抛出异常

Scala中可以把抛出的异常设置为任何的类型。比如设定为整数:

def func01(i: Int): Int = {
	try {
		val x = 42 + 5
		x + ((throw new Exception("fail!")): Int) //异常作为整数返回
	} catch {
		case e: Exception => 42
	}
}

func01(12)            // res1: Int 43

用Option表示函数无定义

如果要自己实现Scala中已经有的Option,表示函数并不对所有的参数都有定义:

sealed trait Option[+A]

// 有定义
case class Some[+A](get: A) extends Option[A]

// 无定义
case object None extends Option[Nothing]

Option基本操作

取值,如果为None则返回参数指定的default

seald trait Option[+A] {

	// .... 

def getOrElse[B>:A](default: => B): B = this match {
  case None => default
  case Some(a) => a
}

	// .... 

}

映射操作的返回类型还是Some(A)

seald trait Option[+A] {

	// .... 

def map[B](f: A => B): Option[B] = this match {
  case None => None
  case Some(a) => Some(f(a))
}

def flatMap[B](f: A => Option[B]): Option[B] =
  map(f) getOrElse None


// Of course, we can also implement `flatMap` 
// with explicit pattern matching.
def flatMap_1[B](f: A => Option[B]): Option[B] = this match {
  case None => None
  case Some(a) => f(a)
}

	// .... 

}

orElse(ob)避免对参数ob求值(除非必要的情况下):

seald trait Option[+A] {

	// .... 

def orElse[B>:A](ob: => Option[B]): Option[B] =
  this map (Some(_)) getOrElse ob

/*
Again, we can implement this with explicit pattern matching.
*/
def orElse_1[B>:A](ob: => Option[B]): Option[B] = this match {
  case None => ob
  case _ => this
}

	// .... 

}

过滤不符合的内容:

seald trait Option[+A] {

	// .... 

def filter(f: A => Boolean): Option[A] = this match {
  case Some(a) if f(a) => this
  case _ => None
}

/*
This can also be defined in terms of `flatMap`.
*/
def filter_1(f: A => Boolean): Option[A] =
  flatMap(a => if (f(a)) Some(a) else None)

	// .... 

}

Option应用场景

Option类型的链式调用,None类型的mapflatMapfilter返回的都是None。 所以链式调用中间有一步的结果为None会中断调用:

case class Employee(name: String, department: String, manager: String)

def lookupByName(name: String): Option[Employee] = name match {
	case "Joe" => Some(new Employee("Joe", "R&D Dept.", "Jade"))
	case _ => None
}

lookupByName("Joe").map(_.department)
                       //> res0: Option[String] = Some(R&D Dept.)
lookupByName("Joe").map(_.department).getOrElse("Default Dept.")
                       //> res2: String = R&D Dept.

lookupByName("Teo").map(_.department)     //> res1: Option[String] = None
lookupByName("Teo").map(_.department).getOrElse("Default Dept.")
                                          //> res3: String = Default Dept.

lookupByName("Joe").map(_.department).filter(_ != "R&D Dept."
	).getOrElse("Default Dept.") //> res8: String = Default Dept.

lookupByName("Joe").map(_.department).filter(_ != "Accounting Dept."
	).getOrElse("Default Dept.") //> res9: String = R&D Dept.

lookupByName("Joe").flatMap(_ match {case t => Some(t)})
                      //> res4: Option[Employee] = Some(Employee(...))
lookupByName("Joe").flatMap(_.department match {case t => Some(t)})
                      //> res5: Option[String] = Some(R&D Dept.)
lookupByName("Teo").flatMap(_ match {case t => Some(t)})
                      //> res6: Option[Employee] = None
lookupByName("Teo").flatMap(_.department match {case t => Some(t)})
                      //> res7: Option[String] = None

使用flatMap实现方差(variance)函数。如果一个序列的平均值是\(m\), 方差是对序列中的第个元素\(x\)进行\(math.pow(x-m, 2)\)。

方差调用的多个阶段中都有可能失败,运算是遇到第一个失败就中止, 因为None.flatMap(f)会返回None,不再调用函数f

// 平均值
def mean(xs: Seq[Double]): Option[Double] =
  if (xs.isEmpty) None
  else Some(xs.sum / xs.length)

def variance(xs: Seq[Double]): Option[Double] =
  mean(xs) flatMap (m => mean(xs.map(x => math.pow(x - m, 2))))

把Option转回异常

必要的情况下,把Option转回为异常处理也不是不可以:

result.getOrElse(throw new Exception("Fail"))

Option与函数提升

用Option有很多好处,但是很多函数的参数与返回值并不是Option类型的。 lift函数可以把函数的参数与返回值都包装为Option,即把f: A => B 包装为g: Option[A] => Option[B]

lift函数返回一个新的函数作为返回值,这个新函数以Option[A]为参数, 这样Option[A].map(f)的返回值类型就是Option[B]

def lift2[A, B](f: A => B): Option[A] => Option[B] = {
	val newFunction = (t: Option[A]) => { t map f }
	newFunction
}                         //> lift2: [A, B](f: A => B)Option[A] => Option[B]

// 简写一下:
def lift[A, B](f: A => B): Option[A] => Option[B] = _ map f
                          //> lift: [A, B](f: A => B)Option[A] => Option[B]

val abs = lift(math.abs) //> absO  : Option[Int] => Option[Int] 

结合异常与提升Option

让异常返回None

def parseStringAge(age: String): Int = age.toInt
                          //> parseStringAge: (age: String)Int

parseStringAge("33")      //> res10: Int = 33

parseStringAge("Hello")   //> java.lang.NumberFormatException: ........ 

def Try[A](a: => A): Option[A] = try Some(a) catch {
	case e: Exception => None   // 这样会丢弃异常信息,以后再改进
}                               //> Try: [A](a: => A)Option[A]

def parseStringAge2(age: String): Option[Int] = {
		Try(age.toInt)
}                               //> parseStringAge2: (age: String)Option[Int]

parseStringAge2("33")           //> res11: Option[Int] = Some(33)
parseStringAge2("hello")        //> res12: Option[Int] = None
parseStringAge2("world")        //> res13: Option[Int] = None

提升有多个参数的函数

lift只能提升单个参数的函数,如果函数有多个参数就不能通过lift提升:

/* 根据年龄与超速罚单的数量评价保险的评分 */
def insuranceRateQuote(age: Int, numberOfSpeedingTickets: Int): Double = ???
   //> insuranceRateQuote: (age: Int, numberOfSpeedingTickets: Int)Double

要通过新的工具函数map2把两个参数都提升为Option

def map2[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] =
  a flatMap (aa => b map (bb => f(aa, bb)))     
//> map2: [A, B, C](a: Option[A], b: Option[B])(f: (A, B) => C)Option[C]
                                                
def parseInsuranceRateQuote(age: String, 
		numberOfSpeedingTickets: String): Option[Double] = 
{
	val optAge:  Option[Int] = Try(age.toInt)
	val optTick: Option[Int] = Try(numberOfSpeedingTickets.toInt)
	map2(optAge, optTick)(insuranceRateQuote)
}
//> parseInsuranceRateQuote: (age: String, 
 //|                numberOfSpeedingTickets: String)Option[Double]

合并多个Option为单个Option的列表

sequence函数把含有多个Option的列表List[Option[A]]结合为一个Option[List[A]] 如果原来的列表里有一个为None,那么sequence函数返回的结果也是None

def sequence[A](lst: List[Option[A]]): Option[List[A]] = lst match {
	case Nil => Some(Nil)
	case h :: t => h flatMap (hh => sequence(t) map (hh :: _))
}     
//> sequence: [A](lst: List[Option[A]])Option[List[A]]

还可以通过 foldRightmap2来实现sequence ,在foldRight里要注明类型为 [Option[List[A]]]不然会被类型推导错误地推导为Some[Nil.type]

def sequence_1[A](a: List[Option[A]]): Option[List[A]] =
	a.foldRight[Option[List[A]]](Some(Nil))((x, y) => map2(x, y)(_ :: _))
//> sequence_1: [A](a: List[Option[A]])Option[List[A]]

sequence仅仅只适合用来合并列表为单个option,如果涉及到map操作,就要遍历两次, 例如下面的字符串转整数,map的时候遍历一遍lst,合并Option时又要遍历一遍:

def parseInts(lst: List[String]): Option[List[Int]] = 
		sequence(lst map (i => Try(i.toInt)))

定义新的工具traverse直接在一个遍历过程中map并合并Option:

def traverse[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] = a match {
		case Nil => Some(Nil)
		case h :: t => map2(f(h), traverse(t)(f))(_ :: _)
}                                 
//> traverse: [A, B](a: List[A])(f: A => Option[B])Option[List[B]]

def traverse_1[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] = {
	a.foldRight[Option[List[B]]](Some(Nil))((h, t) => map2(f(h), t)(_ :: _))
}
//> traverse_1: [A, B](a: List[A])(f: A => Option[B])Option[List[B]]

def sequenceViaTraverse[A](a: List[Option[A]]): Option[List[A]] = {
	traverse(a)(x => x)               
}
//> sequenceViaTraverse: [A](a: List[Option[A]])Option[List[A]]

Option提升与for推导

因为提升非常常见,所以scala自带的for推导已经自带提升效果:

def map2[A, B, C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] =
	a flatMap (aa => 
			b map (bb => 
				f(aa, bb)))

任何的mapflatMap操作都可以替换为for推导:

def map2[A, B, C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] =
	for {
		aa <- a
		bb <- b
	} yield f(aa, bb)

yield关键字可以使用<-符号左边绑定的值,编译器会把这些绑定操作的语法糖 转换为map调用。

Either数据类型

EitherOption一样可以用来替换异常或失败,区别是Option只能用None表示没有值 。不能传递更多关于产生错误原因的信息。Either可以带有更多关于失败的详细信息:

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

父类Either的泛型E代表错误信息的类型,A代表有结果的结果类型。 两个子类LeftRight分别代表错误和正确。

// 求平均值,错误时返回的是相关错误信息的字符串:
def mean(xs: IndexedSeq[Double]): Either[String, Double] = 
  if (xs.isEmpty) 
    Left("mean of empty list!")
  else 
    Right(xs.sum / xs.length)

// 还可以把堆栈调用信息放进去
def safeDiv(x: Int, y: Int): Either[Exception, Int] = 
  try Right(x / y)
  catch { case e: Exception => Left(e) }

有时为了方便,当结果有两种类型的数据又不值得特地定义一个新的类的时候, 也常常用Either应付一下。

把之前的Option类型的Try函数改为用Either类型:

def Try[A](a: => A): Either[Exception, A] =
  try Right(a)
  catch { case e: Exception => Left(e) }

常用函数的实现:

sealed trait Either[+E, +A] {

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

	// 对Right进行映射时,必须把Left的类型提升为父类型以满足`E+`的型变
	def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] =
		this match {
			case Right(a) => f(a)
			case Left(e) => Left(e)
		}
		
	// 对Right进行映射时,必须把Left的类型提升为父类型以满足`E+`的型变
	def orElse[EE >: E, AA >: A](b: => Either[EE, AA]): Either[EE, AA] =
		this match {
			case Right(a) => Right(a)
			case Left(_) => b
		}
		
	def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C): Either[EE, C] = 
		for { a <- this; b1 <- b } yield f(a, b1)
	
}

有了这些定义Either也可以使用for推导:

def parseInsuranceRateQuote(age: String, 
		numberOfSpeedingTickets: String): Either[Exception, Double] = 
{
	for {
		a       <- Try(age.toInt)
		tickets <- Try(numberOfSpeedingTickets.toInt)
	} yield insuranceRateQuote(a, tickets)
}

通过Either还可以效验数据,比如检测构造函数是否有效:

sealed class Name(val value: String)
sealed class Age (val value: Int)
case   class Person(name: Name, age: Age)

def mkName(name: String): Either[String, Name] =
	if (name == "" || name == null) Left("Name is Empty.")
	else Right(new Name(name))

def mkAge(age: Int): Either[String, Age] =
	if (age < 0) Left("Age is out of range.")
	else Right(new Age(age))

def mkPerson(name: String, age: Int): Either[String, Person] =
	mkName(name).map2(mkAge(age))(Person(_, _))