scala学习函数式编程续case类(代码片段)

顧棟 顧棟     2022-12-22     129

关键词:

CASE CLASSES

https://docs.scala-lang.org/overviews/scala-book/case-classes.html

另一个为函数式编程提供支持的 Scala 特性是 case 类。案例类具有常规类的所有功能,等等。当编译器在类前面看到 case 关键字时,它会为您生成代码,具有以下好处:

  • 默认情况下,案例类构造函数参数是公共 val 字段,因此为每个参数生成访问器方法。
  • apply 方法是在类的伴生对象中创建的,因此您不需要使用 new 关键字来创建类的新实例。
  • 生成了一个 unapply 方法,它使您可以在匹配表达式中以更多方式使用案例类。
  • 在类中生成了一个复制方法。你可能不会在 Scala/OOP 代码中使用这个特性,但它在 Scala/FP 中一直在使用。
  • 生成了 equals 和 hashCode 方法,它们让您可以比较对象并轻松地将它们用作映射中的键。
  • 生成了默认的 toString 方法,有助于调试。

这些功能都在以下部分中进行了演示。

With apply you don’t need new

当你将一个类定义为一个 case 类时,你不必使用 new 关键字来创建一个新的实例:

scala> case class Person(name: String, relation: String)
defined class Person

// "new" not needed before Person
scala> val christina = Person("Christina", "niece")
christina: Person = Person(Christina,niece)

正如在上一课中所讨论的,这是因为在 Person 的伴生对象中生成了一个名为“apply”的方法。

No mutator methods

默认情况下,案例类构造函数参数是 val 字段,因此为每个参数生成一个 accessor 方法:

scala> christina.name
res0: String = Christina

但是,不会生成 mutator 方法:

// can't mutate the `name` field
scala> christina.name = "Fred"
<console>:10: error: reassignment to val
       christina.name = "Fred"
                  ^

因为在 FP 中你永远不会改变数据结构,所以构造函数字段默认为 val 是有道理的。

An unapply method

在上一课关于伴随对象的课程中,您看到了如何编写 unapply 方法。 case 类的一个好处是它会自动为你的类生成一个 unapply 方法,所以你不必编写一个。

为了证明这一点,想象一下你有这个特征:

trait Person 
    def name: String

然后,创建这些案例类来扩展该特征:

case class Student(name: String, year: Int) extends Person
case class Teacher(name: String, specialty: String) extends Person

因为它们被定义为 case 类——并且它们具有内置的 unapply 方法——你可以编写这样的匹配表达式:

def getPrintableString(p: Person): String = p match 
    case Student(name, year) =>
        s"$name is a student in Year $year."
    case Teacher(name, whatTheyTeach) =>
        s"$name teaches $whatTheyTeach."

注意 case 语句中的这两种模式:

case Student(name, year) =>
case Teacher(name, whatTheyTeach) =>

这些模式之所以有效,是因为 StudentTeacher 被定义为具有类型签名符合特定标准的 unapply 方法的案例类。 从技术上讲,这些示例中显示的特定类型的模式匹配称为构造函数模式

Scala 标准是unapply方法返回包含在Option中的元组中的case 类构造函数字段。 解决方案的“元组”部分已在上一课中展示。

为了展示该代码是如何工作的,创建一个 StudentTeacher 的实例:

val s = Student("Al", 1)
val t = Teacher("Bob Donnan", "Mathematics")

接下来,当您使用这两个实例调用 getPrintableString 时,REPL 中的输出如下所示:

scala> getPrintableString(s)
res0: String = Al is a student in Year 1.

scala> getPrintableString(t)
res1: String = Bob Donnan teaches Mathematics.

关于unapply方法和提取器的所有内容对于这样的介绍性书籍来说有点高级,但是因为案例类是一个重要的FP主题,所以最好覆盖它们,而不是跳过它们。

copy method

case 类还有一个自动生成的 copy 方法,当你需要执行 a) 克隆对象和 b) 在克隆过程中更新一个或多个字段的过程时,它非常有用。 例如,这就是 REPL 中的流程:

scala> case class BaseballTeam(name: String, lastWorldSeriesWin: Int)
defined class BaseballTeam

scala> val cubs1908 = BaseballTeam("Chicago Cubs", 1908)
cubs1908: BaseballTeam = BaseballTeam(Chicago Cubs,1908)

scala> val cubs2016 = cubs1908.copy(lastWorldSeriesWin = 2016)
cubs2016: BaseballTeam = BaseballTeam(Chicago Cubs,2016)

如图所示,当您使用 copy 方法时,您所要做的就是提供要在克隆过程中修改的字段的名称。

因为您从不改变 FP 中的数据结构,这就是您从现有实例创建类的新实例的方式。 此过程可称为“复制时更新”。

equals and hashCode methods

Case 类也有自动生成的 equalshashCode 方法,因此可以比较实例:

scala> case class Person(name: String, relation: String)
defined class Person

scala> val christina = Person("Christina", "niece")
christina: Person = Person(Christina,niece)

scala> val hannah = Person("Hannah", "niece")
hannah: Person = Person(Hannah,niece)

scala> christina == hannah
res1: Boolean = false

这些方法还让您可以轻松地在集合和地图等集合中使用您的对象。

toString methods

最后,case 类还有一个很好的默认 toString 方法实现,这至少在调试代码时很有帮助:

scala> christina
res0: Person = Person(Christina,niece)

The biggest advantage

虽然所有这些特性都对函数式编程有很大好处,但正如他们在书中所写,Scala 编程 (Odersky、Spoon 和 Venners),“案例类的最大优势是它们支持模式匹配。” 模式匹配是 FP 语言的一大特性,Scala 的 case 类提供了一种在匹配表达式等领域实现模式匹配的简单方法。

CASE OBJECTS

https://docs.scala-lang.org/overviews/scala-book/case-objects.html

在我们进入case对象之前,我们应该提供一些关于“常规”Scala对象的背景知识。 正如我们在本书前面提到的,当你想创建一个单例对象时,你可以使用 Scala 的“对象”。 正如文档所述,“与类的单个实例无关的方法和值属于单例对象,表示为 使用关键字object而不是class。”

一个常见的例子是当你创建一个“工具”对象时,比如这个:

object PizzaUtils 
    def addTopping(p: Pizza, t: Topping): Pizza = ...
    def removeTopping(p: Pizza, t: Topping): Pizza = ...
    def removeAllToppings(p: Pizza): Pizza = ...

Or this one:

object FileUtils 
    def readTextFileAsString(filename: String): Try[String] = ...
    def copyFile(srcFile: File, destFile: File): Try[Boolean] = ...
    def readFileToByteArray(file: File): Try[Array[Byte]] = ...
    def readFileToString(file: File): Try[String] = ...
    def readFileToString(file: File, encoding: String): Try[String] = ...
    def readLines(file: File, encoding: String): Try[List[String]] = ...

这是使用 Scala object 结构的常用方法。

Case objects

case object 就像一个 object,但就像一个 case 类比一个普通类具有更多的特征一样,一个 case 对象比一个普通对象具有更多的特征。 其特点包括:

  • 它是可序列化的
  • 它有一个默认的 hashCode 实现
  • 它有一个改进的toString 实现

由于这些特性,case 对象主要用于两个地方(而不是常规对象):

  • 创建枚举时
  • 为要在其他对象之间传递的“消息”创建容器时 (such as with the Akka actors library)

Creating enumerations with case objects

正如我们在本书前面所展示的,您可以像这样在 Scala 中创建枚举:

sealed trait Topping
case object Cheese extends Topping
case object Pepperoni extends Topping
case object Sausage extends Topping
case object Mushrooms extends Topping
case object Onions extends Topping

sealed trait CrustSize
case object SmallCrustSize extends CrustSize
case object MediumCrustSize extends CrustSize
case object LargeCrustSize extends CrustSize

sealed trait CrustType
case object RegularCrustType extends CrustType
case object ThinCrustType extends CrustType
case object ThickCrustType extends CrustType

然后在您的代码中稍后使用这些枚举:

case class Pizza (
    crustSize: CrustSize,
    crustType: CrustType,
    toppings: Seq[Topping]
)

Using case objects as messages

案例对象派上用场的另一个地方是当您想对“消息”的概念进行建模时。 例如,假设您正在编写一个类似于 Amazon 的 Alexa 的应用程序,并且您希望能够传递“说话”消息,例如“说出所附的文本”、“停止说话”、“暂停”和“继续” 。” 在 Scala 中,您可以为这些消息创建单例对象,如下所示:

case class StartSpeakingMessage(textToSpeak: String)
case object StopSpeakingMessage
case object PauseSpeakingMessage
case object ResumeSpeakingMessage

请注意,StartSpeakingMessage 被定义为一个 case class 而不是 case object。 这是因为 case 对象不能有任何构造函数参数。

鉴于这些消息,如果 Alexa 是使用 Akka 库编写的,您会在“speak”类中找到这样的代码:

class Speak extends Actor 
  def receive = 
    case StartSpeakingMessage(textToSpeak) =>
        // code to speak the text
    case StopSpeakingMessage =>
        // code to stop speaking
    case PauseSpeakingMessage =>
        // code to pause speaking
    case ResumeSpeakingMessage =>
        // code to resume speaking
  

这是在 Scala 应用程序中传递消息的一种很好的、安全的方式。

FUNCTIONAL ERROR HANDLING IN SCALA

https://docs.scala-lang.org/overviews/scala-book/functional-error-handling.html

因为函数式编程就像代数,所以没有空值或异常。 但是当然,当您尝试访问已关闭的服务器或丢失的文件时,您仍然会遇到异常,那么您该怎么办? 本课演示了 Scala 中函数式错误处理的技术。

Option/Some/None

我们已经演示了在 Scala 中处理错误的一种技术:名为OptionSomeNone的三个类。 不是编写像 toInt 这样的方法来抛出异常或返回空值,而是声明该方法返回一个 Option,在这种情况下是一个 Option[Int]

def toInt(s: String): Option[Int] = 
    try 
        Some(Integer.parseInt(s.trim))
     catch 
        case e: Exception => None
    

稍后在您的代码中,您使用 matchfor 表达式处理来自 toInt 的结果:

toInt(x) match 
    case Some(i) => println(i)
    case None => println("That didn't work.")


val y = for 
    a <- toInt(stringA)
    b <- toInt(stringB)
    c <- toInt(stringC)
 yield a + b + c

这些方法在 “No Null Values”课程中讨论过,所以我们不会在这里重复讨论。

Try/Success/Failure

另一个名为 TrySuccessFailure 的类的工作方式与 OptionSomeNone 类似,但具有两个不错的特性:

  • Try 使得捕获异常变得非常简单
  • Failure 包含异常

这是为使用这些类而重新编写的 toInt 方法。 首先,将类导入当前范围:

import scala.util.Try,Success,Failure

之后,这就是 toIntTry 的样子:

def toInt(s: String): Try[Int] = Try 
    Integer.parseInt(s.trim)

如您所见,这比 Option/Some/None 方法要短得多,并且可以进一步缩短为:

def toInt(s: String): Try[Int] = Try(Integer.parseInt(s.trim))

这两种方法都比 Option/Some/None 方法短得多。

REPL 演示了这是如何工作的。 一、成功案例:

scala> val a = toInt("1")
a: scala.util.Try[Int] = Success(1)

其次,这是当 Integer.parseInt 抛出异常时的样子:

scala> val b = toInt("boo")
b: scala.util.Try[Int] = Failure(java.lang.NumberFormatException: For input string: "boo")

正如该输出所示,toInt 返回的 Failure 包含失败的原因,即异常。

有很多方法可以处理 Try 的结果——包括从失败中“recover”的能力——但常见的方法仍然涉及使用 matchfor 表达式:

toInt(x) match 
    case Success(i) => println(i)
    case Failure(s) => println(s"Failed. Reason: $s")


val y = for 
    a <- toInt(stringA)
    b <- toInt(stringB)
    c <- toInt(stringC)
 yield a + b + c

请注意,当使用 for 表达式并且一切正常时,它返回包含在 Success 中的值:

scala.util.Try[Int] = Success(6)

相反,如果失败,则返回一个 Failure

scala.util.Try[Int] = Failure(java.lang.NumberFormatException: For input string: "a")

Even more …

还有其他类的工作方式类似,包括Scala库中的Either/Left/Right等第三方库,但常用Option/Some/None和Try/Success/Failure,先学好 .

你可以使用任何你喜欢的东西,但 Try/Success/Failure 通常用于处理可能抛出异常的代码——因为你几乎总是想了解异常——而 Option/Some/None 用于其他地方,例如 避免使用空值。

scala学习(函数式编程面向对象编程)(代码片段)

文章目录函数式编程基础函数编程函数定义函数参数函数至简原则高阶函数编程面向对象编程基础面向对象编程高阶面向对象编程函数式编程基础函数编程函数定义packagelearn03objectdemo01defmain(args:Array[String]):Unit=//无参、无返回... 查看详情

spark基础-scala学习(集合)(代码片段)

集合scala的集合体系结构ListLinkedListSet集合的函数式编程函数式编程综合案例:统计多个文本内的单词总数scala的集合体系结构scala中的集合体系主要包括:Iterable、Seq、Set、Map。其中Iterable是所有集合trait的根trait。这个结构与java... 查看详情

scala函数式编程函数式的数据结构下(代码片段)

前情提要Scala函数式编程指南(一)函数式思想介绍scala函数式编程(二)scala基础语法介绍Scala函数式编程(三)scala集合和函数Scala函数式编程(四)函数式的数据结构上1.List代码解析今天介绍的内容,主要是对上一篇介绍的sca... 查看详情

scala函数式编程(代码片段)

Scala函数式编程什么是函数式编程?1、函数式编程将计算视为数学上的函数计算2、函数成为了和普通的值一样的"头等公民",可以像任何其他数据类型的值一样被传递和操作函数式编程成为越来越流行的编程范式1... 查看详情

scala学习之函数式风格编程(代码片段)

...unctional-programming.htmlScala允许您以面向对象编程(OOP)风格、函数式编程(FP)风格甚至混合风格编写代码,结合使用这两种方法。本书假设您是从Java、C++或C#等OOP语言来到Scala的& 查看详情

scala函数式编程函数式的错误处理(代码片段)

前情提要Scala函数式编程指南(一)函数式思想介绍scala函数式编程(二)scala基础语法介绍Scala函数式编程(三)scala集合和函数Scala函数式编程(四)函数式的数据结构上Scala函数式编程(四)函数式的数据结构下1.面向对象的... 查看详情

scala笔记整理:函数式编程(代码片段)

[TOC]作为值传递的函数测试代码如下:packagecn.xpleaf.bigdata.p4.function/***scala中关于函数的操作*/object_01FunctionOpsdefmain(args:Array[String]):Unit=functionOps1/***作为值传递的函数*将一个函数作为值传递给另外一个函数变量的时候,约定需要在... 查看详情

spark基础-scala学习(代码片段)

函数式编程将函数赋值给变量匿名函数高阶函数高级函数的类型推断scala的常用高阶函数闭包sam转换currying函数return将函数赋值给变量scala中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量sca... 查看详情

scala函数式编程初步(高阶函数)(代码片段)

参数式函数定义一个参数是函数的函数。(完整定义=>匿名函数)objecthelloWorlddefmain(args:Array[String]):Unit=defaddxy(x:Int,y:Int):Int=x+ydefmulxy(x:Int,y:Int):Int=x*ydeffunc1(a:Int,b:Int,f:(Int,Int)=> 查看详情

spark基础学习笔记08:scala简介与安装(代码片段)

文章目录零、本讲学习目标一、Scala简介(一)Scala概述(二)函数式编程(三)Scala特性1、一切都是对象2、一切都是函数3、一切都是表达式(四)在线运行Scala二、Windows上安装Scala(一)... 查看详情

scala学习笔记-函数式编程(14)

将函数赋值给变量1//Scala中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量2//Scala的语法规定,将函数赋值给变量时,必须在函数后面加上空格和下划线34defsayHello(name:String){println("Hello,"+name)}... 查看详情

scala学习--样例类和模式匹配(代码片段)

Scala学习--样例类和模式匹配1.样例类样例类是Scala用来对对象进行模式匹配而并不用大量样板代码的方式。样例类使用case作为修饰符,其特点如下:1.添加一个跟类同名的工厂方法,用于对象构造scala>valv=Var("x")v:Var=Var(x... 查看详情

scala的函数式编程(代码片段)

Scala的函数式编程  Scala的函数式编程的特点  -高阶函数  -闭包  -模式匹配可参考:http://blog.51cto.com/14048416/2337136  -单一赋值  -延迟计算  -类型推导  -尾部调用优化 &e... 查看详情

函数式编程--为什么要学习函数式编程?(代码片段)

函数式编程(FunctionalProgramming,FP)什么是函数式编程?通过纯函数来实现一些细粒度的函数,然后把这些细粒度的函数组合成功能更强大的函数,这一过程就是函数式编程,经典函数式编程库:lodash函数式编程是编程范式之一,... 查看详情

scala基础(代码片段)

...是一门类似Java的多范式语言,集合了面向对象编程和函数式编程的特性。使用Scala语言编写Spark应用程序的考虑:1)Scala具有强大的并发性,支持函数式编程,可以更好的支持分布式系统。在大数据时代,... 查看详情

函数式编程(代码片段)

...在函数的参数中传递函数(higher-orderfunction--高阶函数)。Why学习一点新的编程范式可以有效防止老年痴呆。真的很有趣相比于过程化、面向对象,函数式书写的代码更易读,更简短。因为函数式编程是无副作用(sideeffects)的,不需... 查看详情

scala编程之惰性函数(代码片段)

一、为什么需要惰性函数惰性计算(尽可能延迟表达式求值)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来了一些好处。首先,您可以将耗时的计算推迟到绝对需要的时候。其次,您可以创造... 查看详情

scala之函数式编程(代码片段)

目录函数和方法的区别: 参数默认值: 函数至简原则---能省则省:至简原则细节匿名函数的化简:匿名函数至简原则:高阶函数:高阶函数的三种用法:(1)函数可以作为值进行传递(2&... 查看详情