Partial Function in Scala

栏目: Scala · 发布时间: 5年前

内容简介:Scala 的模式匹配可能是最常用到的代码片段,但是这种情况看上去有点费解。实际上,根据 scala 的

Scala 的模式匹配可能是最常用到的代码片段, matchcase 配合使用,应该是 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 呢?我们可以看下 mapcollect 这两个方法在定义上的区别:

//参数是一个普通函数
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 , MapSet 在 Scala 中都是函数,因而我们可以这样使用

val pets = List("cat", "dog", "frog")
pets(0)
//cat
pets(3)
//java.lang.IndexOutOfBoundsException: 3

可以把 pets 这个函数看作在 0,1,2 上有定义,但是在 3 上没有定义。是的,Scala 中 ListMap 都是偏函数( 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程序员修炼之道

Java程序员修炼之道

[英] Benjamin J. Evans、[荷兰] Martijn Verburg / 吴海星 / 人民邮电出版社 / 2013-7 / 89.00元

本书分为四部分,第一部分全面介绍Java 7 的新特性,第二部分探讨Java 关键编程知识和技术,第三部分讨论JVM 上的新语言和多语言编程,第四部分将平台和多语言编程知识付诸实践。从介绍Java 7 的新特性入手,本书涵盖了Java 开发中最重要的技术,比如依赖注入、测试驱动的开发和持续集成,探索了JVM 上的非Java 语言,并详细讲解了多语言项目, 特别是涉及Groovy、Scala 和Cl......一起来看看 《Java程序员修炼之道》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具