内容简介: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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java程序员修炼之道
[英] Benjamin J. Evans、[荷兰] Martijn Verburg / 吴海星 / 人民邮电出版社 / 2013-7 / 89.00元
本书分为四部分,第一部分全面介绍Java 7 的新特性,第二部分探讨Java 关键编程知识和技术,第三部分讨论JVM 上的新语言和多语言编程,第四部分将平台和多语言编程知识付诸实践。从介绍Java 7 的新特性入手,本书涵盖了Java 开发中最重要的技术,比如依赖注入、测试驱动的开发和持续集成,探索了JVM 上的非Java 语言,并详细讲解了多语言项目, 特别是涉及Groovy、Scala 和Cl......一起来看看 《Java程序员修炼之道》 这本书的介绍吧!