Scala case语句与偏函数

Scala通过case语句提供了形式简单、功能强大的模式匹配功能。但是也许你不知道,Scala还具有一个与case语句相关的语言特性,那就是:在Scala中,被“{}”包含的一系列case语句可以被看成是一个函数字面量,它可以被用在任何普通的函数字面量适用的地方,例如被当做参数传递。 
Scala代码   收藏代码
  1. scala> val defaultValue:Option[Int] => Int = {  
  2. case  Some(x) => x  
  3. case None => 0                                }                                                                      
  4. scala> defaultValue(Some(5))                         
  5. res1: Int = 5                     

defaultValue是一个函数字面量,它的值是: 
Scala代码   收藏代码
  1. {  
  2.   case  Some(x) => x  
  3.   case None => 0                                 
  4. }                        

看懂了以上的代码,我们就不难理解在Scala的Actor中经常使用的react函数的语法形式: 
Scala代码   收藏代码
  1. react {  
  2. case (name: String, actor: Actor) => {  
  3.   actor ! getip(name)  
  4.   act()  
  5. }  
  6. case msg => {  
  7.   println("Unhandled message: "+ msg)  
  8.   act()  
  9. }  
  10. }  

react是一个函数,它接收一个函数字面量作为参数。至此我们都没有提到偏函数的概念。什么是偏函数?它与Case语句有什么关系? 

在Scala中,偏函数是具有类型PartialFunction[-A,+B]的一种函数。A是其接受的函数类型,B是其返回的结果类型。偏函数最大的特点就是它只接受和处理其参数定义域的一个子集,而对于这个子集之外的参数则抛出运行时异常。这与Case语句的特性非常契合,因为我们在使用case语句是,常常是匹配一组具体的模式,最后用“_”来代表剩余的模式。如果一一组case语句没有涵盖所有的情况,那么这组case语句就可以被看做是一个偏函数。 

case语句作为偏函数字面量: 
Scala代码   收藏代码
  1. val second:PartialFunction[List[Int],Int] = {  
  2.     case List(x::y::_) => y  
  3. }  

second函数的功能是返回一个List[Int]中的第二个值。case函数体只涵盖了当一个List的长度大于2的情况,而忽略Nil和长度为1的列表。 
Scala代码   收藏代码
  1. scala.MatchError: List(2)  
  2.         at $anonfun$1.apply(<console>:9)  
  3.         at $anonfun$1.apply(<console>:9)  
  4.         at .<init>(<console>:11)  
  5.         at .<clinit>(<console>)  
  6.         at RequestResult$.<init>(<console>:9)  
  7.         at RequestResult$.<clinit>(<console>)  
  8.         at RequestResult$scala_repl_result(<console>)  
  9.         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
  10.         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)  
  11.         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)  
  12.         at java.lang.reflect.Method.invoke(Method.java:597)  
  13.         at scala.tools.nsc.Interpreter$Request$$anonfun$loadAndRun$1$$anonfun$apply$18.apply(Interpreter.scala:981)  
  14.         at scala.tools.nsc.Interpreter$Request$$anonfun$loadAndRun$1$$anonfun$apply$18.apply(Interpreter.scala:981)  
  15.         at scala.util.contr...  
  16.   
  17. scala> second(1::2::Nil)  
  18. res8: Int = 2  

当我们试图传入一个不在偏函数的定义域范围内的参数时,抛出了异常。如果我们想在调用函数前先检查一个参数是否在定义域范围以避免抛出异常,那么可以使用偏函数的isDefinedAt方法。 
Scala代码   收藏代码
  1. scala> second.isDefinedAt(List(2,3,4))  
  2. res10: Boolean = true  

实际上,scala编译器把函数字面量: 
Scala代码   收藏代码
  1. {  
  2.     case List(x::y::_) => y  
  3. }  

编译成了如下的等价形式: 
Scala代码   收藏代码
  1. new PartialFunction[List[Int], Int] {  
  2. def apply(xs: List[Int]) = xs match {  
  3. case x :: y :: _ => y  
  4. }  
  5. def isDefinedAt(xs: List[Int]) = xs match {  
  6. case x :: y :: _ => true  
  7. case _ => false  
  8. }  
  9. }  

这种转换是一种编译期行为,我们必须把second显式的声明为PartialFunction类型,如果没有给second指定类型,那么scala编译器会把后面的一组case语句编译成Function1类型,即完整的函数。 

Tips:一组case语句要成为一个偏函数,那么它被赋予的变量必须被声明为PartionFunction[-A,+B]  

那么我们什么时候该使用偏函数?或者说偏函数给我们带来了什么好处?当我们确定我们的程序不会被传入不可处理的值时,我们就可以使用偏函数。这样万一程序被传入了不应该被传入的值,程序自动抛出异常,而不需要我们手工编写代码去抛出异常,减少了我们的代码量。 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值