Scala 3 — 书籍

为何选择 Scala 3?

语言

使用 Scala 有很多好处,尤其是 Scala 3。很难列出 Scala 的所有好处,但“十大”好处可能如下所示

  1. Scala 融合了函数式编程 (FP) 和面向对象编程 (OOP)
  2. Scala 是静态类型的,但通常感觉像动态类型的语言
  3. Scala 的语法简洁,但仍然可读;它通常被称为富有表现力
  4. Scala 2 中的隐式是一个决定性特征,它们在 Scala 3 中得到了改进和简化
  5. Scala 与 Java 无缝集成,因此您可以创建包含 Scala 和 Java 混合代码的项目,并且 Scala 代码可以轻松使用数千个现有的 Java 库
  6. Scala 可以用于服务器,也可以在浏览器中与 Scala.js 一起使用
  7. Scala 标准库有数十种预构建的函数式方法,可以节省您的时间,并大大减少编写自定义 for 循环和算法的需要
  8. “最佳实践”已内置到 Scala 中,它支持不可变性、匿名函数、高阶函数、模式匹配、默认情况下不可扩展的类等
  9. Scala 生态系统提供世界上最现代的 FP 库
  10. 强大的类型系统

1) FP/OOP 融合

与任何其他语言相比,Scala 更支持 FP 和 OOP 范式的融合。正如 Martin Odersky 所说,Scala 的本质是在类型化设置中融合函数式和面向对象编程,其中

  • 逻辑函数和
  • 模块化对象

模块化的最佳示例可能是标准库中的类。例如,List 被定义为一个类,从技术上讲它是一个抽象类,并且可以像这样创建一个新实例

val x = List(1, 2, 3)

然而,对程序员来说,一个简单的 List 实际上是由几种专门类型的组合构建的,包括名为 IterableSeqLinearSeq 的特质。这些类型同样由其他小型模块化代码单元组成。

除了从一系列模块化特质构建 List 这样的类型之外,List API 还包含几十个其他方法,其中许多是高阶函数

val xs = List(1, 2, 3, 4, 5)

xs.map(_ + 1)         // List(2, 3, 4, 5, 6)
xs.filter(_ < 3)      // List(1, 2)
xs.find(_ > 3)        // Some(4)
xs.takeWhile(_ < 3)   // List(1, 2)

在这些示例中,列表中的值不能被修改。List 类是不可变的,因此所有这些方法都返回新值,如每条注释中的数据所示。

2) 动态感觉

Scala 的类型推断通常会让语言感觉是动态类型的,即使它是静态类型的。变量声明就是如此

val a = 1
val b = "Hello, world"
val c = List(1,2,3,4,5)
val stuff = ("fish", 42, 1_234.5)

将匿名函数传递给高阶函数时也是如此

list.filter(_ < 4)
list.map(_ * 2)
list.filter(_ < 4)
    .map(_ * 2)

以及定义方法时

def add(a: Int, b: Int) = a + b

在 Scala 3 中,这种情况比以往任何时候都更加明显,例如使用联合类型

// union type parameter
def help(id: Username | Password) =
  val user = id match
    case Username(name) => lookupName(name)
    case Password(hash) => lookupPassword(hash)
  // more code here ...

// union type value
val b: Password | Username = if (true) name else password

3) 简洁的语法

Scala 是一种低仪式,“简洁但仍然可读”的语言。例如,变量声明很简洁

val a = 1
val b = "Hello, world"
val c = List(1,2,3)

创建特质、类和枚举等类型很简洁

trait Tail:
  def wagTail(): Unit
  def stopTail(): Unit

enum Topping:
  case Cheese, Pepperoni, Sausage, Mushrooms, Onions

class Dog extends Animal, Tail, Legs, RubberyNose

case class Person(
  firstName: String,
  lastName: String,
  age: Int
)

高阶函数很简洁

list.filter(_ < 4)
list.map(_ * 2)

所有这些表达式以及更多表达式都很简洁,并且仍然非常可读:我们称之为表达

4) 隐式,简化

Scala 2 中的隐式是主要的独特设计特征。它们代表了抽象上下文基本方式,采用统一范例来满足各种用例,其中包括

  • 实现类型类
  • 建立上下文
  • 依赖注入
  • 表达能力

从那时起,其他语言采用了类似的概念,所有这些概念都是术语推断核心思想的变体:给定一个类型,编译器会合成一个具有该类型的“规范”术语。

虽然隐式是 Scala 2 中的一个决定性功能,但在 Scala 3 中其设计得到了极大的改进

  • 有一种定义“给定”值的方法
  • 有一种引入隐式参数和参数的方法
  • 有一种单独的方式来导入给定值,这种方式不允许它们隐藏在正常的导入中
  • 有一种定义隐式转换的方法,该方法明确标记为隐式转换,并且不需要特殊语法

这些更改的好处包括

  • 新设计避免了功能交互,使语言更加一致
  • 它使隐式更容易学习,更难滥用
  • 它极大地提高了使用隐式的 95% Scala 程序的清晰度
  • 它有可能以一种原则性的方式启用术语推理,这种方式也是可访问和友好的

这些功能在其他部分中进行了详细描述,因此请参阅 上下文抽象简介,以及 givenusing 子句 部分以了解更多详细信息。

5) 无缝的 Java 集成

Scala/Java 交互在很多方面都是无缝的。例如

  • 您可以在 Scala 项目中使用所有数千个可用的 Java 库
  • Scala String 本质上是一个 Java String,其中添加了其他功能
  • Scala 无缝地使用 Java java.time._ 包中的日期/时间类

您还可以在 Scala 中使用 Java 集合类,并且为了赋予它们更多功能,Scala 包含方法,以便您可以将它们转换为 Scala 集合。

虽然几乎每次交互都是无缝的,但 “与 Java 交互”章节 演示了如何更好地一起使用一些功能,包括如何使用

  • Scala 中的 Java 集合
  • Scala 中的 Java Optional
  • Scala 中的 Java 接口
  • Java 中的 Scala 集合
  • Java 中的 Scala Option
  • Java 中的 Scala 特性
  • 在 Java 代码中抛出异常的 Scala 方法
  • Java 中的 Scala 可变参数

有关这些功能的更多详细信息,请参阅该章节。

6) 客户端和服务器

Scala 可与出色的框架一起在服务器端使用

  • Play Framework 让你能够构建高度可扩展的服务器端应用程序和微服务
  • Akka Actors 让你能够使用 Actor 模型极大地简化分布式和并发软件应用程序

Scala 还可以与 Scala.js 项目 一起在浏览器中使用,该项目是 JavaScript 的类型安全替代品。Scala.js 生态系统拥有数十个库,让你能够在浏览器中使用 React、Angular、jQuery 和许多其他 JavaScript 和 Scala 库。

除了这些工具之外,Scala Native 项目“是一个专门为 Scala 设计的优化预先编译器和轻量级托管运行时”。它让你能够使用纯 Scala 代码构建“系统”样式的二进制可执行应用程序,并且还让你能够使用更低级别的基元。

7) 标准库方法

你几乎不需要再编写自定义 for 循环,因为 Scala 标准库中的数十个预构建函数方法既可以节省你的时间,又可以帮助在不同的应用程序中使代码保持一致。

以下示例显示了一些内置集合方法,除了这些之外还有很多。虽然这些都使用 List 类,但相同的方法适用于其他集合类,如 SeqVectorLazyListSetMapArrayArrayBuffer

以下是一些示例

List.range(1, 3)                          // List(1, 2)
List.range(start = 1, end = 6, step = 2)  // List(1, 3, 5)
List.fill(3)("foo")                       // List(foo, foo, foo)
List.tabulate(3)(n => n * n)              // List(0, 1, 4)
List.tabulate(4)(n => n * n)              // List(0, 1, 4, 9)

val a = List(10, 20, 30, 40, 10)          // List(10, 20, 30, 40, 10)
a.distinct                                // List(10, 20, 30, 40)
a.drop(2)                                 // List(30, 40, 10)
a.dropRight(2)                            // List(10, 20, 30)
a.dropWhile(_ < 25)                       // List(30, 40, 10)
a.filter(_ < 25)                          // List(10, 20, 10)
a.filter(_ > 100)                         // List()
a.find(_ > 20)                            // Some(30)
a.head                                    // 10
a.headOption                              // Some(10)
a.init                                    // List(10, 20, 30, 40)
a.intersect(List(19,20,21))               // List(20)
a.last                                    // 10
a.lastOption                              // Some(10)
a.map(_ * 2)                              // List(20, 40, 60, 80, 20)
a.slice(2, 4)                             // List(30, 40)
a.tail                                    // List(20, 30, 40, 10)
a.take(3)                                 // List(10, 20, 30)
a.takeRight(2)                            // List(40, 10)
a.takeWhile(_ < 30)                       // List(10, 20)
a.filter(_ < 30).map(_ * 10)              // List(100, 200, 100)

val fruits = List("apple", "pear")
fruits.map(_.toUpperCase)                 // List(APPLE, PEAR)
fruits.flatMap(_.toUpperCase)             // List(A, P, P, L, E, P, E, A, R)

val nums = List(10, 5, 8, 1, 7)
nums.sorted                               // List(1, 5, 7, 8, 10)
nums.sortWith(_ < _)                      // List(1, 5, 7, 8, 10)
nums.sortWith(_ > _)                      // List(10, 8, 7, 5, 1)

8) 内置最佳实践

Scala 惯用法以多种方式鼓励最佳实践。对于不可变性,建议你创建不可变的 val 声明

val a = 1                 // immutable variable

你还应该使用不可变集合类,如 ListMap

val b = List(1,2,3)       // List is immutable
val c = Map(1 -> "one")   // Map is immutable

用例类主要用于领域建模,并且其参数是不可变的

case class Person(name: String)
val p = Person("Michael Scott")
p.name           // Michael Scott
p.name = "Joe"   // compiler error (reassignment to val name)

如前一节所示,Scala 集合类支持高阶函数,你可以将方法(未显示)和匿名函数传递给它们

a.dropWhile(_ < 25)
a.filter(_ < 25)
a.takeWhile(_ < 30)
a.filter(_ < 30).map(_ * 10)
nums.sortWith(_ < _)
nums.sortWith(_ > _)

match 表达式让你能够使用模式匹配,它们真正是返回值的表达式

val numAsString = i match {
  case 1 | 3 | 5 | 7 | 9 => "odd"
  case 2 | 4 | 6 | 8 | 10 => "even"
  case _ => "too big"
}
val numAsString = i match
  case 1 | 3 | 5 | 7 | 9 => "odd"
  case 2 | 4 | 6 | 8 | 10 => "even"
  case _ => "too big"

由于它们可以返回值,因此它们通常用作方法的主体

def isTruthy(a: Matchable) = a match {
  case 0 | "" => false
  case _ => true
}
def isTruthy(a: Matchable) = a match
  case 0 | "" => false
  case _ => true

9) 生态系统库

用于函数式编程的 Scala 库,如 CatsZio,是 FP 社区中的领先库。所有这些流行语,如高性能、类型安全、并发、异步、资源安全、可测试、函数式、模块化、二进制兼容、高效、效果/效果等,都可以用于这些库。

我们可以在此处列出数百个库,但幸运的是,它们都列在另一个位置:有关这些详细信息,请参阅 “Awesome Scala” 列表

10) 强大的类型系统

Scala 具有强大的类型系统,并且在 Scala 3 中得到了进一步的改进。Scala 3 的目标在早期就已定义,与类型系统相关的目标包括

  • 简化
  • 消除不一致
  • 安全性
  • 人体工程学
  • 性能

简化通过几十项已更改和已删除的功能实现。例如,从 Scala 2 中重载的 implicit 关键字到 Scala 3 中的 givenusing 术语的更改使语言更加清晰,尤其对于初学者而言。

消除不一致与 Scala 3 中几十项 已删除的功能已更改的功能已添加的功能 相关。此类别中一些最重要的功能是

  • 相交类型
  • 并集类型
  • 隐式函数类型
  • 依赖函数类型
  • 特征参数
  • 泛型元组

安全性与几个新的和已更改的功能相关

  • 多重相等
  • 限制隐式转换
  • 空安全性
  • 安全初始化

人体工程学的良好示例是枚举和扩展方法,它们已以非常可读的方式添加到 Scala 3 中

// enumeration
enum Color:
  case Red, Green, Blue

// extension methods
extension (c: Circle)
  def circumference: Double = c.radius * math.Pi * 2
  def diameter: Double = c.radius * 2
  def area: Double = math.Pi * c.radius * c.radius

性能涉及多个领域。其中之一是 不透明类型。在 Scala 2 中,有几次尝试创建解决方案来遵循领域驱动设计 (DDD) 实践,即为值提供更有意义的类型。这些尝试包括

  • 类型别名
  • 值类
  • 案例类

不幸的是,所有这些方法都有弱点,如 不透明类型 SIP 中所述。相反,该 SIP 中所述的不透明类型的目标是“对这些包装器类型的操作在运行时不会产生任何额外开销,同时在编译时仍提供类型安全的使用。”

有关更多类型系统详细信息,请参阅 参考文档

其他出色的功能

Scala 有许多出色的功能,选择前 10 个列表可能很主观。多项调查显示,不同的开发者群体喜欢不同的功能。希望您在使用该语言时发现更多出色的 Scala 功能。

此页面的贡献者