内容简介:Scala 的模式匹配可能是最常用到的代码片段,但是这种情况看上去有点费解。实际上,根据 scala 的
Scala 的模式匹配可能是最常用到的代码片段, match
和 case
配合使用,应该是 Scala 程序员最常写的代码片段:
v match {
case Some(str) => ...
case None => ...
}
但是 case
有时也会出现在没有 match
关键词的场合:
map foreach {case(k,v) => println(s"$k -> $v")}
这种情况看上去有点费解。实际上,根据 scala 的 语言规范 ,这是一种定义匿名函数的方式。
不使用 match
语句的一串 case
语句可以构造一个匿名函数,如下:
{ case p1 => b1 … case pn => bn }
上述表达式的类型可以是 scala.Functionk[S1,…,Sk, R]
或是 scala.PartialFunction[S1, R]
,后一种类型就是我们要讨论的偏函数。
如果期待的类型是 scala.Functionk[S1,…,Sk, R]
,则上述表达式等价于:
(x1:S1, …, xk:Sk) => (x1, …, xk) match {
case p1 => b1 … case pn => bn
}
同样等价于:
new scala.Functionk[S1,…,Sk, T] {
def apply(x1:S1,…,xk:Sk): T = (x1,…,xk) match {
case p1 => b1 … case pn => bn
}
}
如果期待的类型是 scala.PartialFunction[S1, R]
,则等价于:
new scala.PartialFunction[S, T] {
def apply(x: S): T = x match {
case p1 => b1 … case pn => bn
}
def isDefinedAt(x: S): Boolean = {
case p1 => true … case pn => true
case _ => false
}
}
使用 case
语句的方式构造匿名函数有更高的灵活性,可以充分使用模式匹配的优势,比如安全地进行类型转换,“解构”参数等。
偏函数
先来看一个简单的例子:
List(41, "cat") map { case i: Int ⇒ i + 1 }
//scala.MatchError: cat (of class java.lang.String)
List(41, "cat") collect { case i: Int ⇒ i + 1 }
//res1: List[Int] = List(42)
为什么后一条语句可以成功执行,没有抛出 MatchError
呢?我们可以看下 map
和 collect
这两个方法在定义上的区别:
//参数是一个普通函数 def map[B](f: (A) ⇒ B): List[B] //参数是一个偏函数 def collect[B](pf: PartialFunction[A, B]): List[B]
很明显,区别正是在于偏函数。所以,到底什么是偏函数呢?偏函数(partial function)是 数学 上的概念, partial 是相对于 total 而言的。我们知道,函数是定义域到值域的一种映射关系,而偏函数只是在定义域的一个子集上面存在映射关系。
例如:
def fraction(d: Int) = 42 / d
这个函数对于 d == 0
是没有意义的, fraction(0)
会抛出异常。有了偏函数,我们可以这样来实现:
val fraction = new PartialFunction[Int, Int] {
def apply(d: Int) = 42 / d
def isDefinedAt(d: Int) = d != 0
}
fraction.isDefinedAt(42)
//res2: Boolean = true
fraction.isDefinedAt(0)
//res3: Boolean = false
fraction(42)
//res4: Int = 1
fraction(0)
//java.lang.ArithmeticException: / by zero
通过 isDefinedAt
方法,我们可以判断一个偏函数对于给定的参数是否是有定义的。
我们也可以用 case
语句这样来写:
val fraction: PartialFunction[Int, Int] = {
case d: Int if d != 0 ⇒ 42 / d
}
fraction.isDefinedAt(0)
//false
目前 Scala 不能推断 case
语句构成的匿名函数的类型,必须明确指定。对于本节开始时的例子,也可以这样定义:
val incAny: PartialFunction[Any, Int] = {
case i: Int ⇒ i + 1
}
incAny.isDefinedAt(41)
//true
incAny.isDefinedAt("hello")
//false
由于 case
语句中只匹配了参数为 Int
的情况,因而该偏函数对于 string
类型的参数是没有定义的。
你可能不知道的偏函数
Seq
, Map
和 Set
在 Scala 中都是函数,因而我们可以这样使用
val pets = List("cat", "dog", "frog")
pets(0)
//cat
pets(3)
//java.lang.IndexOutOfBoundsException: 3
可以把 pets
这个函数看作在 0,1,2 上有定义,但是在 3 上没有定义。是的,Scala 中 List
和 Map
都是偏函数( Set
并不是):
pets.isDefinedAt(0) //true pets.isDefinedAt(3) //false pets.isInstanceOf[PartialFunction[_,_]] //true
你甚至可以这样写:
Seq(1, 2, 42) collect pets //Seq[java.lang.String] = List(dog, frog)
如果每次调用偏函数前都用 isDefinedAt
判断参数的合理性,这有点类似于 Java 中检查 null
值,在 Scala 中并不提倡这样。好在 PartialFunction
提供了一个 lift
方法,在没有定义时返回 None
,这样就避免了恼人的 null
值检测:
pets.lift(0)
//Option[java.lang.String] = Some(innocent)
pets.lift(42)
//Option[java.lang.String] = None
pets.lift(0) map ("I love my " + _) getOrElse ""
//java.lang.String = I love my cat
pets.lift(42) map ("I love my " + _) getOrElse ""
//java.lang.String = ""
以上所述就是小编给大家介绍的《Partial Function in Scala》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Probability and Computing
Michael Mitzenmacher、Eli Upfal / Cambridge University Press / 2005-01-31 / USD 66.00
Assuming only an elementary background in discrete mathematics, this textbook is an excellent introduction to the probabilistic techniques and paradigms used in the development of probabilistic algori......一起来看看 《Probability and Computing》 这本书的介绍吧!