译者注:原文出处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资源 scala-SDK-4.7.0-vfinal-2.12-li gz文件
包含翻译后的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 IDEA是一款著名的集成开发环境(IDE),广泛用于Java、Scala和其他 JVM 语言的开发。"scala-intellij-bin-2020.2.3.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文档,用...
包含翻译后的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 IDEA的Scala插件中都有良好的支持。通过IDE,开发者可以方便地使用这些功能,提升代码的简洁性和可读性。 对于...
此外,该插件还支持Scala的模式匹配、匿名函数、特质等特性,使得IDE能更好地理解和处理这种高度抽象的语言。 代码重构是软件开发中的重要环节,Scala插件提供了诸如提取方法、重命名变量、移动类等重构操作,确保...
在实际开发中,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
4. **测试框架支持**:Scala插件通常会集成Scala的测试框架,如ScalaTest或Specs2,使得在IDE内创建和运行测试变得方便。 5. **额外的工具和库**:可能还包括Scala相关的工具和库,例如 sbt(Scala构建工具)的集成...
包含翻译后的API文档:scala-compiler-2.11.8-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.scala-lang:scala-compiler:2.11.8; 标签:scala、lang、compiler、中英对照文档、jar包、java; 使用...
2. **模式匹配**:Scala的模式匹配功能使得处理数据结构和解析数据变得更加简单,它可以与case类和枚举类型一起使用,实现强大的数据解构。 3. **函数式编程**:Scala支持高阶函数和闭包,可以定义匿名函数,使用...
4. 找到"Scala"插件后,确认其版本号与你的IDE版本匹配。 5. 点击"Install",等待安装完成,然后重启IDE。 如果下载的zip文件中包含`readme.txt`,通常这个文件会包含关于插件的安装指南、更新日志或者使用注意事项...
标签:11、parser、scala、combinators_2、lang、modules、jar包、java、API文档、中文版; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构...
4. **文档和帮助文件**:这些文件通常包括用户指南、API参考、快速启动指南等,帮助用户更好地理解和使用Scala及IntelliJ IDEA的功能。 5. **许可证和法律文件**:包含关于软件许可、版权和使用条款的信息,确保...
Scala是一种强大的多范式编程语言,它融合了面向对象和函数式编程的概念。IntelliJ IDEA是一款广受欢迎的集成开发环境(IDE),尤其在Java和Scala开发者中非常流行。"scala-intellij-bin-2017.2.6" 是一个特定版本的...
包含翻译后的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插件,亲测可用!!!
2. **模式匹配**:Scala中的模式匹配允许开发者以一种优雅的方式处理数据结构,比如在处理集合或解析XML时。 3. **函数式编程**:Scala支持高阶函数和不可变数据结构,使得代码更加简洁,可读性更强,并且易于测试...
Scala是一种强大的多范式编程语言,它融合了函数式编程和面向对象编程的特点。IntelliJ IDEA是一款广受赞誉的Java开发集成环境,为开发者提供了高效、智能的代码编写体验。"scala-intellij-bin-0.41"是专门为...