https://docs.scala-lang.org/overviews/scala-book/functional-programming.html
Scala 允许您以面向对象编程 (OOP) 风格、函数式编程 (FP) 风格甚至混合风格编写代码,结合使用这两种方法。本书假设您是从 Java、C++ 或 C# 等 OOP 语言来到 Scala 的,因此除了涵盖 Scala 类之外,本书中没有任何关于 OOP 的特殊部分。但是由于 FP 风格对于许多开发人员来说仍然相对较新,我们将在接下来的几课中简要介绍 Scala 对 FP 的支持。
函数式编程是一种强调仅使用纯函数和不可变值编写应用程序的编程风格。正如 Alvin Alexander 在 Functional Programming, Simplified 中所写,与其使用这种描述,不如说函数式程序员非常强烈地希望将他们的代码视为数学——将他们的函数组合视为一系列代数方程.在这方面,您可以说函数式程序员喜欢将自己视为数学家。这是导致他们只使用纯函数和不可变值的驱动力,因为这就是你在代数和其他形式的数学中使用的东西。
函数式编程是一个很大的主题,没有简单的方法可以将整个主题浓缩到这本小书中,但在接下来的课程中,我们将带您体验 FP,并展示 Scala 为开发人员编写函数式编程提供的一些工具代码。
PURE FUNCTIONShttps://docs.scala-lang.org/overviews/scala-book/pure-functions.html
Scala 提供的第一个帮助您编写函数式代码的功能是编写纯函数的能力。 在 Functional Programming, Simplified 中,Alvin Alexander 定义了一个像这样的纯函数:
- 函数的输出仅取决于其输入变量
- 它不会改变任何隐藏状态
- 没有任何“后门”:不从外部读取数据(包括控制台、Web 服务、数据库、文件等),也不向外部写入数据
作为这个定义的结果,任何时候你用相同的输入值调用一个纯函数,你总是会得到相同的结果。 例如,您可以使用输入值“2”无限次调用“double”函数,并且始终会得到结果“4”。
Examples of pure functions鉴于纯函数的定义,正如您想象的那样, scala.math._ 包中的这些方法是纯函数:
abs
ceil
max
min
这些 Scala String
方法也是纯函数:
isEmpty
length
substring
Scala 集合类上的许多方法也可以用作纯函数,包括 drop
、filter
和 map
。
相反,以下函数是不纯的,因为它们违反了定义。
集合类上的 foreach
方法是不纯的,因为它只用于它的副作用,例如打印到 STDOUT。
A great hint that foreach
is impure is that it’s method signature declares that it returns the type Unit
. Because it returns nothing, logically the only reason you ever call it is to achieve some side effect. Similarly, any method that returns Unit
is going to be an impure function.
与日期和时间相关的方法,如 getDayOfWeek、getHour 和 getMinute 都是不纯的,因为它们的输出取决于输入参数以外的其他东西。 在这些示例中,他们的结果依赖于某种形式的隐藏 I/O,隐藏输入。
一般而言,不纯函数执行以下一项或多项操作:
- 读取隐藏的输入,即它们访问未作为输入参数显式传递给函数的变量和数据
- 写隐藏的输出
- 改变给定的参数
- 与外界执行某种 I/O
当然,如果一个应用程序不能读取或写入外部世界,它就不是很有用,所以人们提出这个建议:
使用纯函数编写应用程序的核心,然后围绕该核心编写一个不纯的“包装器”以与外部世界交互。 如果你喜欢食物类比,这就像在纯蛋糕上放一层不纯的糖霜。
有一些方法可以让与外界的不纯洁的互动感觉更纯洁一些。 例如,您会听到诸如用于处理用户输入、文件、网络和数据库的“IO”Monad 之类的东西。 但最终,FP 应用程序有一个核心的纯函数结合其他函数来与外界交互。
Writing pure functions在 Scala 中编写纯函数是函数式编程中比较简单的部分之一:您只需使用 Scala 的方法语法编写纯函数即可。 这是一个将给定的输入值加倍的纯函数:
def double(i: Int): Int = i * 2
虽然本书没有涉及递归,但如果你喜欢一个好的“挑战”示例,这里有一个计算整数列表总和的纯函数(List[Int]
):
def sum(list: List[Int]): Int = list match {
case Nil => 0
case head :: tail => head + sum(tail)
}
即使我们没有涉及递归,如果你能理解这段代码,你就会发现它符合我对纯函数的定义。
Key points本课的第一个重点是纯函数的定义:
A 纯函数 是一个仅依赖于其声明的输入和其内部算法来产生其输出的函数。 它不会从“外部世界”(函数作用域之外的世界)读取任何其他值,也不会修改外部世界中的任何值。
第二个关键点是现实世界的应用程序由纯函数和非纯函数的组合组成。 一个常见的建议是使用纯函数编写应用程序的核心,然后使用非纯函数与外界进行通信。
PASSING FUNCTIONS AROUNDhttps://docs.scala-lang.org/overviews/scala-book/passing-functions-around.html
虽然曾经创建的每种编程语言都可能允许您编写纯函数,但 Scala 的第二个伟大的 FP 特性是您可以将函数创建为变量,就像创建 String 和 Int 变量一样。 这个特性有很多好处,其中最常见的是它可以让你将函数作为参数传递给其他函数。 您在本书前面演示 map 和 filter 方法时看到了这一点:
val nums = (1 to 10).toList
val doubles = nums.map(_ * 2)
val lessThanFive = nums.filter(_ 2)
List(5,1,3,11,7).takeWhile(_ toUpper(s))
那些使用“常规”函数的示例等效于这些匿名函数示例:
List("foo", "bar").map(s => s.toUpperCase)
List("foo", "bar").map(_.toUpperCase)
NO NULL VALUES
https://docs.scala-lang.org/overviews/scala-book/no-null-values.html
函数式编程就像编写一系列代数方程,因为在代数中不使用空值,所以在 FP 中也不使用空值。 这就引出了一个有趣的问题:在您通常在 Java/OOP 代码中使用空值的情况下,您会怎么做?
Scala 的解决方案是使用类似 Option/Some/None 类的结构。 我们将在本课中介绍这些技术。
A first example虽然第一个 Option/Some/None
示例不处理空值,但它是演示 Option/Some/None
类的好方法,因此我们将从它开始。
想象一下,您想编写一个方法来轻松将字符串转换为整数值,并且您想要一种优雅的方式来处理当您的方法获取像“foo”这样的字符串而不是转换的东西时可能抛出的异常 到一个数字,比如“1”。 对此类函数的初步猜测可能如下所示:
def toInt(s: String): Int = {
try {
Integer.parseInt(s.trim)
} catch {
case e: Exception => 0
}
}
这个函数的想法是,如果字符串转换为整数,则返回转换后的 Int
,但如果转换失败,则返回 0。这对于某些目的来说可能没问题,但并不准确。 例如,该方法可能收到了“0”,但它也可能收到了“foo”或“bar”或无数其他字符串。 这就产生了一个真正的问题:你怎么知道这个方法什么时候真正收到了“0”,或者什么时候收到了别的东西? 答案是,使用这种方法,无法知道。
Scala 解决这个问题的方法是使用三个类,称为Option
、Some
和None
。 Some
和 None
类是 Option
的子类,因此解决方案如下所示:
- 你声明
toInt
返回一个Option
类型 - 如果
toInt
收到一个字符串,它可以 转换为Int
,则将Int
包装在Some
中 - 如果
toInt
收到一个它不能转换的字符串,它返回一个None
解决方案的实现如下所示:
def toInt(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s.trim))
} catch {
case e: Exception => None
}
}
这段代码可以读作,“当给定的字符串转换为整数时,返回包裹在Some
包装器中的整数,例如Some(1)
。 当字符串无法转换为整数时,返回一个 None
值。”
下面是两个演示 toInt
实际操作的 REPL 示例:
scala> val a = toInt("1")
a: Option[Int] = Some(1)
scala> val a = toInt("foo")
a: Option[Int] = None
如图所示,字符串1
转换为Some(1)
,字符串foo
转换为None
。 这是 Option/Some/None 方法的本质。 它用于处理异常(如本例所示),同样的技术也适用于处理空值。
您会发现这种方法在整个 Scala 库类和第三方 Scala 库中都有使用。
Being a consumer of toInt现在假设您是 toInt
方法的使用者。 您知道该方法返回 Option[Int]
的子类,所以问题变成了,您如何处理这些返回类型?
根据您的需要,有两个主要答案:
- 使用“匹配”表达式
- 使用 for 表达式
还有其他方法,但这是两种主要方法,尤其是从 FP 的角度来看。
Using a match expression一种可能性是使用 match
表达式,如下所示:
toInt(x) match {
case Some(i) => println(i)
case None => println("That didn't work.")
}
在这个例子中,如果 x
可以转换为 Int
,则执行第一个 case
语句; 如果 x
无法转换为 Int
,则执行第二个 case
语句。
另一种常见的解决方案是使用 for 表达式——即本书前面展示的 for/yield 组合。 为了演示这一点,假设您要将三个字符串转换为整数值,然后将它们相加。 for/yield 解决方案如下所示:
val y = for {
a
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?