Partial Function in Scala

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

内容简介: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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

跨平台桌面应用开发:基于Electron与NW.js

跨平台桌面应用开发:基于Electron与NW.js

【丹】Paul B. Jensen / Goddy Zhao / 2018-3 / 99

《跨平台桌面应用开发:基于Electron与NW.js》是一本同时介绍 Electron和 NW.js的图书,这两者是目前流行的支持使用 HTML、CSS 和 JavaScript 进行桌面应用开发的框架。书中包含大量的编码示例,而且每个示例都是五脏俱全的实用应用,作者对示例中的关键代码都做了非常详细的解释和说明,可让读者通过实际的编码体会使用这两款框架开发桌面应用的切实感受。除此之外,在内容上,......一起来看看 《跨平台桌面应用开发:基于Electron与NW.js》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换