`
thomassun
  • 浏览: 27649 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Scala新手指南中文版 - 第四篇 Pattern Matching Anonymous Functions(模式匹配匿名函数)

阅读更多

译者注:原文出处http://danielwestheide.com/blog/2012/12/12/the-neophytes-guide-to-scala-part-4-pattern-matching-anonymous-functions.html,翻译:Thomas

 

前一篇中,我大致介绍了在Scala里使用模式的几种方式,顺便提到了模式还可用在匿名函数中。此篇我们将会深入的理解这个话题,来看看定义匿名函数的不同方式。

如果你参加过Coursera的Scala课程(译者注:还没有参加过?赶紧报名吧)或者已经用Scala一阵子了,你一定已经时不时的写过些匿名函数了。比如,为了搜索索引,你可能想要把一个歌名列表转成小写,你可以写一个匿名函数然后丢给list的map方法:

 

val songTitles = List("The White Hare", "Childe the Hunter", "Take no Rogues")
songTitles.map(t => t.toLowerCase)
或者,你想要代码简短些,你可以用Scala的占位符语法写出下面的规范代码:

 

 

songTitle.map(_.toLowerCase)
这看上去没啥特别。那么我们来看看这种语法如何用在稍微不同的场景里:我们有一个pair的序列,每一个pair表示一个词以及它在文中出现的频次。我们想要过滤出那些超过或不到一定频次的单词,仅仅返回单词就可以了,不需要单词的词频。我们来写一个函数wordsWithoutOutliers(wordFrequencies: Seq[(String, Int)]): Seq[String]。

 

我们的第一种解决方法是用filter和map方法,用我们熟悉的方式传递一个匿名函数:

 

val wordFrequencies = ("habitual", 6) :: ("and", 56) :: ("consuetudinary", 2) ::
  ("additionally", 27) :: ("homely", 5) :: ("society", 13) :: Nil
def wordsWithoutOutliers(wordFrequencies: Seq[(String, Int)]): Seq[String] =
  wordFrequencies.filter(wf => wf._2 > 3 && wf._2 < 25).map(_._1)
wordsWithoutOutliers(wordFrequencies) // List("habitual", "homely", "society")
这方法存在几个问题,首先是可读性 - 访问tuple的字段我看着都要吐了(_2是啥,_._1又是啥哦)。 只要能够解构这些Tuple,我们的代码就会看着舒服些,当然也会可读些。

 

谢天谢地,Scala提供一种定义匿名函数的替代方法:模式匹配匿名函数是由一些case组成的以花括号包含的代码块作为函数体,不过代码块前不带match关键字。我们用此方式来来重写下函数:

 

def wordsWithoutOutliers(wordFrequencies: Seq[(String, Int)]): Seq[String] =
  wordFrequencies.filter { case (_, f) => f > 3 && f < 25 } map { case (w, _) => w }
在这个例子中,每个匿名函数只有一个case,因为情况不复杂 - 我们只是单纯的解构一个我们在编译时已经明确的数据结构,所以不会出错。这是使用模式匹配匿名函数的最常用的用法。

 

如果你把两个匿名函数赋给常量,你就会清楚知道它们的实际类型:

 

val predicate: (String, Int) => Boolean = { case (_, f) => f > 3 && f < 25 }
val transformFn: (String, Int) => String = { case (w, _) => w }
请注意你必须提供参数的数据类型,因为Scala编译器没有足够信息来推断模式匹配匿名函数的类型。

 

没人能够阻止你实现非常复杂的case序列,不过,如果你定义类似的匿名函数并且将它们传递给其它函数,像我们的例子一样,你必须要确保针对所有可能的输入,你的匿名函数中必须有一个case能被匹配到并返回值,否则运行时可能抛出MatchError

偏函数

有时候,你仅仅需要能处理特定输入数据范围的的参数函数,事实上这种函数能帮助我们排除目前实现的wordsWithoutOutliers函数的另一个问题:我们首先过滤输入的序列然后map被过滤出来的元素,如果我们可以归结成一个方案使我们只需要遍历序列一次,就能让函数消耗更少的CPU周期,提高执行效率,同时让代码更加短小易读。

如果你看过Scala集合的API,你也许会注意到有一个叫做collect的方法,在一个Seq[A]数据下,它的形式是

 

def collect[B](pf: PartialFunction[A, B])
该方法为Seq里的每个元素执行一次偏函数并返回一个新的序列 - 这偏函数同时过滤和map序列。

 

那么什么是偏函数呢?简单来说,它是一个明确只处理特定数据范围的一元函数,调用者可以检查它是否有定义某个数据的处理逻辑。

为此,PartialFunction提供了一个isDefinedAt方法。实际上,PartialFunction[-A,+B]类型扩展了(A)=>B类型(也可以写作Function1[A,B]),一个模式匹配匿名函数的类型总是PartialFunction。

源于这样的层级关系,在调用一个需要输入Function1作为参数的函数时(如map或filter),给它提供一个模式匹配匿名函数也是完全可以的,只要这函数能够处理所有输入数,也即,总是会匹配一种情形。

而collect方法指定需要传递一个PartialFunction[A, B]函数,这函数或许不会为所有的输入数据定义处理逻辑。在collect方法内部,它会逐个检查序列中的每个元素,通过调用偏函数的isDefinedAt来确认偏函数里是否定义了相关逻辑,如果isDefinedAt返回false,该元素就会被跳过,否则把元素传递给偏函数,偏函数的返回值被加入collect返回的序列中。

我们先来定义一个偏函数,用来重构wordsWithoutOutliers方法以使用collect函数:

 

val pf: PartialFunction[(String, Int), String] = {
  case (word, freq) if freq > 3 && freq < 25 => word
}

 

我们为case子句添加了一个守卫分句,所以函数将不会处理不在指定范围的元素。

除了用上面的模式匹配匿名函数的语法外,我们还可以通过显式的继承PartialFunction接口来实现这个函数:

 

val pf = new PartialFunction[(String, Int), String] {
  def apply(wordFrequency: (String, Int)) = wordFrequency match {
    case (word, freq) if freq > 3 && freq < 25 => word
  }
  def isDefinedAt(wordFrequency: (String, Int)) = wordFrequency match {
    case (word, freq) if freq > 3 && freq < 25 => true
    case _ => false
  }
}
显然,你可能更愿意用简洁的模式匹配匿名函数的写法。

 

如果我们将这偏函数传递给map方法,编译是可以通过的(译者注:因为偏函数是Function1的子类),不过将会抛出运行时错误MatchError,因为由于守卫分句的存在,这个偏函数没有为所有可能的输入值进行处理:

 

  wordFrequencies.map(pf) // 将会抛出MatchError
 当我们将这个偏函数传递给collect方法时,它将会如我们所愿的执行,同时进行过滤和map:

 

 

  wordFrequencies.collect(pf) // List("habitual", "homely", "society")
这个结果和我们目前实现的wordsWithoutOutliers方法输出是一致的。让我们来改写下wordsWithoutOutliers函数
def wordsWithoutOutliers(wordFrequencies: Seq[(String, Int)]): Seq[String] =
  wordFrequencies.collect { case (word, freq) if freq > 3 && freq < 25 => word }
偏函数还有其他一些有用的特性,例如,他们提供了函数链接,用规整的函数式的方法来实现OOP里的所谓责任链模式。这个话题会在后续篇章里讲解,在我们搞定函数拼装问题后。

偏函数也是许多Scala类库和API的要点。例如,Akka的actor定义如何处理发送给它的消息时就是通过定义偏函数的方式。因而,理解偏函数这个概念对理解许多Scala的类库和API显得非常重要。

总结

在此章节中,我们知道了用不同方式定义匿名函数,即一组case语句的方法,它提供了一种更显精简的途径进行漂亮的结构。此外,我们还涉及了偏函数的话题,通过一个简单的使用场景演示了它们强大的用处。

在下一章节,我会带大家深挖一下无所不在的Option类型,解释为什么需要这个类型,如何最好的利用它。

有任何问题或反馈,请让我知道(译者注:有任何翻译错误或问题的讨论,也可以联系我:Thomas

作者:Daniel Westheide,2012.12.12

 
0
0
分享到:
评论

相关推荐

    scala资源 scala-SDK-4.7.0-vfinal-2.12-li gz文件

    scala资源 scala-SDK-4.7.0-vfinal-2.12-li gz文件

    scala-xml_2.12-1.0.6-API文档-中文版.zip

    包含翻译后的API文档:scala-xml_2.12-1.0.6-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.scala-lang.modules:scala-xml_2.12:1.0.6; 标签:scala、lang、modules、xml、中文文档、jar包、java; 使用方法...

    scala-intellij-bin-2020.2.3.zip

    Scala是一种强大的多范式编程语言,它融合了面向对象和函数式编程的概念。IntelliJ IDEA是一款著名的集成开发环境(IDE),广泛用于Java、Scala和其他 JVM 语言的开发。"scala-intellij-bin-2020.2.3.zip" 是一个...

    flink-scala_2.12-1.14.3-API文档-中文版.zip

    包含翻译后的API文档:flink-scala_2.12-1.14.3-javadoc-API文档-中文(简体)版.zip 对应Maven信息:groupId:org.apache.flink,artifactId:flink-scala_2.12,version:1.14.3 使用方法:解压翻译后的API文档,用...

    scala-compiler-2.12.7-API文档-中文版.zip

    包含翻译后的API文档:scala-compiler-2.12.7-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.scala-lang:scala-compiler:2.12.7; 标签:scala、lang、compiler、中文文档、jar包、java; 使用方法:解压翻译...

    scala-intellij-bin-2016.3.1.zip

    在函数式编程方面,Scala提供了高阶函数、柯里化、模式匹配、不可变数据结构等特性,这些在IntelliJ IDEA的Scala插件中都有良好的支持。通过IDE,开发者可以方便地使用这些功能,提升代码的简洁性和可读性。 对于...

    scala-intellij-bin-2019.1.2.zip

    此外,该插件还支持Scala的模式匹配、匿名函数、特质等特性,使得IDE能更好地理解和处理这种高度抽象的语言。 代码重构是软件开发中的重要环节,Scala插件提供了诸如提取方法、重命名变量、移动类等重构操作,确保...

    scala-intellij-bin-2017.2.13

    在实际开发中,Scala的特性如类型推断、模式匹配、高阶函数等,都能在IDE的辅助下得到更好的理解和应用。例如,类型推断使得开发者无需显式声明变量类型,而模式匹配则简化了数据解析和处理的过程。高阶函数则允许将...

    scala-SDK-4.7.0-vfinal-2.12-linux.gtk.x86_64.tar.gz

    scala-SDK-4.7.0-vfinal-2.12-linux.gtk.x86_64.tar.gz scala-SDK-4.7.0-vfinal-2.12-linux.gtk.x86_64.tar.gz

    scala-intellij-bin-2016.3.9

    4. **测试框架支持**:Scala插件通常会集成Scala的测试框架,如ScalaTest或Specs2,使得在IDE内创建和运行测试变得方便。 5. **额外的工具和库**:可能还包括Scala相关的工具和库,例如 sbt(Scala构建工具)的集成...

    scala-compiler-2.11.8-API文档-中英对照版.zip

    包含翻译后的API文档:scala-compiler-2.11.8-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.scala-lang:scala-compiler:2.11.8; 标签:scala、lang、compiler、中英对照文档、jar包、java; 使用...

    scala-intellij-bin-2021.3.6.zip

    2. **模式匹配**:Scala的模式匹配功能使得处理数据结构和解析数据变得更加简单,它可以与case类和枚举类型一起使用,实现强大的数据解构。 3. **函数式编程**:Scala支持高阶函数和闭包,可以定义匿名函数,使用...

    scala插件 scala-intellij-bin-2018.3.5.zip scala-intellij-bin-2018.3.6.zip

    4. 找到"Scala"插件后,确认其版本号与你的IDE版本匹配。 5. 点击"Install",等待安装完成,然后重启IDE。 如果下载的zip文件中包含`readme.txt`,通常这个文件会包含关于插件的安装指南、更新日志或者使用注意事项...

    scala-parser-combinators-2.11-1.0.4-API文档-中文版.zip

    标签:11、parser、scala、combinators_2、lang、modules、jar包、java、API文档、中文版; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构...

    scala-intellij-bin-2023.1.15.zip

    4. **文档和帮助文件**:这些文件通常包括用户指南、API参考、快速启动指南等,帮助用户更好地理解和使用Scala及IntelliJ IDEA的功能。 5. **许可证和法律文件**:包含关于软件许可、版权和使用条款的信息,确保...

    scala-intellij-bin-2017.2.6

    Scala是一种强大的多范式编程语言,它融合了面向对象和函数式编程的概念。IntelliJ IDEA是一款广受欢迎的集成开发环境(IDE),尤其在Java和Scala开发者中非常流行。"scala-intellij-bin-2017.2.6" 是一个特定版本的...

    scala-reflect-2.11.8-API文档-中英对照版.zip

    包含翻译后的API文档:scala-reflect-2.11.8-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.scala-lang:scala-reflect:2.11.8; 标签:reflect、scala、lang、jar包、java、API文档、中英对照版; ...

    scala-intellij-bin-2018.3.2.zip

    scala-intellij-bin-2018.3.2.zip插件,亲测可用!!!scala-intellij-bin-2018.3.2.zip插件,亲测可用!!!scala-intellij-bin-2018.3.2.zip插件,亲测可用!!!

    scala-intellij-bin-2019.2.28.zip

    2. **模式匹配**:Scala中的模式匹配允许开发者以一种优雅的方式处理数据结构,比如在处理集合或解析XML时。 3. **函数式编程**:Scala支持高阶函数和不可变数据结构,使得代码更加简洁,可读性更强,并且易于测试...

    scala-intellij-bin-0.41

    Scala是一种强大的多范式编程语言,它融合了函数式编程和面向对象编程的特点。IntelliJ IDEA是一款广受赞誉的Java开发集成环境,为开发者提供了高效、智能的代码编写体验。"scala-intellij-bin-0.41"是专门为...

Global site tag (gtag.js) - Google Analytics