From f38f5ff61bb9df1e5477ed1955a1573564dab3fa Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Wed, 11 May 2022 07:39:57 +0800 Subject: [PATCH 01/53] modify/correct _zh-cn/index.md --- _zh-cn/index.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/_zh-cn/index.md b/_zh-cn/index.md index 9e1e7d3507..c8da57c6e5 100644 --- a/_zh-cn/index.md +++ b/_zh-cn/index.md @@ -12,37 +12,37 @@ scala3-sections: - title: "第一步" links: - title: "Scala 3 中的新东西" - description: "Scala 3 现存新特性概览" + description: "Scala 3 中令人兴奋的新特性概览" icon: "fa fa-star" link: /scala3/new-in-scala3.html - title: "快速开始" - description: "安装 Scala 3 并开始写些 Scala 代码" + description: "在你的电脑中安装 Scala 3,然后开始写些 Scala 代码!" icon: "fa fa-rocket" link: /scala3/getting-started.html - - title: "Scala 3 书籍" - description: "一部介绍主要语言特性的线上书" + - title: "Scala 3 册子" + description: "通过一系列小课程来学习 Scala。" icon: "fa fa-book" link: /scala3/book/introduction.html - title: "更多细节" links: - title: "迁移指引" - description: "一份帮你从 Scala 2 迁移到 Scala 3 的指引" + description: "一份帮你从 Scala 2 迁移到 Scala 3 的指引。" icon: "fa fa-suitcase" link: /scala3/guides/migration/compatibility-intro.html - title: "导览" - description: "关于语言特别之处的详细导览" + description: "关于语言特别之处的详细导览。" icon: "fa fa-map" link: /zh-cn/scala3/guides.html - title: "Scala 库 API" - description: "Scala 3 标准库API文档(多个小版本)" + description: "Scala 3 每一个版本的标准库API文档。" icon: "fa fa-file-alt" link: https://scala-lang.org/api/3.x/ - title: "语言参考手册" - description: "Scala 3 语言参考手册" + description: "Scala 3 语言参考手册。" icon: "fa fa-book" link: https://docs.scala-lang.org/scala3/reference - title: "贡献指南" - description: "Scala 3 编译器指南及如何贡献代码" + description: "Scala 3 编译器指南及如何修复问题" icon: "fa fa-cogs" link: /scala3/guides/contribution/contribution-intro.html - title: "Scala 3 全新的 Scaladoc" @@ -109,4 +109,8 @@ scala2-sections: description: "Scala改进过程(Scala Improvement Process),语言及编译器进展" icon: "fa fa-cogs" link: /sips/index.html + - title: "为 Scala 作贡献" + description: "为 Scala 项目作贡献的完整指南" + icon: "fa fa-cogs" + link: /contribute/ --- From 30add3033c86a06478f4af1962dbad3c04e3f5f7 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Wed, 11 May 2022 08:24:45 +0800 Subject: [PATCH 02/53] modify _zh-cn/scala3/new-in-scala3.md --- _zh-cn/scala3/new-in-scala3.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/_zh-cn/scala3/new-in-scala3.md b/_zh-cn/scala3/new-in-scala3.md index 8308c7ac89..5f82276635 100644 --- a/_zh-cn/scala3/new-in-scala3.md +++ b/_zh-cn/scala3/new-in-scala3.md @@ -1,10 +1,9 @@ --- layout: singlepage-overview -title: New in Scala 3 +title: Scala 3 里的新东西 scala3: true --- - -令人振奋的新版 Scala 3 带来了许多改进和新功能。在这里,我们为你提供最重要的变更的快速概述。如果你想深入挖掘,还有一些参考资料供你使用。 +令人振奋的新版 Scala 3 带来了许多改进和新功能。在这里,我们为你提供最重要的变更的快速概述。如果你想深入挖掘,还有一些参考资料供你使用: - [Scala 3 Book]({% link _overviews/scala3-book/introduction.md %}) 面向刚接触 Scala 语言的开发人员。 - [Syntax Summary][syntax-summary] 为您提供了新语法的正式描述。 @@ -12,11 +11,10 @@ scala3: true - [Migration Guide][migration] 为你提供了从 Scala 2 迁移到 Scala 3 的所有必要信息。 - [Scala 3 Contributing Guide][contribution] Scala 3 贡献指南,更深入地探讨了编译器,包括修复问题的指南。 -## What's new in Scala 3 +## Scala 3 里有什么新东西 Scala 3 是对 Scala 语言的一次彻底改造。在其核心部分,类型系统的许多方面都被改变了,变得更有原则性。虽然这也带来了令人兴奋的新功能(比如联合类型),但首先意味着类型系统变得(甚至)不那么碍事了,例如[类型推断][type-inference]和 overload resolution 都得到了很大的改善。 ### 新的和闪亮的:语法 - 除了许多(小的)清理工作,Scala 3 的语法还提供了以下改进: - 用于控制结构的新“quiet”语法,如 `if`、`while` 和 `for` 。 ([new control syntax][syntax-control]) @@ -25,10 +23,10 @@ Scala 3 是对 Scala 语言的一次彻底改造。在其核心部分,类型 - [类型级通配符][syntax-wildcard] 从 `_` 更改为 `?`。 - implicit(和它们的语法)已被[大量修订][implicits]。 -### Opinionated: Contextual Abstractions -Scala的一个基本核心概念是(在某种程度上仍然是)为用户提供一小部分强大的功能,这些功能可以被组合成巨大的(有时甚至是不可预见的)表达能力。例如,_implicit_ 的特性被用来模拟上下文抽象、表达类型级计算、模拟类型类、执行隐式强制、编码扩展方法等等。从这些用例中学习,Scala 3 采取了一种略微不同的方法,专注于 __意图__ 而非 __机制__。Scala 3 没有提供一个非常强大的功能,而是提供了多个定制的语言功能,让程序员直接表达他们的意图。 +### Opinionated: 上下文抽象 +Scala的一个基本核心概念是(在某种程度上仍然是)为用户提供一小部分强大的功能,这些功能可以被组合成巨大的(有时甚至是不可预见的)表达能力。例如,_implicit_ 的特性被用来模拟上下文抽象、表达类型级计算、模拟类型类、执行隐式强制、编码扩展方法等等。从这些用例中学习,Scala 3 采取了一种略微不同的方法,专注于 **意图** 而非 **机制**。Scala 3 没有提供一个非常强大的功能,而是提供了多个定制的语言功能,让程序员直接表达他们的意图。 -- **Abtracting over contextual information**. [Using clauses][contextual-using] 允许程序员对调用上下文中的信息进行抽象,这些信息应该以隐式方式传递。作为对 Scala 2 implicits 的改进,可以按类型指定`using`子句,从而将函数签名从从未显式引用的术语变量名中解放出来。 +- **Abtracting over contextual information**. [Using clauses][contextual-using] 允许程序员对调用上下文中的信息进行抽象,这些信息应该以隐式方式传递。作为对 Scala 2 implicits 的改进,可以按类型指定 using 子句,从而将函数签名从从未显式引用的术语变量名中解放出来。 - **Providing Type-class instances**. [Given instances][contextual-givens] 允许程序员定义某个类型的 _规范值_ 。这使得使用类型类的编程更加简单,而不会泄露实现细节。 @@ -40,8 +38,8 @@ Scala的一个基本核心概念是(在某种程度上仍然是)为用户提 - **Actionable feedback from the compiler**. 如果一个隐式参数不能被编译器解决,它现在提供了可能解决这个问题的[import suggestions](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html)。 -### Say What You Mean: 类型系统改进 -除了极大地改进了类型推断,Scala 3 类型系统还提供了许多新的功能,还为你提供了强大的工具来静态地表达类型中的不变量。 +### 表达真正的意思: 类型系统改进 +除了极大地改进了类型推断,Scala 3 类型系统还提供了许多新的功能,还为你提供了强大的工具来静态地表达类型中的不变量: - **Enumerations**. [枚举][enums]已经被重新设计,以便与样例类很好地融合,并形成表达[代数数据类型][enums-adts]的新标准。 @@ -58,7 +56,6 @@ Scala的一个基本核心概念是(在某种程度上仍然是)为用户提 - **Match types**. Scala 3 提供了对[matching on types][types-match]的直接支持,而不是使用隐式解析对类型级别的计算进行编码。将类型级计算整合到类型检查器中,可以改进错误信息,并消除对复杂编码的需求。 ### Re-envisioned:面向对象的编程 - Scala 一直处于函数式编程和面向对象编程的前沿 -- 而 Scala 3 在这两个方向上都推动了边界的发展! 上述类型系统的变化和上下文抽象的重新设计使得 _函数式编程_ 比以前更容易。同时,以下的新特性使结构良好的 _面向对象设计_ 成为可能,并支持最佳实践。 - **Pass it on**. Trait 更接近于 class,现在也可以接受[参数][oo-trait-parameters],使其作为模块化软件分解的工具更加强大。 @@ -79,7 +76,6 @@ Scala 2 中的宏只是一个实验性的功能,而 Scala 3 则为元编程提 - **Quoted code blocks**. Scala 3为代码增加了[quasi-quotation][meta-quotes]的新功能,这为构建和分析代码提供了方便的高级接口。构建加一加一的代码就像`'{ 1 + 1 }`一样简单。 - **Reflection API**. 对于更高级的用例,[quotes.reflect][meta-reflection]提供了更详细的控制来检查和生成程序树。 - 如果你想进一步了解 Scala 3 中的元编程,我们邀请你参阅我们的[教程][meta-tutorial]。 [enums]: {{ site.scala3ref }}/enums/enums.html From 1c937b8a3cf7e58a8b611e29357f0f45fd2829af Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Wed, 11 May 2022 17:28:50 +0800 Subject: [PATCH 03/53] add _zh-cn/scala3/guides/tasty-overview.md --- _zh-cn/scala3/guides/tasty-overview.md | 146 +++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 _zh-cn/scala3/guides/tasty-overview.md diff --git a/_zh-cn/scala3/guides/tasty-overview.md b/_zh-cn/scala3/guides/tasty-overview.md new file mode 100644 index 0000000000..ca2d4260c5 --- /dev/null +++ b/_zh-cn/scala3/guides/tasty-overview.md @@ -0,0 +1,146 @@ +--- +layout: singlepage-overview +title: TASTy 概览 +--- +假定你创建了一个 Scala 3 源代码文件叫 _Hello.scala_: + +```scala +@main def hello = println("Hello, world") +``` + +然后用 `scalac` 编译了该文件: + +```bash +$ scalac Hello.scala +``` + +你会发现在 `scalac` 生成的其它文件结果中,有些文件是以 _.tasty_ 为扩展名: + +```bash +$ ls -1 +Hello$package$.class +Hello$package.class +Hello$package.tasty +Hello.scala +hello.class +hello.tasty +``` + +这自然地会引出一个问题,“什么是 tasty?” + +## 什么是 TASTy? + +TASTy 是从 _Typed Abstract Syntax Trees_ 这个术语的首字母缩写来的。它是 Scala 3 的高级交换格式,在本文档中,我们将它称为 _Tasty_ 。 + +首先要知道的是,Tasty 文件是由 `scalac` 编译器生成的,并且包含 _所有_ 有关源代码的信息,这些信息包括程序的语法结构,以及有关类型,位置甚至文档的 _所有_ 信息。Tasty 文件包含的信息比 _.class_ 文件多得多,后者是为在 JVM 上运行而生成的。(后面有详细介绍)。 + +在 Scala 3 中,编译流程像这样: + +```text + +-------------+ +-------------+ +-------------+ +$ scalac | Hello.scala | -> | Hello.tasty | -> | Hello.class | + +-------------+ +-------------+ +-------------+ + ^ ^ ^ + | | | + 你的代码 TASTy 文件 Class 文件 + 用于 scalac 用于 JVM + (包括完整信息) (不完整信息) +``` + +您可以通过使用 `-print-tasty` 标志在 _.tasty_ 文件上运行编译器,以人类可读的形式查看 _.tasty_ 文件的内容。 +您还可以使用 `-decompile` 标志以类似于 Scala 源代码的形式查看反编译的内容。 + +```bash +$ scalac -print-tasty hello.tasty +$ scalac -decompile hello.tasty +``` + +### The issue with _.class_ files + +由于象 [类型擦除][erasure] 等问题,_.class_ 文件实际上是代码的不完整表示形式。 +演示这一点的一种简单方法是使用 `List` 示例。 + +_类型擦除_ 意味着当你编写这样的 Scala 代码时,它假定在 JVM 上运行: + +```scala +val xs: List[Int] = List(1, 2, 3) +``` + +该代码被编译为需要与 JVM 兼容的 _.class_ 文件。由于该兼容性要求,该类文件中的代码 --- 您可以使用 `javap` 命令看到它,--- 最终看起来像这样: + +```java +public scala.collection.immutable.List xs(); +``` + +该 `javap` 命令输出显示了类文件中包含的内容,该内容是 Java 的表示形式。请注意,在此输出中,`xs` _不是_ 定义为 `List[Int]` ;它基本上表示为 `List[java.lang.Object]` 。为了使您的 Scala 代码与 JVM 配合使用,`Int` 类型已被擦除。 + +稍后,当您在 Scala 代码中访问 `List[Int]` 的元素时,像这样: + +```scala +val x = xs(0) +``` + +生成的类文件对此行代码进行强制转换操作,您可以将其想象成: + +``` +int x = (Int) xs.get(0) // Java-ish +val x = xs.get(0).asInstanceOf[Int] // more Scala-like +``` + +同样,这样做是为了兼容性,因此您的 Scala 代码可以在 JVM 上运行。但是,我们已经有的整数列表的信息在类文件中丢失了。 +当尝试使用已编译的库来编译 Scala 程序时,会带来问题。为此,我们需要比类文件中通常可用的信息更多的信息。 + +此讨论仅涵盖类型擦除的主题。对于 JVM 没有意识到的所有其他 Scala 结构,也存在类似的问题,包括 unions, intersections, 带有参数的 traits 以及更多 Scala 3 特性。 + +### TASTy to the Rescue + +因此,TASTy 格式不是没有关于 _.class_ 文件中原始类型的信息,或者只有公共 API(如Scala 2.13 “Pickle” 格式),而是在类型检查后存储完整的抽象语法树(AST)。存储整个 AST 有很多优点:它支持单独编译,针对不同的 JVM 版本重新编译,程序的静态分析等等。 + +### 重点 + +因此,这是本节的第一个要点:您在 Scala 代码中指定的类型在 _.class_ 文件中没有完全准确地表示。 + +第二个关键点是要了解 _编译时_ 和 _运行时_ 提供的信息之间存在差异: + +- 在**编译时**,当 `scalac` 读取和分析你的代码时,它知道 `xs` 是一个 `List[Int]` +- 当编译器将你的代码写入类文件时,它会写 `xs` 是 `List[Object]` ,并在访问 `xs` 的任何地方添加转换信息 +- 然后在**运行时** --- 你的代码在 JVM 中运行,--- JVM 不知道你的列表是一个 `List[Int]` + +对于 Scala 3 和 Tasty,这里有一个关于编译时的重要说明: + +- 当您编写使用其他 Scala 3 库的 Scala 3 代码时,`scalac` 不必再读取其 _.class_ 文件;它可以读取其 _.tasty_ 文件,如前所述,这些文件是代码的 _准确_ 表示形式。这对于在 Scala 2.13 和 Scala 3 之间实现单独编译和兼容性非常重要。 + +## Tasty 的好处 + +可以想象,拥有代码的完整表示形式具有[许多好处][benefits]: + +- 编译器使用它来支持单独的编译。 +- Scala 基于 _Language Server Protocol_ 的语言服务器使用它来支持超链接、命令补全、文档以及全局操作,如查找引用和重命名。 +- Tasty 为新一代[基于反射的宏][macros]奠定了良好的基础。 +- 优化器和分析器可以使用它进行深度代码分析和高级代码生成。 + +在相关的说明中,Scala 2.13.6 有一个 TASTy 读取器,Scala 3 编译器也可以读取2.13“Pickle”格式。Scala 3 迁移指南中的 [类路径兼容性页面][compatibility-ref] 解释了此交叉编译功能的好处。 + +## 更多信息 + +总之,Tasty 是 Scala 3 的高级交换格式,_.tasty_ 文件包含源代码的完整表示形式,从而带来了上一节中概述的好处。 + +有关更多详细信息,请参阅以下资源: + +- 在 [此视频](https://www.youtube.com/watch?v=YQmVrUdx8TU) 中,Scala 中心的Jamie Thompson 对 Tasty 的工作原理及其优势进行了详尽的讨论 +- [库作者的二进制兼容性][binary] 讨论二进制兼容性、源代码兼容性和 JVM 执行模型 +- [Scala 3 Transition 的前向兼容性](https://www.scala-lang.org/blog/2020/11/19/scala-3-forward-compat.html) 演示了在同一项目中使用 Scala 2.13 和 Scala 3 的技术 + +这些文章提供了有关 Scala 3 宏的更多信息: + +- [Scala 宏库](https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html) +- [宏:Scala 3 的计划](https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html) +- [Quotes Reflect 的参考文档][quotes-reflect] +- [宏的参考文档](macros) + +[benefits]: https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html +[erasure]: https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#type-erasure +[binary]: {% link _overviews/tutorials/binary-compatibility-for-library-authors.md %} +[compatibility-ref]: {% link _overviews/scala3-migration/compatibility-classpath.md %} +[quotes-reflect]: {{ site.scala3ref }}/metaprogramming/reflection.html +[macros]: {{ site.scala3ref }}/metaprogramming/macros.html From 770e5a78f3c91f43780eca0aa65392a53fcb84dd Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Fri, 13 May 2022 15:03:10 +0800 Subject: [PATCH 04/53] Change _zh-cn/scala3/guides/tasty-overview.md according to review. Add _zh-cn/scala3/reference/README.md Add _zh-cn/overview/scala3-book/why-scala-3.md --- _zh-cn/overviews/scala3-book/why-scala-3.md | 411 ++++++++++++++++++++ _zh-cn/scala3/guides/tasty-overview.md | 10 +- _zh-cn/scala3/reference/README.md | 5 + 3 files changed, 421 insertions(+), 5 deletions(-) create mode 100644 _zh-cn/overviews/scala3-book/why-scala-3.md create mode 100644 _zh-cn/scala3/reference/README.md diff --git a/_zh-cn/overviews/scala3-book/why-scala-3.md b/_zh-cn/overviews/scala3-book/why-scala-3.md new file mode 100644 index 0000000000..8a2121caed --- /dev/null +++ b/_zh-cn/overviews/scala3-book/why-scala-3.md @@ -0,0 +1,411 @@ +--- +title: 为什么是 Scala 3 ? +type: chapter +description: This page describes the benefits of the Scala 3 programming language. +num: 3 +previous-page: scala-features +next-page: taste-intro +--- + +{% comment %} +TODO: Is “Scala 3 Benefits” a better title? +NOTE: Could mention “grammar” as a way of showing that Scala isn’t a large language; see this slide: https://www.slideshare.net/Odersky/preparing-for-scala-3#13 +{% endcomment %} + +使用 Scala 有很多好处,特别是 Scala 3。 +很难列出 Scala 的每一个好处,但“前十名”列表可能看起来像这样: + +1. Scala 融合了函数式编程(FP)和面向对象编程(OOP) +2. Scala 是静态类型的语言,但通常感觉像一种动态类型语言。 +3. Scala 的语法简洁,但仍然可读;它通常被称为 _易于表达_ +4. Scala 2 中的 _Implicits_ 是一个定义特性,它们在 Scala 3 中得到了改进和简化。 +5. Scala 与 Java 无缝集成,因此您可以创建混合了 Scala 和 Java 代码的项目,Scala 代码可以轻松使用成千上万个现有的 Java 库 +6. Scala 可以在服务器上使用,通过 [Scala.js](https://www.scala-js.org), Scala 也可以在浏览器中使用 +7. Scala 标准库具有数十种预构建的函数式方法,可节省您的时间,并大大减少编写自定义 `for` 循环和算法的需要 +8. Scala 内置了“最佳实践”,它支持不可变性,匿名函数,高阶函数,模式匹配,默认情况下无法扩展的类等 +9. Scala 生态系统提供世界上最现代化的 FP 库 +10. 强型式系统 + +## 1) FP/OOP 融合 + +Scala 比任何其他语言都更支持 FP 和 OOP 范式的融合。 +正如 Martin Odersky 所说,Scala 的本质是在类型化环境中融合了函数式和面向对象编程,具有: + +- 逻辑函数,以及 +- 模块化对象 + +模块化的一些最佳示例可能是标准库中的类。 +例如,`List` 被定义为一个类---从技术上讲,它是一个抽象类---并且像这样创建了一个新实例: + +```scala +val x = List(1, 2, 3) +``` + +但是,在程序员看来是一个简单的 `List` 实际上是由几种特殊类型的组合构建的,包括名为`Iterable`, `Seq`, 和 `LinearSeq` 的 traits。 +这些类型同样由其他小型的模块化代码单元组成。 + +除了从一系列模块化 traits 构建/cases像 `List` 这样的类型之外,`List` API还包含数十种其他方法,其中许多是高阶函数: + +```scala +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的 _类型推断_ 经常使语言感觉是动态类型的,即使它是静态类型的。 +对于变量声明,情况确实如此: + +```scala +val a = 1 +val b = "Hello, world" +val c = List(1,2,3,4,5) +val stuff = ("fish", 42, 1_234.5) +``` + +当把匿名函数传递给高阶函数时,情况也是如此: + +```scala +list.filter(_ < 4) +list.map(_ * 2) +list.filter(_ < 4) + .map(_ * 2) +``` + +还有定义方法的时候: + +```scala +def add(a: Int, b: Int) = a + b +``` + +这在Scala 3中比以往任何时候都更加真实,例如在使用[union types][union-types] 时: + +```scala +// 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是一种 low ceremony,“简洁但仍然可读”的语言。例如,变量声明是简洁的: + +```scala +val a = 1 +val b = "Hello, world" +val c = List(1,2,3) +``` + +创建类型如traits, 类和枚举都很简洁: + +```scala +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 +) +``` + +高阶函数简洁: + +```scala +list.filter(_ < 4) +list.map(_ * 2) +``` + +所有这些表达方式以及更多表达方式都很简洁,并且仍然非常易读:我们称之为 _富有表现力_。 + +## 4) 隐式,简化 + +Scala 2 中的隐式是一个主要明显的设计特征。 +它们代表了抽象上下文的基本方式,具有服务于各种用例的统一范式,其中包括: + +- 实现 [type classes]({% link _overviews/scala3-book/ca-type-classes.md %}) +- 建立背景 +- 依赖注入 +- 表达能力 + +从那以后,其他语言也采用了类似的概念,所有这些都是 _术语推断_ 核心思想的变体:给定一个类型,编译器合成一个具有该类型的“规范”术语。 + +虽然隐式是 Scala 2 中的一个定义特性,但它们的设计在 Scala 3 中得到了极大的改进: + +- 定义“given”值的方法只有一种 +- 只有一种方法可以引入隐式参数和参数 +- 有一种单独的方式来导入 givens,不允许它们隐藏在正常导入的海洋中 +- 只有一种定义隐式转换的方法,它被清楚地标记为这样,并且不需要特殊的语法 + +这些变化的好处包括: + +- 新设计避免了特性交叉,使语言更加一致 +- 它使隐式更容易学习和不容易滥用 +- 它极大地提高了 95% 使用隐式的 Scala 程序的清晰度 +- 它有可能以一种易于理解和友好的原则方式进行术语推断 + +这些功能在其他部分有详细描述,因此请参阅 [上下文抽象介绍][context] 和 [`given` 和 `using` 子句][given] 部分了解更多详细信息。 + +## 5) 与 Java 无缝集成 + +Scala/Java 交互在许多方面都是无缝的。 +例如: + +- 您可以使用 Scala 项目中可用的所有数千个 Java 库 +- Scala `String` 本质上是 Java `String`,添加了附加功能 +- Scala 无缝使用 Java 中 *java.time._* 包中的日期/时间类 + +您还可以在 Scala 中使用 Java 集合类,并为它们提供更多功能,Scala 包含方法,因此您可以将它们转换为 Scala 集合。 + +虽然几乎所有交互都是无缝的,但[“与 Java 交互”一章][java] 演示了如何更好地结合使用某些功能,包括如何使用: + +- Scala 中的 Java 集合 +- Scala 中的 Java `Optional` +- Scala 中的 Java 接口 +- Java 中的 Scala 集合 +- Java 中的 Scala `Option` +- Java 中的 Scala traits +- 在 Java 代码中引发异常的 Scala 方法 +- Java 中的 Scala 可变参数 + +有关这些功能的更多详细信息,请参见该章。 + +## 6) 客户 &服务器 + +Scala 可以通过非常棒的框架在服务器端使用: + +- [Play Framework](https://www.playframework.com) 可让您构建高度可扩展的服务器端应用程序和微服务 +- [Akka Actors](https://akka.io) 让你使用actor模型大大简化分布式和并发软件应用程序 + +Scala 也可以通过 [Scala.js 项目](https://www.scala-js.org) 在浏览器中使用,它是 JavaScript 的类型安全替代品。 +Scala.js 生态系统 [有几十个库](https://www.scala-js.org/libraries) 让您可以在浏览器中使用 React、Angular、jQuery 和许多其他 JavaScript 和 Scala 库。 + +除了这些工具之外,[Scala Native](https://github.com/scala-native/scala-native) 项目“是一个优化的提前编译器和专为 Scala 设计的轻量级托管运行时”。它允许您使用纯 Scala 代码构建“系统”风格的二进制可执行应用程序,还允许您使用较低级别的原语。 + +## 7) 标准库方法 + +您将很少需要再次编写自定义的 `for` 循环,因为 Scala 标准库中的数十种预构建函数方法既可以节省您的时间,又有助于使代码在不同应用程序之间更加一致。 + +下面的例子展示了一些内置的集合方法,除此之外还有很多。 +虽然这些都使用 `List` 类,但相同的方法适用于其他集合类,例如 `Seq`、`Vector`、`LazyList`、`Set`、`Map`、`Array` 和 `ArrayBuffer`。 + +这里有些例子: + +```scala +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` 声明: + +```scala +val a = 1 // 不可变变量 +``` + +还鼓励您使用不可变集合类,例如 `List` 和 `Map`: + +```scala +val b = List(1,2,3) // List 是不可变的 +val c = Map(1 -> "one") // Map 是不可变的 +``` + +Case 类主要用于 [领域建模]({% link _overviews/scala3-book/domain-modeling-intro.md %}),它们的参数是不可变的: + +```scala +case class Person(name: String) +val p = Person("Michael Scott") +p.name // Michael Scott +p.name = "Joe" // 编译器错误(重新分配给 val 名称) +``` + +如上一节所示,Scala 集合类支持高阶函数,您可以将方法(未显示)和匿名函数传递给它们: + +```scala +a.dropWhile(_ < 25) +a.filter(_ < 25) +a.takeWhile(_ < 30) +a.filter(_ < 30).map(_ * 10) +nums.sortWith(_ < _) +nums.sortWith(_ > _) +``` + +`match` 表达式让您可以使用模式匹配,它们确实是返回值的 _表达式_: + +```scala +val numAsString = i match + case 1 | 3 | 5 | 7 | 9 => "odd" + case 2 | 4 | 6 | 8 | 10 => "even" + case _ => "too big" +``` + +因为它们可以返回值,所以它们经常被用作方法的主体: + +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" => false + case _ => true +``` + +## 9) 生态系统库 + +用于函数式编程的 Scala 库,如 [Cats](https://typelevel.org/cats) 和 [Zio](https://zio.dev) 是 FP 社区中的前沿库。 +所有流行语,如高性能、类型安全、并发、异步、资源安全、可测试、功能性、模块化、二进制兼容、高效、副作用/有副作用等,都可以用于这些库。 + +我们可以在这里列出数百个库,但幸运的是它们都列在另一个位置:有关这些详细信息,请参阅 [“Awesome Scala” 列表](https://github.com/lauris/awesome-scala)。 + +## 10) 强类型系统 + +Scala 有一个强大的类型系统,它在 Scala 3 中得到了更多的改进。 +Scala 3 的目标很早就定义了,与类型系统相关的目标包括: + +- 简化 +- 消除不一致 +- 安全 +- 人体工程学 +- 性能 + +_简化_ 来自数十个更改和删除的特性。 +例如,从 Scala 2 中重载的 `implicit` 关键字到 Scala 3 中的术语 `given` 和 `using` 的变化使语言更加清晰,尤其是对于初学者来说。 + +_消除不一致_ 与Scala 3中的几十个[删除的特性][dropped]、[改变的特性][changed]和[增加的特性][add]有关。 +此类别中一些最重要的功能是: + +- Intersection类型 +- 联合类型 +- 隐式函数类型 +- 依赖函数类型 +- trait 参数 +- 通用元组 + +{% comment %} +A list of types from the Dotty documentation: + +- Inferred types +- Generics +- Intersection types +- Union types +- Structural types +- Dependent function types +- Type classes +- Opaque types +- Variance +- Algebraic Data Types +- Wildcard arguments in types: ? replacing _ +- Type lambdas +- Match types +- Existential types +- Higher-kinded types +- Singleton types +- Refinement types +- Kind polymorphism +- Abstract type members and path-dependent types +- Dependent function types +- Bounds +{% endcomment %} + +_安全_ 与几个新的和改变的特性有关: + +- Multiversal equality +- Restricting implicit conversions +- Null safety +- Safe initialization + +_人体工程学_ 的好例子是枚举和扩展方法,它们以非常易读的方式添加到 Scala 3 中: + +```scala +// 枚举 +enum Color: + case Red, Green, Blue + +// 扩展方法 +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 +``` + +_性能_ 涉及几个方面。 +其中之一是 [不透明类型][opaque-types]。 +在 Scala 2 中,有几次尝试创建解决方案以与域驱动设计 (DDD) 实践相一致,即赋予值更有意义的类型。 +这些尝试包括: + +- 类型别名 +- 值类 +- case类 + +不幸的是,所有这些方法都有弱点,如 [_Opaque Types_ SIP](https://docs.scala-lang.org/sips/opaque-types.html) 中所述。 +相反,如 SIP 中所述,不透明类型的目标是“对这些包装器类型的操作不得在运行时产生任何额外开销,同时在编译时仍提供类型安全使用。” + +有关更多类型系统的详细信息,请参阅 [参考文档][reference]。 + +## 其他很棒的功能 + +Scala 有许多很棒的特性,选择十大列表可能是主观的。 +多项调查表明,不同的开发人员群体喜欢不同的特性。 + +[java]: {% link _overviews/scala3-book/interacting-with-java.md %} +[given]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[reference]: {{ site.scala3ref }} +[dropped]: {{ site.scala3ref }}/dropped-features +[changed]: {{ site.scala3ref }}/changed-features +[added]:{{ site.scala3ref }}/other-new-features + +[union-types]: {% link _overviews/scala3-book/types-union.md %} +[opaque-types]: {% link _overviews/scala3-book/types-opaque-types.md %} diff --git a/_zh-cn/scala3/guides/tasty-overview.md b/_zh-cn/scala3/guides/tasty-overview.md index ca2d4260c5..9ab7cc3124 100644 --- a/_zh-cn/scala3/guides/tasty-overview.md +++ b/_zh-cn/scala3/guides/tasty-overview.md @@ -60,7 +60,7 @@ $ scalac -decompile hello.tasty 由于象 [类型擦除][erasure] 等问题,_.class_ 文件实际上是代码的不完整表示形式。 演示这一点的一种简单方法是使用 `List` 示例。 -_类型擦除_ 意味着当你编写这样的 Scala 代码时,它假定在 JVM 上运行: +_类型擦除_ 意味着当你编写这样的Scala代码,并假定它是在JVM上运行时: ```scala val xs: List[Int] = List(1, 2, 3) @@ -72,7 +72,7 @@ val xs: List[Int] = List(1, 2, 3) public scala.collection.immutable.List xs(); ``` -该 `javap` 命令输出显示了类文件中包含的内容,该内容是 Java 的表示形式。请注意,在此输出中,`xs` _不是_ 定义为 `List[Int]` ;它基本上表示为 `List[java.lang.Object]` 。为了使您的 Scala 代码与 JVM 配合使用,`Int` 类型已被擦除。 +该 `javap` 命令输出显示了类文件中包含的内容,该内容是 Java 的表示形式。请注意,在此输出中,`xs` _不是_ 定义为 `List[Int]` ;它真正表示的是 `List[java.lang.Object]` 。为了使您的 Scala 代码与 JVM 配合使用,`Int` 类型已被擦除。 稍后,当您在 Scala 代码中访问 `List[Int]` 的元素时,像这样: @@ -88,13 +88,13 @@ val x = xs.get(0).asInstanceOf[Int] // more Scala-like ``` 同样,这样做是为了兼容性,因此您的 Scala 代码可以在 JVM 上运行。但是,我们已经有的整数列表的信息在类文件中丢失了。 -当尝试使用已编译的库来编译 Scala 程序时,会带来问题。为此,我们需要比类文件中通常可用的信息更多的信息。 +当尝试使用已编译的库来编译 Scala 程序时,会带来问题。为此,我们需要的信息比类文件中通常可用的信息更多。 此讨论仅涵盖类型擦除的主题。对于 JVM 没有意识到的所有其他 Scala 结构,也存在类似的问题,包括 unions, intersections, 带有参数的 traits 以及更多 Scala 3 特性。 ### TASTy to the Rescue -因此,TASTy 格式不是没有关于 _.class_ 文件中原始类型的信息,或者只有公共 API(如Scala 2.13 “Pickle” 格式),而是在类型检查后存储完整的抽象语法树(AST)。存储整个 AST 有很多优点:它支持单独编译,针对不同的 JVM 版本重新编译,程序的静态分析等等。 +因此,TASTy 格式不是像 _.class_ 文件那样没有原始类型的信息,或者只有公共 API(如Scala 2.13 “Pickle” 格式),而是在类型检查后存储完整的抽象语法树(AST)。存储整个 AST 有很多优点:它支持单独编译,针对不同的 JVM 版本重新编译,程序的静态分析等等。 ### 重点 @@ -136,7 +136,7 @@ val x = xs.get(0).asInstanceOf[Int] // more Scala-like - [Scala 宏库](https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html) - [宏:Scala 3 的计划](https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html) - [Quotes Reflect 的参考文档][quotes-reflect] -- [宏的参考文档](macros) +- [宏的参考文档][macros] [benefits]: https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html [erasure]: https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#type-erasure diff --git a/_zh-cn/scala3/reference/README.md b/_zh-cn/scala3/reference/README.md new file mode 100644 index 0000000000..4606ed70d9 --- /dev/null +++ b/_zh-cn/scala3/reference/README.md @@ -0,0 +1,5 @@ +https://docs.scala-lang.org/scala3/reference 的页面内容是从 [Scala 3 编译器库](https://github.com/lampepfl/dotty) 生成的。 + +请到这里为 Scala 3 参考文档做贡献: + +https://github.com/lampepfl/dotty/tree/main/docs/_docs From 3edca3b9ea1c47a6fa954acf63cc97f9f6652b6c Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Mon, 16 May 2022 19:56:49 +0800 Subject: [PATCH 05/53] add few chapters in _zh-cn/overviews/scala3-book --- .../scala3-book/taste-control-structures.md | 270 ++++++++++++++++++ .../overviews/scala3-book/taste-functions.md | 62 ++++ .../scala3-book/taste-hello-world.md | 53 ++++ _zh-cn/overviews/scala3-book/taste-intro.md | 16 ++ _zh-cn/overviews/scala3-book/taste-methods.md | 129 +++++++++ .../overviews/scala3-book/taste-modeling.md | 215 ++++++++++++++ _zh-cn/overviews/scala3-book/taste-repl.md | 59 ++++ .../scala3-book/taste-vars-data-types.md | 198 +++++++++++++ 8 files changed, 1002 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/taste-control-structures.md create mode 100644 _zh-cn/overviews/scala3-book/taste-functions.md create mode 100644 _zh-cn/overviews/scala3-book/taste-hello-world.md create mode 100644 _zh-cn/overviews/scala3-book/taste-intro.md create mode 100644 _zh-cn/overviews/scala3-book/taste-methods.md create mode 100644 _zh-cn/overviews/scala3-book/taste-modeling.md create mode 100644 _zh-cn/overviews/scala3-book/taste-repl.md create mode 100644 _zh-cn/overviews/scala3-book/taste-vars-data-types.md diff --git a/_zh-cn/overviews/scala3-book/taste-control-structures.md b/_zh-cn/overviews/scala3-book/taste-control-structures.md new file mode 100644 index 0000000000..caefdfbe5b --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-control-structures.md @@ -0,0 +1,270 @@ +--- +title: 控制结构 +type: section +description: This section demonstrates Scala 3 control structures. +num: 8 +previous-page: taste-vars-data-types +next-page: taste-modeling +--- + + +Scala 具有您在其他编程语言中可以找到的控制结构,并且还具有强大的 `for` 表达式和 `match` 表达式: + +- `if`/`else` +- `for` 循环和表达式 +- `match` 表达式 +- `while` 循环 +- `try`/`catch` + +这些结构在以下示例中进行了说明。 + +## `if`/`else` + +Scala 的 `if`/`else` 控制结构看起来与其他语言相似: + +```scala +if x < 0 then + println("negative") +else if x == 0 then + println("zero") +else + println("positive") +``` + +请注意,这确实是一个 _表达式_ ---不是一个 _语句_。 +这意味着它返回一个值,因此您可以将结果赋值给一个变量: + +```scala +val x = if a < b then a else b +``` + +正如您将在本书中看到的那样,_所有_ Scala 控制结构都可以用作表达式。 + +> 表达式返回结果,而语句不返回。 +> 语句通常用于它们的副作用,例如使用 `println` 打印到控制台。 + +在 Scala 2 中 `if`/`else` 控制结构的构造不同,需要括号和大括号,而不是关键字 `then`。 + +```scala +// Scala 2 syntax +if (test1) { + doX() +} else if (test2) { + doY() +} else { + doZ() +} +``` + +## `for` 循环和表达式 + +`for` 关键字用于创建 `for` 循环。 +这个例子展示了如何打印 `List` 中的每个元素: + +```scala +val ints = List(1, 2, 3, 4, 5) + +for i <- ints do println(i) +``` + +代码 `i <- ints` 被称为 _生成器_,紧随 `do` 关键字的代码是 _循环体_。 + +Scala 2 中这种控制结构的旧语法是: + +```scala +// Scala 2 语法 +for (i <- ints) println(i) +``` + +再次注意括号的使用和 Scala 3 中新的 `for`-`do`。 + +### 守卫 + +您还可以在 `for` 循环中使用一个或多个 `if` 表达式。 +这些被称为 _守卫_。 +此示例打印 `ints` 中大于 `2` 的所有数字: + +```scala +for + i <- ints + if i > 2 +do + println(i) +``` + +您可以使用多个生成器和守卫。 +此循环遍历数字 `1` 到 `3`,并且对于每个数字,它还遍历字符 `a` 到 `c`。 +然而,它也有两个守卫,所以唯一一次调用 print 语句是当 `i` 的值为 `2` 并且 `j` 是字符 `b` 时: + +```scala +for + i <- 1 to 3 + j <- 'a' to 'c' + if i == 2 + if j == 'b' +do + println(s"i = $i, j = $j") // prints: "i = 2, j = b" +``` + +### `for` 表达式 + +`for` 关键字更强大:当您使用 `yield` 关键字代替 `do` 时,您会创建 `for` _表达式_ 用于计算和产生结果。 + +几个例子演示了这一点。 +使用与上一个示例相同的 `ints` 列表,此代码创建一个新列表,其中新列表中每个元素的值是原始列表中元素值的两倍: + +```` +scala> val doubles = for i <- ints yield i * 2 +val doubles: List[Int] = List(2, 4, 6, 8, 10) +```` + +Scala 的控制结构语法很灵活,`for` 表达式可以用其他几种方式编写,具体取决于您的偏好: + +```scala +val doubles = for i <- ints yield i * 2 // 如上所示的风格 +val doubles = for (i <- ints) yield i * 2 +val doubles = for (i <- ints) yield (i * 2) +val doubles = for { i <- ints } yield (i * 2) +``` + +此示例显示如何将列表中每个字符串的第一个字符大写: + +```scala +val names = List("chris", "ed", "maurice") +val capNames = for name <- names yield name.capitalize +``` + +最后,这个 `for` 表达式遍历一个字符串列表,并返回每个字符串的长度,但前提是该长度大于 `4`: + +```scala +val fruits = List("apple", "banana", "lime", "orange") + +val fruitLengths = for + f <- fruits + if f.length > 4 +yield + // 在这里你可以 + // 使用多行代码 + f.length + +//fruitLengths: List[Int] = List(5, 6, 6) +``` + +`for` 循环和表达式更多细节在本书的 [控制结构部分][control] 中,和 [参考文档]({{ site.scala3ref }}/other-new-features/control-syntax.html) 中。 + +## `match` 表达式 + +Scala 有一个 `match` 表达式,它最基本的用途类似于 Java `switch` 语句: + +```scala +val i = 1 + +// later in the code ... +i match + case 1 => println("one") + case 2 => println("two") + case _ => println("other") +``` + +但是,`match` 确实是一个表达式,这意味着它会根据模式匹配返回一个结果,您可以将其绑定到一个变量: + +```scala +val result = i match + case 1 => "one" + case 2 => "two" + case _ => "other" +``` + +`match` 不仅限于使用整数值,它可以用于任何数据类型: + +```scala +val p = Person("Fred") + +// later in the code +p match + case Person(name) if name == "Fred" => + println(s"$name says, Yubba dubba doo") + + case Person(name) if name == "Bam Bam" => + println(s"$name says, Bam bam!") + + case _ => println("Watch the Flintstones!") +``` + +事实上,`match` 表达式可以用许多模式的不同类型来测试变量。 +此示例显示 (a) 如何使用 `match` 表达式作为方法的主体,以及 (b) 如何匹配显示的所有不同类型: + +```scala +// getClassAsString 是一个接受任何类型的单个参数的方法。 +def getClassAsString(x: Matchable): String = x match + case s: String => s"'$s' is a String" + case i: Int => "Int" + case d: Double => "Double" + case l: List[_] => "List" + case _ => "Unknown" + +// examples +getClassAsString(1) // Int +getClassAsString("hello") // 'hello' is a String +getClassAsString(List(1, 2, 3)) // List +``` + +`getClassAsString` 方法将 [Matchable]({{ site.scala3ref }}/other-new-features/matchable.html) 类型的值作为参数,它可以是 +任何支持模式匹配的类型(某些类型不支持模式匹配,因为这可能 +打破封装)。 + +Scala 中的模式匹配还有 _更多_ 内容。 +模式可以嵌套,模式的结果可以绑定,模式匹配甚至可以是用户自定义的。 +有关详细信息,请参阅 [控制结构章节][control] 中的模式匹配示例。 + +## `try`/`catch`/`finally` + +Scala 的 `try`/`catch`/`finally` 控制结构让你捕获异常。 +它类似于 Java,但其语法与 `match` 表达式一致: + +```scala +try + writeTextToFile(text) +catch + case ioe: IOException => println("Got an IOException.") + case nfe: NumberFormatException => println("Got a NumberFormatException.") +finally + println("Clean up your resources here.") +``` + +## `while` 循环 + +Scala 还有一个 `while` 循环结构。 +它的单行语法如下所示: + +```scala +while x >= 0 do x = f(x) +``` + +在 Scala 2 中,语法有点不同。条件用括号括起来,并且 +没有 `do` 关键字: + +```scala +while (x >= 0) { x = f(x) } +``` + +为了兼容性,Scala 3 仍然支持 Scala 2 语法。 + +`while` 循环多行语法如下所示: + +```scala +var x = 1 + +while + x < 3 +do + println(x) + x += 1 +``` + +## 自定义控制结构 + +由于 by-name 参数、中缀表示法、流畅接口、可选括号、扩展方法和高阶函数等功能,您还可以创建自己的代码,就像控制结构一样工作。 +您将在 [控制结构][control] 部分了解更多信息。 + +[control]: {% link _overviews/scala3-book/control-structures.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-functions.md b/_zh-cn/overviews/scala3-book/taste-functions.md new file mode 100644 index 0000000000..b458e09a59 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-functions.md @@ -0,0 +1,62 @@ +--- +title: First-Class Functions +type: section +description: This page provides an introduction to functions in Scala 3. +num: 11 +previous-page: taste-methods +next-page: taste-objects +--- + + +Scala具有函数式编程语言中您期望的大多数功能,包括: + +- Lambdas(匿名函数) +- 高阶函数 (HOFs) +- 标准库中的不可变集合 + +Lambdas(也称为 _匿名函数_)是保持代码简洁但可读性的重要组成部分。 + +`List` 类的 `map` 方法是高阶函数的典型示例---一个将函数作为参数的函数。 + +这两个示例是等效的,并演示如何通过将 lambda 传递到 `map` 方法中,将列表中的每个数字乘以 `2`: + +```scala +val a = List(1, 2, 3).map(i => i * 2) // List(2,4,6) +val b = List(1, 2, 3).map(_ * 2) // List(2,4,6) +``` + +这些示例也等效于以下代码,该代码使用 `double` 方法而不是lambda: + +```scala +def double(i: Int): Int = i * 2 + +val a = List(1, 2, 3).map(i => double(i)) // List(2,4,6) +val b = List(1, 2, 3).map(double) // List(2,4,6) +``` + +> 如果您以前从未见过 `map` 方法,它会将给定的函数应用于列表中的每个元素,从而生成一个包含结果值的新列表。 + +将 lambda 传递给集合类上的高阶函数(如 `List`)是 Scala 体验的一部分,您每天都会这样做。 + +## 不可变集合 + +当您使用不可变集合(如 `List`,`Vector`)以及不可变的 `Map` 和 `Set` 类时,重要的是要知道这些函数不会改变它们被调用的集合;相反,它们返回包含更新数据的新集合。 +因此,以“流式”的风格将它们链接在一起以解决问题也很常见。 + +例如,此示例演示如何对一个集合进行两次筛选,然后将其余集合中的每个元素乘某个数: + +```scala +// a sample list +val nums = (1 to 10).toList // List(1,2,3,4,5,6,7,8,9,10) + +// methods can be chained together as needed +val x = nums.filter(_ > 3) + .filter(_ < 7) + .map(_ * 10) + +// result: x == List(40, 50, 60) +``` + +除了在整个标准库中使用的高阶函数外,您还可以[创建自己的][higher-order] 高阶函数。 + +[higher-order]: {% link _overviews/scala3-book/fun-hofs.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-hello-world.md b/_zh-cn/overviews/scala3-book/taste-hello-world.md new file mode 100644 index 0000000000..577699528c --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-hello-world.md @@ -0,0 +1,53 @@ +--- +title: Hello, World! +type: section +description: This section demonstrates a Scala 3 'Hello, World!' example. +num: 5 +previous-page: taste-intro +next-page: taste-repl +--- + + +Scala 3 “Hello, world!” 例子展示如下。 +首先,把以下代码写入 _Hello.scala_: + +```scala +@main def hello() = println("Hello, world!") +``` + +代码中, `hello` 是方法。 +它使用 `def` 定义,并用 `@main` 注释的手段把它声明为“main”方法。 +使用 `println` 方法,它在标准输出 (STDOUT)中打印了 `"Hello, world!"` 字符串。 + +下一步,用 `scalac` 编译代码: + +```bash +$ scalac Hello.scala +``` + +如果你是从 Java 转到 Scala,`scalac` 就像 `javac`,所以该命令会创建几个文件: + +```bash +$ ls -1 +Hello$package$.class +Hello$package.class +Hello$package.tasty +Hello.scala +hello.class +hello.tasty +``` + +与 Java 一样,_.class_ 文件是字节码文件,它们已准备好在 JVM 中运行。 + +现在您可以使用 `scala` 命令运行 `hello` 方法: + +```bash +$ scala hello +Hello, world! +``` + +假设它运行成功,那么恭喜,您刚刚编译并运行了您的第一个 Scala 应用程序。 + +> 在 [Scala 工具][scala_tools] 章节中可以找到 sbt 和其他使 Scala 开发更容易的工具相关的更多信息。 + +[scala_tools]: {% link _overviews/scala3-book/scala-tools.md %} \ No newline at end of file diff --git a/_zh-cn/overviews/scala3-book/taste-intro.md b/_zh-cn/overviews/scala3-book/taste-intro.md new file mode 100644 index 0000000000..fc4294c0f7 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-intro.md @@ -0,0 +1,16 @@ +--- +title: Scala 的味道 +type: chapter +description: This chapter provides a high-level overview of the main features of the Scala 3 programming language. +num: 4 +previous-page: why-scala-3 +next-page: taste-hello-world +--- + + +本章提供的快速导览涉及 Scala 3 编程语言的主要特性。 +在本导览之后,本书的其余部分提供了有关这些特性的更多详细信息,而[参考文档][reference]提供了_许多_ 细节。 + +> 在本书中,我们建议您在[Scastie](https://scastie.scala-lang.org) 或 Scala REPL 中尝试一些示例,该示例稍后演示。 + +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_zh-cn/overviews/scala3-book/taste-methods.md b/_zh-cn/overviews/scala3-book/taste-methods.md new file mode 100644 index 0000000000..6507cab8a0 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-methods.md @@ -0,0 +1,129 @@ +--- +title: Methods +type: section +description: This section provides an introduction to defining and using methods in Scala 3. +num: 10 +previous-page: taste-modeling +next-page: taste-functions +--- + + +## Scala 方法 + +Scala 类、case 类、traits、枚举和对象都可以包含方法。 +简单方法的语法如下所示: + +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = + // the method body + // goes here +``` + +这有一些例子: + +```scala +def sum(a: Int, b: Int): Int = a + b +def concatenate(s1: String, s2: String): String = s1 + s2 +``` + +您不必声明方法的返回类型,因此如果您愿意,可以像这样编写这些方法: + +```scala +def sum(a: Int, b: Int) = a + b +def concatenate(s1: String, s2: String) = s1 + s2 +``` + +这是你如何调用这些方法: + +```scala +val x = sum(1, 2) +val y = concatenate("foo", "bar") +``` + +这是一个多行的方法: + +```scala +def getStackTraceAsString(t: Throwable): String = + val sw = new StringWriter + t.printStackTrace(new PrintWriter(sw)) + sw.toString +``` + +方法参数也可以具有默认值。 +在此示例中,`timeout` 参数的默认值为 `5000`: + +```scala +def makeConnection(url: String, timeout: Int = 5000): Unit = + println(s"url=$url, timeout=$timeout") +``` + +由于方法声明中提供了默认的 `超时` 值,因此可以通过以下两种方式调用该方法: + +```scala +makeConnection("https://localhost") // url=http://localhost, timeout=5000 +makeConnection("https://localhost", 2500) // url=http://localhost, timeout=2500 +``` + +Scala 还支持在调用方法时使用 _命名参数_,因此如果您愿意,也可以像这样调用该方法: + +```scala +makeConnection( + url = "https://localhost", + timeout = 2500 +) +``` + +当多个方法参数具有相同的类型时,命名参数特别有用。 +乍一看,使用此方法,您可能想知道哪些参数设置为 `true` 或 `false`: + +```scala +engage(true, true, true, false) +``` + +如果没有IDE的帮助,那段代码可能很难阅读,但这个代码要明显得多: + +```scala +engage( + speedIsSet = true, + directionIsSet = true, + picardSaidMakeItSo = true, + turnedOffParkingBrake = false +) +``` + +## 扩展方法 + +_扩展方法_ 允许您向封闭类添加新方法。 +例如,如果要将两个名为 `hello` 和 `aloha` 的方法添加到 `String` 类中,请将它们声明为扩展方法: + +```scala +extension (s: String) + def hello: String = s"Hello, ${s.capitalize}!" + def aloha: String = s"Aloha, ${s.capitalize}!" + +"world".hello // "Hello, World!" +"friend".aloha // "Aloha, Friend!" +``` + +`extension` 关键字声明了括号内的参数将定义一个或多个扩展方法。 +如此示例所示,可以在扩展方法体中使用 `String` 类型的参数 `s`。 + +下一个示例演示如何将 `makeInt` 方法添加到 `String` 类。 +在这里,`makeInt` 采用一个名为 `radix` 的参数。 +该代码不考虑可能的字符串到整数转换错误,但跳过细节,示例显示了它的工作原理: + +```scala +extension (s: String) + def makeInt(radix: Int): Int = Integer.parseInt(s, radix) + +"1".makeInt(2) // Int = 1 +"10".makeInt(2) // Int = 2 +"100".makeInt(2) // Int = 4 +``` + +## See also + +Scala方法可以更强大:它们可以采用类型参数和上下文参数。 +它们在[领域建模][data-1]一节中有详细介绍。 + +[data-1]: {% link _overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-modeling.md b/_zh-cn/overviews/scala3-book/taste-modeling.md new file mode 100644 index 0000000000..89a63d6a0f --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-modeling.md @@ -0,0 +1,215 @@ +--- +title: 领域建模 +type: section +description: This section provides an introduction to data modeling in Scala 3. +num: 9 +previous-page: taste-control-structures +next-page: taste-methods +--- + + +{% comment %} +NOTE: I kept the OOP section first, assuming that most readers will be coming from an OOP background. +{% endcomment %} + +Scala 同时支持函数式编程 (FP) 和面向对象编程 (OOP),以及这两种范式的融合。 +本节简要概述了 OOP 和 FP 中的数据建模。 + +## OOP 领域建模 + +以 OOP 风格编写代码时,用于数据封装的两个主要工具是 _traits_ 和 _classes_。 + +{% comment %} +NOTE: Julien had a comment, “in OOP we don’t really model data. +It’s more about modeling operations, imho.” + +How to resolve? Is there a good DDD term to use here? +{% endcomment %} + +### traits + +Scala trait 可以用作简单的接口,但它们也可以包含抽象和具体的方法和字段,并且它们可以有参数,就像类一样。 +它们为您提供了一种将行为组织成小型模块化单元的好方法。 +稍后,当您想要创建属性和行为的具体实现时,类和对象可以扩展特征,根据需要混合尽可能多的特征以实现所需的行为。 + +作为如何将 traits 用作接口的示例,以下是三个 traits,它们为狗和猫等动物定义了结构良好并且模块化的行为: + +```scala +trait Speaker: + def speak(): String // 没有函数体,这样它是抽象的。 + +trait TailWagger: + def startTail(): Unit = println("tail is wagging") + def stopTail(): Unit = println("tail is stopped") + +trait Runner: + def startRunning(): Unit = println("I’m running") + def stopRunning(): Unit = println("Stopped running") +``` + +鉴于这些特征,这里有一个 `Dog` 类,它扩展了所有这些特征,同时为抽象 `speak` 方法提供了一种行为: + +```scala +class Dog(name: String) extends Speaker, TailWagger, Runner: + def speak(): String = "Woof!" +``` + +请注意该类如何使用 `extends` 关键字扩展 traits。 + +类似地,这里有一个 `Cat` 类,它实现了这些相同的 traits,同时还覆盖了它继承的两个具体方法: + +```scala +class Cat(name: String) extends Speaker, TailWagger, Runner: + def speak(): String = "Meow" + override def startRunning(): Unit = println("Yeah ... I don’t run") + override def stopRunning(): Unit = println("No need to stop") +``` + +这些示例显示了如何使用这些类: + +```scala +val d = Dog("Rover") +println(d.speak()) // prints "Woof!" + +val c = Cat("Morris") +println(c.speak()) // "Meow" +c.startRunning() // "Yeah ... I don’t run" +c.stopRunning() // "No need to stop" +``` + +如果该代码有意义---太好了,您把 traits 作为接口感到舒服。 +如果没有,请不要担心,它们在 [Domain Modeling][data-1] 章节中有更详细的解释。 + +### 类 + +Scala _classes_ 用于 OOP 风格的编程。 +这是一个模拟“人”的类的示例。在 OOP 中,字段通常是可变的,所以 `firstName` 和 `lastName` 都被声明为 `var` 参数: + +```scala +class Person(var firstName: String, var lastName: String): + def printFullName() = println(s"$firstName $lastName") + +val p = Person("John", "Stephens") +println(p.firstName) // "John" +p.lastName = "Legend" +p.printFullName() // "John Legend" +``` + +请注意,类声明创建了一个构造函数: + +```斯卡拉 +// 此代码使用该构造函数 +val p = Person("约翰", "斯蒂芬斯") +``` + +[Domain Modeling][data-1] 章节中介绍了构造函数和其他与类相关的主题。 + +## FP 领域建模 + +{% comment %} +NOTE: Julien had a note about expecting to see sealed traits here. +I didn’t include that because I didn’t know if enums are intended +to replace the Scala2 “sealed trait + case class” pattern. How to resolve? +{% endcomment %} + +以 FP 风格编写代码时,您将使用以下结构: + +- 枚举来定义 ADT +- Case类 +- Traits + +### 枚举 + +`enum` 构造是在 Scala 3 中对代数数据类型 (ADT) 进行建模的好方法。 +例如,披萨具有三个主要属性: + +- 面饼大小 +- 面饼类型 +- 馅料 + +这些是用枚举简洁地建模的: + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` + +一旦你有了一个枚举,你就可以按照你通常使用特征、类或对象的所有方式来使用枚举: + +```scala +import CrustSize.* +val currentCrustSize = Small + +// enums in a `match` expression +currentCrustSize match + case Small => println("Small crust size") + case Medium => println("Medium crust size") + case Large => println("Large crust size") + +// enums in an `if` statement +if currentCrustSize == Small then println("Small crust size") +``` + +下面是另一个如何在 Scala 中创建和使用 ADT 的示例: + +```scala +enum Nat: + case Zero + case Succ(pred: Nat) +``` + +枚举在本书的 [领域建模][data-1] 部分和 [参考文档]({{ site.scala3ref }}/enums/enums.html) 中有详细介绍。 + +### Case 类 + +Scala `case` 类允许您使用不可变数据结构对概念进行建模。 +`case` 类具有 `class` 的所有功能,还包含其他功能,使它们对函数式编程很有用。 +当编译器在 `class` 前面看到 `case` 关键字时,它具有以下效果和好处: + +- Case 类构造函数参数默认为 public `val` 字段,因此字段是不可变的,并且为每个参数生成访问器方法。 +- 生成一个 `unapply` 方法,它允许您在 `match` 表达式中以更多方式使用 case 类。 +- 在类中生成一个 `copy` 方法。 + 这提供了一种在不更改原始对象的情况下创建对象的更新副本的方法。 +- 生成 `equals` 和 `hashCode` 方法来实现结构相等等式。 +- 生成一个默认的 `toString` 方法,有助于调试。 + +{% comment %} +NOTE: Julien had a comment about how he decides when to use case classes vs classes. Add something here? +{% endcomment %} + +您_可以_自己手动将所有这些方法添加到一个类中,但是由于这些功能在函数式编程中非常常用,因此使用“case”类要方便得多。 + +这段代码演示了几个 `case` 类的特性: + +```scala +// define a case class +case class Person( + name: String, + vocation: String +) + +// create an instance of the case class +val p = Person("Reginald Kenneth Dwight", "Singer") + +// a good default toString method +p // : Person = Person(Reginald Kenneth Dwight,Singer) + +// can access its fields, which are immutable +p.name // "Reginald Kenneth Dwight" +p.name = "Joe" // error: can’t reassign a val field + +// when you need to make a change, use the `copy` method +// to “update as you copy” +val p2 = p.copy(name = "Elton John") +p2 // : Person = Person(Elton John,Singer) +``` + +有关 `case` 类的更多详细信息,请参阅 [领域建模][data-1] 部分。 + +[data-1]: {% link _overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-repl.md b/_zh-cn/overviews/scala3-book/taste-repl.md new file mode 100644 index 0000000000..1f4c892027 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-repl.md @@ -0,0 +1,59 @@ +--- +title: The REPL +type: section +description: This section provides an introduction to the Scala REPL. +num: 6 +previous-page: taste-hello-world +next-page: taste-vars-data-types +--- + + +Scala REPL(“Read-Evaluate-Print-Loop”)是一个命令行解释器,您可以将其用作“游乐场”区域来测试 Scala 代码。 +你可以通过运行 `scala` 或 `scala3` 命令来启动一个 REPL 会话,具体取决于您在操作系统命令行中的安装,您将看到如下所示的“欢迎”提示: + +```bash +$ scala +Welcome to Scala 3.0.0 (OpenJDK 64-Bit Server VM, Java 11.0.9). +Type in expressions for evaluation. +Or try :help. + +scala> _ +``` + +REPL 是一个命令行解释器,所以它就在那里等着你输入一些东西。 +现在您可以输入 Scala 表达式来查看它们是如何工作的: + +```` +scala> 1 + 1 +val res0: Int = 2 + +scala> 2 + 2 +val res1: Int = 4 +```` + +如输出所示,如果您不为表达式的结果分配变量,REPL 会为您创建名为 `res0`、`res1` 等的变量。 +您可以在后续表达式中使用这些变量名称: + +```` +scala> val x = res0 * 10 +val x: Int = 20 +```` + +请注意,REPL 输出还显示了表达式的结果。 + +您可以在 REPL 中运行各种实验。 +这个例子展示了如何创建然后调用一个 `sum` 方法: + +```` +scala> def sum(a: Int, b: Int): Int = a + b +def sum(a: Int, b: Int): Int + +scala> sum(2, 2) +val res2: Int = 4 +```` + +如果您更喜欢基于浏览器的游乐场环境,也可以使用 [scastie.scala-lang.org](https://scastie.scala-lang.org)。 + +如果您更喜欢在文本编辑器中而不是在控制台提示符中编写代码,您可以使用 [worksheet]。 + +[worksheet]: {% link _overviews/scala3-book/tools-worksheets.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-vars-data-types.md b/_zh-cn/overviews/scala3-book/taste-vars-data-types.md new file mode 100644 index 0000000000..8864c23773 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-vars-data-types.md @@ -0,0 +1,198 @@ +--- +title: 变量和数据类型 +type: section +description: This section demonstrates val and var variables, and some common Scala data types. +num: 7 +previous-page: taste-repl +next-page: taste-control-structures +--- + + + +本节介绍 Scala 变量和数据类型。 + +## 两种类型的变量 + +当你在 Scala 中创建一个新变量时,你声明该变量是不可变的还是可变的: + + + + + + + + + + + + + + + + + + +
变量类型说明
val创建一个不可变变量——类似于 Java 中的 final。 您应该始终使用 val 创建一个变量,除非有理由使用可变变量。
var创建一个可变变量,并且只应在变量的内容随时间变化时使用。
+ +这些示例展示了如何创建 `val` 和 `var` 变量: + +```scala +// immutable +val a = 0 + +// mutable +var b = 1 +``` + +在应用程序中,不能重新给一个 `val` 变量赋值。 +如果您尝试重新赋值一个 `val` 变量,将导致编译器错误: + +```scala +val msg = "Hello, world" +msg = "Aloha" // "reassignment to val" error; this won’t compile +``` + +相反,可以重新分配一个 `var` 变量: + +```scala +var msg = "Hello, world" +msg = "Aloha" // 因为可以重新分配 var,所以可以编译 +``` + +## 声明变量类型 + +创建变量时,您可以显式声明其类型,或让编译器推断类型: + +```scala +val x: Int = 1 // 显式 +val x = 1 // 隐式的;编译器推断类型 +``` + +第二种形式称为 _类型推断_,它是帮助保持此类代码简洁的好方法。 +Scala 编译器通常可以为您推断数据类型,如以下 REPL 示例的输出所示: + +```scala +scala> val x = 1 +val x: Int = 1 + +scala> val s = "a string" +val s: String = a string + +scala> val nums = List(1, 2, 3) +val nums: List[Int] = List(1, 2, 3) +``` + +如果您愿意,您始终可以显式声明变量的类型,但在像这样的简单赋值中,不须要这样: + +```scala +val x: Int = 1 +val s: String = "a string" +val p: Person = Person("Richard") +``` + +请注意,使用这种方法,代码感觉太啰嗦。 + +{% comment %} +TODO: Jonathan had an early comment on the text below: “While it might feel like this, I would be afraid that people automatically assume from this statement that everything is always boxed.” Suggestion on how to change this? +{% endcomment %} + +## 内置数据类型 + +Scala 带有你所期望的标准数值数据类型,它们都是类的成熟(full-blown)实例。 +在 Scala 中,一切都是对象。 + +这些示例展示了如何声明数字类型的变量: + +```scala +val b: Byte = 1 +val i: Int = 1 +val l: Long = 1 +val s: Short = 1 +val d: Double = 2.0 +val f: Float = 3.0 +``` + +因为 `Int` 和 `Double` 是默认的数字类型,所以您通常创建它们而不显式声明数据类型: + +```scala +val i = 123 // 默认为 Int +val j = 1.0 // 默认为 Double +``` + +在您的代码中,您还可以将字符 `L`、`D` 和 `F`(或者它们对应的小写字母)加到数字后面以指定它们是 `Long`、`Double` 或 `Float` 值: + +```scala +val x = 1_000L // val x: Long = 1000 +val y = 2.2D // val y: Double = 2.2 +val z = 3.3F // val z: Float = 3.3 +``` + +当您需要非常大的数字时,请使用 `BigInt` 和 `BigDecimal` 类型: + +```scala +var a = BigInt(1_234_567_890_987_654_321L) +var b = BigDecimal(123_456.789) +``` + +其中 `Double` 和 `Float` 是近似十进制数,`BigDecimal` 用于精确算术。 + +Scala 还有 `String` 和 `Char` 数据类型: + +```scala +val name = "Bill" // String +val c = 'a' // Char +``` + +### 字符串 + +Scala 字符串类似于 Java 字符串,但它们有两个很棒的附加特性: + +- 他们支持字符串插值 +- 创建多行字符串很容易 + +#### 字符串插值 + +字符串插值提供了一种非常易读的方式在字符串中使用变量。 +例如,给定这三个变量: + +```scala +val firstName = "John" +val mi = 'C' +val lastName = "Doe" +``` + +您可以将这些变量组合在一个字符串中,如下所示: + +```scala +println(s"Name: $firstName $mi $lastName") // "Name: John C Doe" +``` + +只需在字符串前面加上字母 `s`,然后在字符串中的变量名之前放置一个 `$` 符号。 + +要将任意表达式嵌入字符串中,请将它们括在花括号中: + +``` scala +println(s"2 + 2 = ${2 + 2}") // 打印 "2 + 2 = 4" + +val x = -1 +println(s"x.abs = ${x.abs}") // 打印 "x.abs = 1" +``` + +放在字符串前面的 `s` 只是一种可能的插值器。 +如果使用 `f` 而不是 `s`,则可以在字符串中使用 `printf` 样式的格式化语法。 +此外,字符串插值器只是一种特殊方法,可以定义自己的方法。 +例如,一些数据库的程序库定义了非常强大的 `sql` 插值器。 + +#### 多行字符串 + +多行字符串是通过将字符串包含在三个双引号内来创建的: + +```scala +val quote = """The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting.""" +``` + +> 有关字符串插值器和多行字符串的更多详细信息,请参阅[“First Look at Types”章节][first-look]。 + +[first-look]: {% link _overviews/scala3-book/first-look-at-types.md %} From e732e9e79ddbacd037f7109c1b3729b9066a00cf Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Tue, 17 May 2022 16:19:40 +0800 Subject: [PATCH 06/53] translated all tastes files. in scala3-book. --- .../scala3-book/taste-collections.md | 108 ++++++++++++++++++ .../taste-contextual-abstractions.md | 54 +++++++++ _zh-cn/overviews/scala3-book/taste-objects.md | 90 +++++++++++++++ _zh-cn/overviews/scala3-book/taste-summary.md | 29 +++++ .../scala3-book/taste-toplevel-definitions.md | 62 ++++++++++ 5 files changed, 343 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/taste-collections.md create mode 100644 _zh-cn/overviews/scala3-book/taste-contextual-abstractions.md create mode 100644 _zh-cn/overviews/scala3-book/taste-objects.md create mode 100644 _zh-cn/overviews/scala3-book/taste-summary.md create mode 100644 _zh-cn/overviews/scala3-book/taste-toplevel-definitions.md diff --git a/_zh-cn/overviews/scala3-book/taste-collections.md b/_zh-cn/overviews/scala3-book/taste-collections.md new file mode 100644 index 0000000000..e1845e2b0b --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-collections.md @@ -0,0 +1,108 @@ +--- +title: Collections +type: section +description: This page provides a high-level overview of the main features of the Scala 3 programming language. +num: 13 +previous-page: taste-objects +next-page: taste-contextual-abstractions +--- + + +Scala 库具有一组丰富的集合类,这些类具有一组丰富的方法。 +集合类有不可变和可变两种形式。 + +## 创建列表 + +为了让您了解这些类的工作原理,下面是一些使用 `List` 类的示例,该类是不可变的链接列表类。 +这些示例显示了创建填充的 `List` 的不同方法: + +```scala +val a = List(1, 2, 3) // a: List[Int] = List(1, 2, 3) + +// Range methods +val b = (1 to 5).toList // b: List[Int] = List(1, 2, 3, 4, 5) +val c = (1 to 10 by 2).toList // c: List[Int] = List(1, 3, 5, 7, 9) +val e = (1 until 5).toList // e: List[Int] = List(1, 2, 3, 4) +val f = List.range(1, 5) // f: List[Int] = List(1, 2, 3, 4) +val g = List.range(1, 10, 3) // g: List[Int] = List(1, 4, 7) +``` + +## `List`方法 + +拥有填充的列表后,以下示例将显示可以对其调用的一些方法。 +请注意,这些都是函数式方法,这意味着它们不会改变调用的集合,而是返回包含更新元素的新集合。 +每个表达式返回的结果显示在每行的注释中: + +```scala +// a sample list +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) + +a.drop(2) // List(30, 40, 10) +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ < 25) // List(10, 20, 10) +a.slice(2,4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeWhile(_ < 30) // List(10, 20) + +// flatten +val a = List(List(1,2), List(3,4)) +a.flatten // List(1, 2, 3, 4) + +// map, flatMap +val nums = List("one", "two") +nums.map(_.toUpperCase) // List("ONE", "TWO") +nums.flatMap(_.toUpperCase) // List('O', 'N', 'E', 'T', 'W', 'O') +``` + +这些示例显示了如何使用 `foldLeft` 和 `reduceLeft` 方法来对整数序列中的值求和: + +```scala +val firstTen = (1 to 10).toList // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +firstTen.reduceLeft(_ + _) // 55 +firstTen.foldLeft(100)(_ + _) // 155 (100 is a “seed” value) +``` + +Scala 集合类还有更多可用的方法,它们在[集合章节][collections]和 [API 文档][api]中进行了演示。 + +## 元组 + +Scala _元组_ 是一种类型,可让您轻松地将不同类型的集合放在同一个容器中。 +例如,给定以下 `Person` case 类: + +```scala +case class Person(name: String) +``` + +这是演示你如创建一个元组,这个元组包含 `Int`, `String`, 和定制的 `Person` 值: + +```scala +val t = (11, "eleven", Person("Eleven")) +``` + +有元组后,可以通过将其值绑定到变量来访问,也可以通过数字访问它们: + +```scala +t(0) // 11 +t(1) // "eleven" +t(2) // Person("Eleven") +``` + +您还可以使用以下 _解析器_ 的办法将元组字段分配变量名: + +```scala +val (num, str, person) = t + +// result: +// val num: Int = 11 +// val str: String = eleven +// val person: Person = Person(Eleven) +``` + +有些情况更适合使用元组, 那就是当你想要将异构类型的集合放在一个小的类似集合的结构中。 +有关更多元组详细信息,请参阅 [参考文档][reference]。 + +[collections]: {% link _overviews/scala3-book/collections-intro.md %} +[api]: https://scala-lang.org/api/3.x/ +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_zh-cn/overviews/scala3-book/taste-contextual-abstractions.md b/_zh-cn/overviews/scala3-book/taste-contextual-abstractions.md new file mode 100644 index 0000000000..f845a0b8d4 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-contextual-abstractions.md @@ -0,0 +1,54 @@ +--- +title: Contextual Abstractions +type: section +description: This section provides an introduction to Contextual Abstractions in Scala 3. +num: 14 +previous-page: taste-collections +next-page: taste-toplevel-definitions +--- + + +{% comment %} +TODO: Now that this is a separate section, it needs a little more content. +{% endcomment %} + +在某些情况下,可以省略在方法调用中你认为是重复的的某些参数。 + +这些参数之所以称为 _上下文参数_,是因为它们是由编译器从方法调用周围的上下文中推断出来的。 + +例如,考虑一个程序,该程序按两个条件对地址列表进行排序:城市名称,然后是街道名称。 + +```scala +val addresses: List[Address] = ... + +addresses.sortBy(address => (address.city, address.street)) +``` + +`sortBy` 方法调用一个函数,该函数为每个地址返回值,这个值会用来与其他地址比较。 +在本例中,我们传递一个函数,该函数返回一对,该对包含城市名称和街道名称。 + +请注意,我们只指示 _怎么_ 比较的,而不是 _如何_ 来执行比较。 +排序算法如何知道如何比较 `String` 对的? + +实际上,`sortBy` 方法采用第二个参数---一个上下文参数---该参数由编译器推断。 +它不会出现在上面的示例中,因为它是由编译器提供的。 + +第二个参数实现 _如何_ 进行比较。 +省略它很方便,因为我们知道 `String` 通常使用词典顺序进行比较。 + +但是,也可以显式传递它: + +```scala +addresses.sortBy(address => (address.city, address.street))(using Ordering.Tuple2(Ordering.String, Ordering.String)) +``` + +在本例中,`Ordering.Tuple2(Ordering.String, Ordering.String)` 实例正是编译器以其他方式推断的实例。 +换句话说,这两个示例生成相同的程序。 + +_上下文抽象_ 用于避免重复代码。 +它们帮助开发人员编写可扩展且同时简洁的代码段。 + +有关更多详细信息,请参阅本书的[上下文抽象章节][contextual],以及[参考文档][reference]。 + +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_zh-cn/overviews/scala3-book/taste-objects.md b/_zh-cn/overviews/scala3-book/taste-objects.md new file mode 100644 index 0000000000..a3a6be6470 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-objects.md @@ -0,0 +1,90 @@ +--- +title: Singleton Objects +type: section +description: This section provides an introduction to the use of singleton objects in Scala 3. +num: 12 +previous-page: taste-functions +next-page: taste-collections +--- + + +在 Scala 中,`object` 关键字创建一个单例对象。 +换句话说,对象定义了一个只有一个实例的类。 + +对象有多种用途: + +- 它们用于创建实用程序方法的集合。 +- _伴随对象_ 与一个类同名二者在同一个文件里。 + 在此情况下,该类也称为 _伴随类_。 +- 它们用于实现 traits,再用 traits 来创建 _模块_。 + +## “实用工具”方法 + +因为 `object` 是单例,所以它的方法可以像 Java 类中的 `static` 方法一样被访问。 +例如,此 `StringUtils` 对象包含一个与字符串相关的方法的小型集合: + +```scala +object StringUtils: + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty + def leftTrim(s: String): String = s.replaceAll("^\\s+", "") + def rightTrim(s: String): String = s.replaceAll("\\s+$", "") +``` + +由于 `StringUtils` 是一个单例,因此可以直接在对象上调用其方法: + +```scala +val x = StringUtils.isNullOrEmpty("") // true +val x = StringUtils.isNullOrEmpty("a") // false +``` + +## 伴随对象 + +伴随类或对象可以访问其伙伴的私有成员。 +对不特定于伴随类实例的方法和值使用伴随对象。 + +此示例演示了伴随类中的 `area` 方法如何访问其伴随对象中的私有 `calculateArea` 方法: + +```scala +import scala.math.* + +class Circle(radius: Double): + import Circle.* + def area: Double = calculateArea(radius) + +object Circle: + private def calculateArea(radius: Double): Double = + Pi * pow(radius, 2.0) + +val circle1 = Circle(5.0) +circle1.area // Double = 78.53981633974483 +``` + +## 从 traits 创建模块 + +对象还可用于实现创建模块的 trait。 +这种技术需要两个traits,并将它们结合起来创建一个具体的 `object`: + +```scala +trait AddService: + def add(a: Int, b: Int) = a + b + +trait MultiplyService: + def multiply(a: Int, b: Int) = a * b + +// implement those traits as a concrete object +object MathService extends AddService, MultiplyService + +// use the object +import MathService.* +println(add(1,1)) // 2 +println(multiply(2,2)) // 4 +``` + +{% comment %} +NOTE: I don’t know if this is worth keeping, but I’m leaving it here as a comment for now. + +> You may read that objects are used to _reify_ traits into modules. +> _Reify_ means, “to take an abstract concept and turn it into something concrete.” This is what happens in these examples, but “implement” is a more familiar word for most people than “reify.” +{% endcomment %} + + diff --git a/_zh-cn/overviews/scala3-book/taste-summary.md b/_zh-cn/overviews/scala3-book/taste-summary.md new file mode 100644 index 0000000000..3b6f74d6b7 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-summary.md @@ -0,0 +1,29 @@ +--- +title: Summary +type: section +description: This page provides a summary of the previous 'Taste of Scala' sections. +num: 16 +previous-page: taste-toplevel-definitions +next-page: first-look-at-types +--- + + +在前面的部分中,您看到了: + +- 如何使用 Scala REPL +- 如何使用 `val` 和 `var` 创建变量 +- 一些常见的数据类型 +- 控制结构 +- 如何使用 OOP 和 FP 样式对现实世界进行建模 +- 如何创建和使用方法 +- 如何使用lambdas(匿名函数)和高阶函数 +- 如何将对象用于多种目的 +- [上下文抽象][contextual]的介绍 + +我们还提到,如果您更喜欢使用基于浏览器的游乐场环境而不是 Scala REPL,您还可以使用[Scastie.scala-lang.org](https://scastie.scala-lang.org/?target=dotty) 或 [ScalaFiddle.io](https://scalafiddle.io)。 + +Scala还有更多功能在这次旋风之旅中没有涵盖。 +有关更多详细信息,请参阅本书的其余部分和[参考文档][reference]。 + +[reference]: {{ site.scala3ref }}/overview.html +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-toplevel-definitions.md b/_zh-cn/overviews/scala3-book/taste-toplevel-definitions.md new file mode 100644 index 0000000000..7e848d6caf --- /dev/null +++ b/_zh-cn/overviews/scala3-book/taste-toplevel-definitions.md @@ -0,0 +1,62 @@ +--- +title: Toplevel Definitions +type: section +description: This page provides an introduction to top-level definitions in Scala 3 +num: 15 +previous-page: taste-contextual-abstractions +next-page: taste-summary +--- + + +在 Scala 3 中,各种定义都可以在源代码文件的 “顶层” 编写。 +例如,您可以创建一个名为 _MyCoolApp.scala_ 的文件,并将以下内容放入其中: + +```scala +import scala.collection.mutable.ArrayBuffer + +enum Topping: + case Cheese, Pepperoni, Mushrooms + +import Topping.* +class Pizza: + val toppings = ArrayBuffer[Topping]() + +val p = Pizza() + +extension (s: String) + def capitalizeAllWords = s.split(" ").map(_.capitalize).mkString(" ") + +val hwUpper = "hello, world".capitalizeAllWords + +type Money = BigDecimal + +// more definitions here as desired ... + +@main def myApp = + p.toppings += Cheese + println("show me the code".capitalizeAllWords) +``` + +如代码中展示的,无需将这些定义放在 `package`, `class` 或其他构造中。 + +## 替换包对象 + +如果你熟悉Scala 2,这种方法可以取代 _包对象_。 +但是,虽然更易于使用,但它们的工作方式类似:当您将定义放在名为 _foo_ 的包中时,您可以在 _foo_ 包内的所有其他包内访问该定义,例如在此示例中的 _foo.bar_ 包中: + +```scala +package foo { + def double(i: Int) = i * 2 +} + +package foo { + package bar { + @main def fooBarMain = + println(s"${double(1)}") // this works + } +} +``` + +本示例中使用大括号来强调包嵌套。 + +这种方法的好处是,您可以将定义放在名为 _com.acme.myapp_ 的包下,然后可以在 _com.acme.myapp.model_、_com.acme.myapp.controller_ 等中引用这些定义。 From 74405c5b3e8f985547fea70850c581c5955d640c Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Tue, 17 May 2022 19:53:51 +0800 Subject: [PATCH 07/53] modified according to review. --- _zh-cn/overviews/scala3-book/taste-collections.md | 2 +- _zh-cn/overviews/scala3-book/taste-control-structures.md | 5 ++--- _zh-cn/overviews/scala3-book/taste-methods.md | 2 +- _zh-cn/overviews/scala3-book/taste-modeling.md | 8 ++++---- _zh-cn/overviews/scala3-book/taste-vars-data-types.md | 4 ++-- _zh-cn/overviews/scala3-book/why-scala-3.md | 8 ++++---- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/_zh-cn/overviews/scala3-book/taste-collections.md b/_zh-cn/overviews/scala3-book/taste-collections.md index e1845e2b0b..1046bf1784 100644 --- a/_zh-cn/overviews/scala3-book/taste-collections.md +++ b/_zh-cn/overviews/scala3-book/taste-collections.md @@ -69,7 +69,7 @@ Scala 集合类还有更多可用的方法,它们在[集合章节][collections ## 元组 Scala _元组_ 是一种类型,可让您轻松地将不同类型的集合放在同一个容器中。 -例如,给定以下 `Person` case 类: +例如,给定以下 `Person` 样例类: ```scala case class Person(name: String) diff --git a/_zh-cn/overviews/scala3-book/taste-control-structures.md b/_zh-cn/overviews/scala3-book/taste-control-structures.md index caefdfbe5b..ace393cf6a 100644 --- a/_zh-cn/overviews/scala3-book/taste-control-structures.md +++ b/_zh-cn/overviews/scala3-book/taste-control-structures.md @@ -210,8 +210,7 @@ getClassAsString(List(1, 2, 3)) // List ``` `getClassAsString` 方法将 [Matchable]({{ site.scala3ref }}/other-new-features/matchable.html) 类型的值作为参数,它可以是 -任何支持模式匹配的类型(某些类型不支持模式匹配,因为这可能 -打破封装)。 +任何支持模式匹配的类型(某些类型不支持模式匹配,因为这可能打破封装)。 Scala 中的模式匹配还有 _更多_ 内容。 模式可以嵌套,模式的结果可以绑定,模式匹配甚至可以是用户自定义的。 @@ -264,7 +263,7 @@ do ## 自定义控制结构 -由于 by-name 参数、中缀表示法、流畅接口、可选括号、扩展方法和高阶函数等功能,您还可以创建自己的代码,就像控制结构一样工作。 +由于传名参数、中缀表示法、流畅接口、可选括号、扩展方法和高阶函数等功能,您还可以创建自己的代码,就像控制结构一样工作。 您将在 [控制结构][control] 部分了解更多信息。 [control]: {% link _overviews/scala3-book/control-structures.md %} diff --git a/_zh-cn/overviews/scala3-book/taste-methods.md b/_zh-cn/overviews/scala3-book/taste-methods.md index 6507cab8a0..9404f4366b 100644 --- a/_zh-cn/overviews/scala3-book/taste-methods.md +++ b/_zh-cn/overviews/scala3-book/taste-methods.md @@ -10,7 +10,7 @@ next-page: taste-functions ## Scala 方法 -Scala 类、case 类、traits、枚举和对象都可以包含方法。 +Scala 类、样例类、traits、枚举和对象都可以包含方法。 简单方法的语法如下所示: ```scala diff --git a/_zh-cn/overviews/scala3-book/taste-modeling.md b/_zh-cn/overviews/scala3-book/taste-modeling.md index 89a63d6a0f..34e9c9bce8 100644 --- a/_zh-cn/overviews/scala3-book/taste-modeling.md +++ b/_zh-cn/overviews/scala3-book/taste-modeling.md @@ -115,7 +115,7 @@ to replace the Scala2 “sealed trait + case class” pattern. How to resolve? 以 FP 风格编写代码时,您将使用以下结构: - 枚举来定义 ADT -- Case类 +- 样例类 - Traits ### 枚举 @@ -166,14 +166,14 @@ enum Nat: 枚举在本书的 [领域建模][data-1] 部分和 [参考文档]({{ site.scala3ref }}/enums/enums.html) 中有详细介绍。 -### Case 类 +### 样例类 Scala `case` 类允许您使用不可变数据结构对概念进行建模。 `case` 类具有 `class` 的所有功能,还包含其他功能,使它们对函数式编程很有用。 当编译器在 `class` 前面看到 `case` 关键字时,它具有以下效果和好处: -- Case 类构造函数参数默认为 public `val` 字段,因此字段是不可变的,并且为每个参数生成访问器方法。 -- 生成一个 `unapply` 方法,它允许您在 `match` 表达式中以更多方式使用 case 类。 +- 样例类构造函数参数默认为 public `val` 字段,因此字段是不可变的,并且为每个参数生成访问器方法。 +- 生成一个 `unapply` 方法,它允许您在 `match` 表达式中以更多方式使用 样例类。 - 在类中生成一个 `copy` 方法。 这提供了一种在不更改原始对象的情况下创建对象的更新副本的方法。 - 生成 `equals` 和 `hashCode` 方法来实现结构相等等式。 diff --git a/_zh-cn/overviews/scala3-book/taste-vars-data-types.md b/_zh-cn/overviews/scala3-book/taste-vars-data-types.md index 8864c23773..4c504e9a00 100644 --- a/_zh-cn/overviews/scala3-book/taste-vars-data-types.md +++ b/_zh-cn/overviews/scala3-book/taste-vars-data-types.md @@ -101,7 +101,7 @@ TODO: Jonathan had an early comment on the text below: “While it might feel li Scala 带有你所期望的标准数值数据类型,它们都是类的成熟(full-blown)实例。 在 Scala 中,一切都是对象。 -这些示例展示了如何声明数字类型的变量: +这些示例展示了如何声明数值类型的变量: ```scala val b: Byte = 1 @@ -181,7 +181,7 @@ println(s"x.abs = ${x.abs}") // 打印 "x.abs = 1" 放在字符串前面的 `s` 只是一种可能的插值器。 如果使用 `f` 而不是 `s`,则可以在字符串中使用 `printf` 样式的格式化语法。 此外,字符串插值器只是一种特殊方法,可以定义自己的方法。 -例如,一些数据库的程序库定义了非常强大的 `sql` 插值器。 +例如,有一些数据库方向的类库定义了非常强大的 `sql` 插值器。 #### 多行字符串 diff --git a/_zh-cn/overviews/scala3-book/why-scala-3.md b/_zh-cn/overviews/scala3-book/why-scala-3.md index 8a2121caed..535be70deb 100644 --- a/_zh-cn/overviews/scala3-book/why-scala-3.md +++ b/_zh-cn/overviews/scala3-book/why-scala-3.md @@ -128,7 +128,7 @@ case class Person( ) ``` -高阶函数简洁: +简洁的高阶函数: ```scala list.filter(_ < 4) @@ -265,7 +265,7 @@ val b = List(1,2,3) // List 是不可变的 val c = Map(1 -> "one") // Map 是不可变的 ``` -Case 类主要用于 [领域建模]({% link _overviews/scala3-book/domain-modeling-intro.md %}),它们的参数是不可变的: +样例类主要用于 [领域建模]({% link _overviews/scala3-book/domain-modeling-intro.md %}),它们的参数是不可变的: ```scala case class Person(name: String) @@ -305,7 +305,7 @@ def isTruthy(a: Matchable) = a match ## 9) 生态系统库 用于函数式编程的 Scala 库,如 [Cats](https://typelevel.org/cats) 和 [Zio](https://zio.dev) 是 FP 社区中的前沿库。 -所有流行语,如高性能、类型安全、并发、异步、资源安全、可测试、功能性、模块化、二进制兼容、高效、副作用/有副作用等,都可以用于这些库。 +所有流行语,如高性能、类型安全、并发、异步、资源安全、可测试、函数式、模块化、二进制兼容、高效、副作用/有副作用等,都可以用于这些库。 我们可以在这里列出数百个库,但幸运的是它们都列在另一个位置:有关这些详细信息,请参阅 [“Awesome Scala” 列表](https://github.com/lauris/awesome-scala)。 @@ -387,7 +387,7 @@ _性能_ 涉及几个方面。 - 类型别名 - 值类 -- case类 +- 样例类 不幸的是,所有这些方法都有弱点,如 [_Opaque Types_ SIP](https://docs.scala-lang.org/sips/opaque-types.html) 中所述。 相反,如 SIP 中所述,不透明类型的目标是“对这些包装器类型的操作不得在运行时产生任何额外开销,同时在编译时仍提供类型安全使用。” From 313fdff2ac627657cfef75d441ab9f9be475176f Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Wed, 18 May 2022 11:05:52 +0800 Subject: [PATCH 08/53] correct translation according review. --- _zh-cn/overviews/scala3-book/taste-collections.md | 2 +- _zh-cn/overviews/scala3-book/taste-objects.md | 12 ++++++------ _zh-cn/overviews/scala3-book/why-scala-3.md | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/_zh-cn/overviews/scala3-book/taste-collections.md b/_zh-cn/overviews/scala3-book/taste-collections.md index 1046bf1784..5e79cb4a15 100644 --- a/_zh-cn/overviews/scala3-book/taste-collections.md +++ b/_zh-cn/overviews/scala3-book/taste-collections.md @@ -89,7 +89,7 @@ t(1) // "eleven" t(2) // Person("Eleven") ``` -您还可以使用以下 _解析器_ 的办法将元组字段分配变量名: +您还可以使用以下 _解构_ 的办法将元组字段分配变量名: ```scala val (num, str, person) = t diff --git a/_zh-cn/overviews/scala3-book/taste-objects.md b/_zh-cn/overviews/scala3-book/taste-objects.md index a3a6be6470..2cb04d4d14 100644 --- a/_zh-cn/overviews/scala3-book/taste-objects.md +++ b/_zh-cn/overviews/scala3-book/taste-objects.md @@ -14,8 +14,8 @@ next-page: taste-collections 对象有多种用途: - 它们用于创建实用程序方法的集合。 -- _伴随对象_ 与一个类同名二者在同一个文件里。 - 在此情况下,该类也称为 _伴随类_。 +- _伴生对象_ 与一个类同名二者在同一个文件里。 + 在此情况下,该类也称为 _伴生类_。 - 它们用于实现 traits,再用 traits 来创建 _模块_。 ## “实用工具”方法 @@ -37,12 +37,12 @@ val x = StringUtils.isNullOrEmpty("") // true val x = StringUtils.isNullOrEmpty("a") // false ``` -## 伴随对象 +## 伴生对象 -伴随类或对象可以访问其伙伴的私有成员。 -对不特定于伴随类实例的方法和值使用伴随对象。 +伴生类或对象可以访问其伙伴的私有成员。 +对不特定于伴生类实例的方法和值使用伴生对象。 -此示例演示了伴随类中的 `area` 方法如何访问其伴随对象中的私有 `calculateArea` 方法: +此示例演示了伴生类中的 `area` 方法如何访问其伴生对象中的私有 `calculateArea` 方法: ```scala import scala.math.* diff --git a/_zh-cn/overviews/scala3-book/why-scala-3.md b/_zh-cn/overviews/scala3-book/why-scala-3.md index 535be70deb..00c37f54af 100644 --- a/_zh-cn/overviews/scala3-book/why-scala-3.md +++ b/_zh-cn/overviews/scala3-book/why-scala-3.md @@ -24,7 +24,7 @@ NOTE: Could mention “grammar” as a way of showing that Scala isn’t a large 7. Scala 标准库具有数十种预构建的函数式方法,可节省您的时间,并大大减少编写自定义 `for` 循环和算法的需要 8. Scala 内置了“最佳实践”,它支持不可变性,匿名函数,高阶函数,模式匹配,默认情况下无法扩展的类等 9. Scala 生态系统提供世界上最现代化的 FP 库 -10. 强型式系统 +10. 强类型式系统 ## 1) FP/OOP 融合 From 300d5cde0b406a7cd0d158d865e15b62caa2f01b Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Wed, 18 May 2022 11:12:27 +0800 Subject: [PATCH 09/53] Missed one translation. Finish all corrects. --- _zh-cn/overviews/scala3-book/taste-vars-data-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/taste-vars-data-types.md b/_zh-cn/overviews/scala3-book/taste-vars-data-types.md index 4c504e9a00..c19549248d 100644 --- a/_zh-cn/overviews/scala3-book/taste-vars-data-types.md +++ b/_zh-cn/overviews/scala3-book/taste-vars-data-types.md @@ -90,7 +90,7 @@ val s: String = "a string" val p: Person = Person("Richard") ``` -请注意,使用这种方法,代码感觉太啰嗦。 +请注意,使用这种方法会感觉代码太啰嗦。 {% comment %} TODO: Jonathan had an early comment on the text below: “While it might feel like this, I would be afraid that people automatically assume from this statement that everything is always boxed.” Suggestion on how to change this? From 3f33fc4a5323239bb0e0b0fc70743f514320dcfc Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sat, 21 May 2022 11:47:00 +0800 Subject: [PATCH 10/53] add _zh-cn/overviews/scala3-book/first-look-at-types.md --- .../scala3-book/first-look-at-types.md | 269 ++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/first-look-at-types.md diff --git a/_zh-cn/overviews/scala3-book/first-look-at-types.md b/_zh-cn/overviews/scala3-book/first-look-at-types.md new file mode 100644 index 0000000000..adc9ebd91c --- /dev/null +++ b/_zh-cn/overviews/scala3-book/first-look-at-types.md @@ -0,0 +1,269 @@ +--- +title: 类型初探 +type: chapter +description: This page provides a brief introduction to Scala's built-in data types, including Int, Double, String, Long, Any, AnyRef, Nothing, and Null. +num: 17 +previous-page: taste-summary +next-page: control-structures +--- + + + +## 所有值都有一个类型 + +在 Scala 中,所有值都有一个类型,包括数值和函数。 +下图展示了类型层次结构的一个子集。 + +Scala 3 Type Hierarchy + +## Scala 类型层次结构 + +`Any` 是所有类型的超类型,也称为 **首类型**。 +它定义了某些通用方法,例如 `equals` , `hashCode` 和 `toString` 。 + +首类型 `Any` 有一个子类型 [`Matchable`][matchable],它用于标记可以执行模式匹配的所有类型。 +保证属性调用 _“参数化”_ 非常重要。 +我们不会在这里详细介绍,但总而言之,这意味着我们不能对类型为 `Any` 的值进行模式匹配,而只能对 `Matchable` 的子类型的值进行模式匹配。 +[参考文档][matchable]包含有关 `Matchable` 的更多信息。 + +`Matchable` 有两个重要的子类型: `AnyVal` 和 `AnyRef` 。 + +*`AnyVal`* 表示值类型。 +有几个预定义的值类型,它们是不可为空的: `Double`, `Float`, `Long`, `Int`, `Short`, `Byte`, `Char`, `Unit`, 和 `Boolean`。 +`Unit` 是一种值类型,它不携带有意义的信息。 +`Unit` 只有一个实例,我们可以将其称为:`()`。 + +*`AnyRef`* 表示引用类型。 +所有非值类型都定义为引用类型。 +Scala中的每个用户定义类型都是 `AnyRef` 的子类型。 +如果在Java运行时环境的上下文中使用Scala,则 `AnyRef` 对应于 `java.lang.Object`。 + +在基于语句的编程语言中, `void` 用于没有返回值的方法。 +如果您在Scala中编写没有返回值的方法,例如以下方法,则 `Unit` 用于相同的目的: + +```scala +def printIt(a: Any): Unit = println(a) +``` + +下面是一个示例,它演示了字符串、整数、字符、布尔值和函数都是 `Any` 的实例,可以像对待其他所有对象一样处理: + +```scala +val list: List[Any] = List( + "a string", + 732, // an integer + 'c', // a character + true, // a boolean value + () => "an anonymous function returning a string" +) + +list.foreach(element => println(element)) +``` + +该代码定义了一个类型为 `List[Any]` 的值 `list` 。 +该列表使用各种类型的元素进行初始化,但每个元素都是 `scala.Any` 的实例,因此我们可以将它们添加到列表中。 + +下面是程序的输出: + +``` +a string +732 +c +true + +``` + +## Scala的“值类型” + +如上所示,Scala的值类型扩展了 `AnyVal`,它们都是成熟的对象。 +这些示例演示如何声明以下数值类型的变量: + +```scala +val b: Byte = 1 +val i: Int = 1 +val l: Long = 1 +val s: Short = 1 +val d: Double = 2.0 +val f: Float = 3.0 +``` + +在前四个示例中,如果未显式指定类型,则数字 `1` 将默认为 `Int` ,因此,如果需要其他数据类型之一 --- `Byte` 、`Long` 或 `Short` --- 则需要显式声明这些类型,如上面代码所示。 +带有小数的数字(如2.0)将默认为 `Double` ,因此,如果您想要 `Float` ,则需要声明 `Float` ,如上一个示例所示。 + +由于 `Int` 和 `Double` 是默认数值类型,因此通常在不显式声明数据类型的情况下创建它们: + +```scala +val i = 123 // defaults to Int +val x = 1.0 // defaults to Double +``` + +在代码中,您还可以将字符 `L` 、 `D` 和 `F` (及其小写等效项)加到数字末尾,以指定它们是 `Long`, `Double`, 或 `Float` 值: + +```scala +val x = 1_000L // val x: Long = 1000 +val y = 2.2D // val y: Double = 2.2 +val z = 3.3F // val z: Float = 3.3 +``` + +Scala还具有 `String` 和 `Char` 类型,通常可以使用隐式形式声明: + +```scala +val s = "Bill" +val c = 'a' +``` + +如下面表格所示,将字符串括在双引号中 --- 或多行字符串括在三引号中 --- 或字符括在单引号中。 + +这些数据类型及其范围包括: + +| 数据类型 | 可能的值 | +| ---------- | --------------- | +| Boolean | `true` 或 `false` | +| Byte | 8 位有符号二进制补码整数(-2^7 至 2^7-1,含)
-128 至 127 | +| Short | 16 位有符号二进制补码整数(-2^15 至 2^15-1,含)
-32,768 至 32,767 | +| Int | 32 位二进制补码整数(-2^31 至 2^31-1,含)
-2,147,483,648 至 2,147,483,647 | +| Long | 64 位 2 的补码整数(-2^63 到 2^63-1,含)
(-2^63 到 2^63-1,包括) | +| Float | 32 位 IEEE 754 单精度浮点数
1.40129846432481707e-45 到 3.40282346638528860e+38 | +| Double | 64 位 IEEE 754 双精度浮点数
4.94065645841246544e-324 到 1.79769313486231570e+308 | +| Char | 16 位无符号 Unicode 字符(0 到 2^16-1,含)
0 到 65,535 | +| String | 一个 `Char` 序列 | + +## `BigInt` 和 `BigDecimal` + +当您需要非常大的数字时,请使用 `BigInt` 和 `BigDecimal` 类型: + +```scala +val a = BigInt(1_234_567_890_987_654_321L) +val b = BigDecimal(123_456.789) +``` + +其中 `Double` 和 `Float` 是近似的十进制数, `BigDecimal` 用于精确算术,例如在使用货币时。 + +`BigInt` 和 `BigDecimal` 的一个好处是,它们支持您习惯于用于数字类型的所有运算符: + +```scala +val b = BigInt(1234567890) // scala.math.BigInt = 1234567890 +val c = b + b // scala.math.BigInt = 2469135780 +val d = b * b // scala.math.BigInt = 1524157875019052100 +``` + +## 关于字符串的两个注释 + +Scala字符串类似于Java字符串,但它们有两个很棒的附加特性: + +- 它们支持字符串插值 +- 创建多行字符串很容易 + +### 字符串插值 + +字符串插值提供了一种非常可读的方式在字符串中使用变量。 +例如,给定以下三个变量: + +```scala +val firstName = "John" +val mi = 'C' +val lastName = "Doe" +``` + +你可以把那些变量组合成这样的字符串: + +```scala +println(s"Name: $firstName $mi $lastName") // "Name: John C Doe" +``` + +只需在字符串前面加上字母 `s` ,然后在字符串内的变量名称之前放置一个 `$` 符号。 + +如果要在字符串中使用可能较大的表达式时,请将它们放在大括号中: + +```scala +println(s"2 + 2 = ${2 + 2}") // prints "2 + 2 = 4" +val x = -1 +println(s"x.abs = ${x.abs}") // prints "x.abs = 1" +``` + +#### 其他插值器 + +放置在字符串前面的 `s` 只是一个可能的插值器。 +如果使用 `f` 而不是 `s` ,则可以在字符串中使用 `printf` 样式的格式语法。 +此外,字符串插值器只是一种特殊方法,可以定义自己的方法。 +例如,一些数据库库定义了非常强大的 `sql` 插值器。 + +### 多行字符串 + +多行字符串是通过将字符串包含在三个双引号内来创建的: + +```scala +val quote = """The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting.""" +``` + +这种基本方法的一个缺点是,第一行之后的行是缩进的,如下所示: + +```scala +"The essence of Scala: + Fusion of functional and object-oriented + programming in a typed setting." +``` + +当间距很重要时,在第一行之后的所有行前面放一个 `|` 符号,并在字符串之后调用 `stripMargin` 方法: + +```scala +val quote = """The essence of Scala: + |Fusion of functional and object-oriented + |programming in a typed setting.""".stripMargin +``` + +现在字符串里所有行都是左对齐了: + +```scala +"The essence of Scala: +Fusion of functional and object-oriented +programming in a typed setting." +``` + +## 类型转换 + +可以通过以下方式强制转换值类型: + +Scala Type Hierarchy + +例如: + +```scala +val b: Byte = 127 +val i: Int = b // 127 + +val face: Char = '☺' +val number: Int = face // 9786 +``` + +只有在没有丢失信息的情况下,才能强制转换为类型。否则,您需要明确说明强制转换: + +```scala +val x: Long = 987654321 +val y: Float = x.toFloat // 9.8765434E8 (注意 `.toFloat` 是必须的,因为强制类型转换后的精度会损) +val z: Long = y // Error +``` + +还可以将引用类型强制转换为子类型。 +这将在教程的后面部分介绍。 + +## `Nothing` 和 `null` + +`Nothing` 是所有类型的子类型,也称为**底部类型**。 +`Nothing` 类型是没有值的。 +一个常见的用途是发出非终止信号,例如抛出异常,程序退出或无限循环---即,它是不计算为值的那种表达式,或者不正常返回的方法。 + +`Null` 是所有引用类型的子类型(即 `AnyRef` 的任何子类型)。 +它具有由关键字面量 `null` 标识的单个值。 +目前,使用 `null` 被认为是不好的做法。它应该主要用于与其他JVM语言的互操作性。选择加入编译器选项会更改 `Null` 状态,以修复与其用法相关的警告。此选项可能会成为将来版本的 Scala 中的默认值。你可以在[这里][safe-null]了解更多关于它的信息。 + +与此同时, `null` 几乎不应该在Scala代码中使用。 +本书的[函数式编程章节][fp]和 [API文档][option-api]中讨论了 `null` 的替代方法。 + +[reference]: {{ site.scala3ref }}/overview.html +[matchable]: {{ site.scala3ref }}/other-new-features/matchable.html +[interpolation]: {% link _overviews/core/string-interpolation.md %} +[fp]: {% link _overviews/scala3-book/fp-intro.md %} +[option-api]: https://scala-lang.org/api/3.x/scala/Option.html +[safe-null]: {{ site.scala3ref }}/experimental/explicit-nulls.html From e10cf0d837e90cd8b236b2de3662e4cebe482b9d Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 22 May 2022 00:12:57 +0800 Subject: [PATCH 11/53] add _zh-cn/overviews/scala3-book/control-structures.md --- .../scala3-book/control-structures.md | 537 ++++++++++++++++++ 1 file changed, 537 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/control-structures.md diff --git a/_zh-cn/overviews/scala3-book/control-structures.md b/_zh-cn/overviews/scala3-book/control-structures.md new file mode 100644 index 0000000000..d52501eb45 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/control-structures.md @@ -0,0 +1,537 @@ +--- +title: 控制结构 +type: chapter +description: This page provides an introduction to Scala's control structures, including if/then/else, 'for' loops, 'for' expressions, 'match' expressions, try/catch/finally, and 'while' loops. +num: 18 +previous-page: first-look-at-types +next-page: domain-modeling-intro +--- + + +Scala具有您希望在编程语言中找到的控制结构,包括: + +- `if`/`then`/`else` +- `for` 循环 +- `while` 循环 +- `try`/`catch`/`finally` + +它还具有另外两个您可能以前从未见过的强大结构,具体取决于您的编程背景: + +- `for` 表达式(也被称作 _`for` 理解_) +- `match` 表达式 + +这些都将在以下各节中进行演示。 + +## if/then/else 结构nstruct + +单行 Scala `if` 语句像这样: + +```scala +if x == 1 then println(x) +``` + +如果要在 `if` 比较后运行多行代码,用这个语法: + +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +``` + +`if`/`else` 语法像这样: + +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +else + println("x was not 1") +``` + +这是 `if`/`else if`/`else` 语法: + +```scala +if x < 0 then + println("negative") +else if x == 0 then + println("zero") +else + println("positive") +``` + +如果您愿意,可以选择在每个表达式的末尾包含 `end if` 语句: + +```scala +if x == 1 then + println("x is 1, as you can see:") + println(x) +end if +``` + +### `if`/`else` 表达式总是有返回值 + +请注意, `if` / `else` 比较形式为 _表达式_,这意味着它们返回一个可以分配给变量的值。 +因此,不需要特殊的三元运算符: + +```scala +val minValue = if a < b then a else b +``` + +由于它们返回一个值,因此可以使用 `if`/`else` 表达式作为方法的主体: + +```scala +def compare(a: Int, b: Int): Int = + if a < b then + -1 + else if a == b then + 0 + else + 1 +``` + +### 题外话:面向表达式的编程 + +作为一般编程的简要说明,当您编写的每个表达式都返回一个值时,该样式称为面向 _面向表达式的编程_ 或 EOP。 +例如,这是一个 _表达式_: + +```scala +val minValue = if a < b then a else b +``` + +相反,不返回值的代码行称为 _语句_,用它们的 _副作用_。 +例如,这些代码行不返回值,因此用它们的副作用: + +```scala +if a == b then action() +println("Hello") +``` + +第一个示例在 `a` 等于 `b` 时将 `action` 方法作为副作用运行。 +第二个示例用于将字符串打印到 STDOUT 的副作用。 +随着你对Scala的了解越来越多,你会发现自己写的 _表达式_ 越来越多,_语句_ 也越来越少。 + +## `for` 循环 + +在最简单的用法中,Scala `for` 循环可用于迭代集合中的元素。 +例如,给定一个整数序列,您可以循环访问其元素并打印其值,如下所示: + +```scala +val ints = Seq(1, 2, 3) +for i <- ints do println(i) +``` + +代码 `i <- ints` 被称为 _生成器_,如果将括号从生成器中去掉,则必须在括号之后的代码前加上 `do` 关键字。 +否则,您可以像这样编写代码: + +```scala +for (i <- ints) println(i) +``` + +无论您使用哪种方法,这都是 Scala REPL 中的结果: + +```` +scala> val ints = Seq(1,2,3) +ints: Seq[Int] = List(1, 2, 3) + +scala> for i <- ints do println(i) +1 +2 +3 +```` + +如果需要在 `for` 生成器后面显示多行代码块,请使用以下语法: + +```scala +for + i <- ints +do + val x = i * 2 + println(s"i = $i, x = $x") +``` + +### 多生成器 + +`for` 循环可以有多个生成器,如以下示例所示: + +```scala +for + i <- 1 to 2 + j <- 'a' to 'b' + k <- 1 to 10 by 5 +do + println(s"i = $i, j = $j, k = $k") +``` + +那个表达式的输出: + +```` +i = 1, j = a, k = 1 +i = 1, j = a, k = 6 +i = 1, j = b, k = 1 +i = 1, j = b, k = 6 +i = 2, j = a, k = 1 +i = 2, j = a, k = 6 +i = 2, j = b, k = 1 +i = 2, j = b, k = 6 +```` + +### 防护 + +`for` 循环也可以包含 `if` 语句,这些语句称为 _防护_: + +```scala +for + i <- 1 to 5 + if i % 2 == 0 +do + println(i) +``` + +以上循环的输出是: + +```` +2 +4 +```` + +`for` 循环可以根据需要有任意数量的防护装置。 +此示例显示了打印数字`4`的一种方法: + +```scala +for + i <- 1 to 10 + if i > 3 + if i < 6 + if i % 2 == 0 +do + println(i) +``` + +### 把 `for` 用在 `Map` 上 + +您还可以将 `for` 循环与 `Map` 一起使用。 +例如,给定州缩写及其全名的 `Map`: + +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AR" -> "Arizona" +) +``` + +您可以使用 `for` 打印键和值,如下所示: + +```scala +for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") +``` + +以下是 REPL 中的样子: + +```scala +scala> for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") +AK: Alaska +AL: Alabama +AR: Arizona +``` + +当 `for` 循环遍历映射时,每个键/值对都绑定到变量 `abbrev` 和 `fullName` ,它们位于元组中: + +```scala +(abbrev, fullName) <- states +``` + +当循环运行时,变量 `abbrev` 被分配给映射中的当前 _键_,变量 `fullName` 被分配给当前map 的 _值_。 + +## `for` 表达式 + +在前面的 `for` 循环示例中,这些循环都用于 _副作用_,特别是使用 `println` 将这些值打印到STDOUT。 + +重要的是要知道,您还可以创建有返回值的 `for` _表达式_。 +您可以通过添加 `yield` 关键字和要返回的表达式来创建 `for` 表达式,如下所示: + +```scala +val list = + for + i <- 10 to 12 + yield + i * 2 + +// list: IndexedSeq[Int] = Vector(20, 22, 24) +``` + +在 `for` 表达式运行后,变量 `list` 是包含所示值的 `Vector` 。 +这是表达式的工作原理: + +1. `for` 表达式开始循环访问范围 `(10, 11, 12)` 中的值。 + 它首先处理值`10`,将其乘以`2`,然后 _产生_ 结果为`20`的值。 +2. 接下来,它适用于`11`---范围中的第二个值。 + 它乘以`2`,然后产生值`22`。 + 您可以将这些产生的值看作它们累积在某个临时位置。 +3. 最后,循环从范围中获取数字 `12`,将其乘以 `2`,得到数字 `24`。 + 循环此时完成并产生最终结果 `Vector(20, 22, 24)`。 + +{% comment %} +NOTE: This is a place where it would be great to have a TIP or NOTE block: +{% endcomment %} + +虽然本节的目的是演示 `for` 表达式,但它可以帮助知道显示的 `for` 表达式等效于以下 `map` 方法调用: + +```scala +val list = (10 to 12).map(i => i * 2) +``` + +只要您需要遍历集合中的所有元素,并将算法应用于这些元素以创建新列表,就可以使用 `for` 表达式。 + +下面是一个示例,演示如何在 `yield` 之后使用代码块: + +```scala +val names = List("_olivia", "_walter", "_peter") + +val capNames = for name <- names yield + val nameWithoutUnderscore = name.drop(1) + val capName = nameWithoutUnderscore.capitalize + capName + +// capNames: List[String] = List(Olivia, Walter, Peter) +``` + +### 使用 `for` 表达式作为方法的主体 + +由于 `for` 表达式产生结果,因此可以将其用作返回有用值的方法的主体。 +此方法返回给定整数列表中介于`3`和`10`之间的所有值: + +```scala +def between3and10(xs: List[Int]): List[Int] = + for + x <- xs + if x >= 3 + if x <= 10 + yield x + +between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7) +``` + +## `while` 循环 + +Scala `while` 循环语法如下: + +```scala +var i = 0 + +while i < 3 do + println(i) + i += 1 +``` + +如果你把测试条件放在括号里,它可以写成这样: + +```scala +var i = 0 + +while (i < 3) { + println(i) + i += 1 +} +``` + + +## `match` 表达式 + +模式匹配是函数式编程语言的一个主要特征,Scala包含一个具有许多功能的 `match` 表达式。 + +在最简单的情况下,您可以使用 `match` 表达式,象Java `switch` 语句,根据整数值匹配。 +请注意,这实际上是一个表达式,因为它计算出一个结果: + +```scala +import scala.annotation.switch + +// `i` is an integer +val day = i match + case 0 => "Sunday" + case 1 => "Monday" + case 2 => "Tuesday" + case 3 => "Wednesday" + case 4 => "Thursday" + case 5 => "Friday" + case 6 => "Saturday" + case _ => "invalid day" // the default, catch-all +``` + +在此示例中,变量 `i` 根据所示情况进行测试。 +如果它介于`0`和`6`之间,则 `day` 绑定到一个字符串,该字符串表示一周中的某一天。 +否则,捕获所有情况,这些情况用 `_` 字符表示,这样 `day` 绑定到字符串 `"invalid day"`。 + +> 在编写像这样的简单 `match` 表达式时,建议在变量 `i` 上使用 `@switch` 注释。 +> 如果开关无法编译为 `tableswitch` 或 `lookupswitch`,则此注释会提供编译时警告,这个开关对性能更好。 + +### 使用缺省值 + +当您需要访问 `match` 表达式中匹配所有情况,也就是默认值时,只需在 `case` 语句的左侧提供一个变量名称,然后根据需要在语句的右侧使用该变量名称: + +```scala +i match + case 0 => println("1") + case 1 => println("2") + case what => println(s"You gave me: $what" ) +``` + +在此示例中,变量被命名为 `what` ,以表明可以为其指定任何合法名称。 +您还可以使用 `_` 作为名称来忽略该值。 + +### 在一行上处理多个可能的匹配项 + +如前所述,`match` 表达式具有许多功能。 +此示例演示如何在每个 `case` 语句中使用多个可能的模式匹配: + +```scala +val evenOrOdd = i match + case 1 | 3 | 5 | 7 | 9 => println("odd") + case 2 | 4 | 6 | 8 | 10 => println("even") + case _ => println("some other number") +``` + +### 在 `case` 子句中使用 `if` 防护 + +您还可以在匹配表达式的 `case` 中使用防护装置。 +在此示例中,第二个和第三个 `case` 都使用防护来匹配多个整数值: + +```scala +i match + case 1 => println("one, a lonely number") + case x if x == 2 || x == 3 => println("two’s company, three’s a crowd") + case x if x > 3 => println("4+, that’s a party") + case _ => println("i’m guessing your number is zero or less") +``` + +下面是另一个示例,它显示了如何将给定值与数字范围进行匹配: + +```scala +i match + case a if 0 to 9 contains a => println(s"0-9 range: $a") + case b if 10 to 19 contains b => println(s"10-19 range: $b") + case c if 20 to 29 contains c => println(s"20-29 range: $c") + case _ => println("Hmmm...") +``` + +#### 样例类和 match 表达式 + +您还可以从 `case` 类中提取字段---以及正确编写了 `apply`/`unapply` 方法的类---并在防护条件下使用这些字段。 +下面是一个使用简单 `Person` 案例类的示例: + +```scala +case class Person(name: String) + +def speak(p: Person) = p match + case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo") + case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!") + case _ => println("Watch the Flintstones!") + +speak(Person("Fred")) // "Fred says, Yubba dubba doo" +speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!" +``` + +### 使用 `match` 表达式作为方法的主体 + +由于 `match` 表达式返回一个值,因此它们可以用作方法的主体。 +此方法采用 `Matchable` 值作为输入参数,并根据 `match` 表达式的结果返回 `Boolean`: + +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" | false => false + case _ => true +``` + +输入参数 `a` 被定义为[`Matchable`类型][matchable]---这是可以对其执行模式匹配的所有Scala类型的根。 +该方法通过在输入上进行匹配来实现,提供两种情况: +第一个检查给定值是整数`0`,空字符串还是 `false`,在这种情况下返回 `false`。 +在默认情况下,我们为任何其他值返回 `true`。 +以下示例演示此方法的工作原理: + +```scala +isTruthy(0) // false +isTruthy(false) // false +isTruthy("") // false +isTruthy(1) // true +isTruthy(" ") // true +isTruthy(2F) // true +``` + +使用 `match` 表达式作为方法的主体是一种非常常见的用法。 + +#### 匹配表达式支持许多不同类型的模式 + +有许多不同形式的模式可用于编写 `match` 表达式。 +示例包括: + +- 常量模式(如 `case 3 => `) +- 序列模式(如 `case List(els : _*) =>`) +- 元组模式(如 `case (x, y) =>`) +- 构造函数模式(如 `case Person(first, last) =>`) +- 类型测试模式(如 `case p: Person =>`) + +所有这些类型的模式匹配都展示在以下 `pattern` 方法中,该方法采用类型为 `Matchable` 的输入参数并返回 `String` : + +```scala +def pattern(x: Matchable): String = x match + + // constant patterns + case 0 => "zero" + case true => "true" + case "hello" => "you said 'hello'" + case Nil => "an empty List" + + // sequence patterns + case List(0, _, _) => "a 3-element list with 0 as the first element" + case List(1, _*) => "list, starts with 1, has any number of elements" + case Vector(1, _*) => "vector, starts w/ 1, has any number of elements" + + // tuple patterns + case (a, b) => s"got $a and $b" + case (a, b, c) => s"got $a, $b, and $c" + + // constructor patterns + case Person(first, "Alexander") => s"Alexander, first name = $first" + case Dog("Zeus") => "found a dog named Zeus" + + // type test patterns + case s: String => s"got a string: $s" + case i: Int => s"got an int: $i" + case f: Float => s"got a float: $f" + case a: Array[Int] => s"array of int: ${a.mkString(",")}" + case as: Array[String] => s"string array: ${as.mkString(",")}" + case d: Dog => s"dog: ${d.name}" + case list: List[?] => s"got a List: $list" + case m: Map[?, ?] => m.toString + + // the default wildcard pattern + case _ => "Unknown" +``` + +{% comment %} +TODO: Add in the new Scala 3 syntax shown on this page: +http://dotty.epfl.ch/docs/reference/changed-features/match-syntax.html +{% endcomment %} + +## try/catch/finally + +与Java一样,Scala也有一个 `try`/`catch`/`finally` 结构,让你可以捕获和管理异常。 +为了保持一致性,Scala使用与 `match` 表达式相同的语法,并支持在可能发生的不同可能的异常上进行模式匹配。 + +在下面的示例中,`openAndReadAFile` 是一个执行其名称含义的方法:它打开一个文件并读取其中的文本,将结果分配给可变变量 `text` : + +```scala +var text = "" +try + text = openAndReadAFile(filename) +catch + case fnf: FileNotFoundException => fnf.printStackTrace() + case ioe: IOException => ioe.printStackTrace() +finally + // close your resources here + println("Came to the 'finally' clause.") +``` + +假设 `openAndReadAFile` 方法使用 Java `java.io.*` 类来读取文件并且不捕获其异常,则尝试打开和读取文件可能会导致 `FileNotFoundException` 和 `IOException` 异常,本例中,这两个异常在 `catch` 块中被捕获。 + +[matchable]: {{ site.scala3ref }}/other-new-features/matchable.html From 3573d192ce0678488253a75fb2d1502738cc0069 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 22 May 2022 20:09:35 +0800 Subject: [PATCH 12/53] add two files as below --- .../scala3-book/domain-modeling-intro.md | 14 + .../scala3-book/domain-modeling-tools.md | 700 ++++++++++++++++++ 2 files changed, 714 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/domain-modeling-intro.md create mode 100644 _zh-cn/overviews/scala3-book/domain-modeling-tools.md diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-intro.md b/_zh-cn/overviews/scala3-book/domain-modeling-intro.md new file mode 100644 index 0000000000..eb63b37efb --- /dev/null +++ b/_zh-cn/overviews/scala3-book/domain-modeling-intro.md @@ -0,0 +1,14 @@ +--- +title: 领域建模 +type: chapter +description: This chapter provides an introduction to domain modeling in Scala 3. +num: 19 +previous-page: control-structures +next-page: domain-modeling-tools +--- + +本章介绍如何使用 Scala 3 对周围的世界进行建模: + +- 工具部分介绍了可供您使用的工具,包括类、traits 、枚举等 +- OOP 建模部分着眼于面向对象编程 (OOP) 风格中的建模属性和行为 +- FP 建模部分以函数式编程 (FP) 风格来看领域建模 diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-tools.md b/_zh-cn/overviews/scala3-book/domain-modeling-tools.md new file mode 100644 index 0000000000..af76e6ca47 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/domain-modeling-tools.md @@ -0,0 +1,700 @@ +--- +title: 工具 +type: section +description: This chapter provides an introduction to the available domain modeling tools in Scala 3, including classes, traits, enums, and more. +num: 20 +previous-page: domain-modeling-intro +next-page: domain-modeling-oop +--- + +Scala 3提供了许多不同的结构,因此我们可以对周围的世界进行建模: + +- 类 +- 对象 +- 伴生对象 +- traits +- 抽象类 +- 枚举 +- 样例类 +- 样例对象 + +本节简要介绍其中的每种语言功能。 + +## 类 + +与其他语言一样,Scala中的 _类_是用于创建对象实例的模板。 +下面是一些类的示例: + +```scala +class Person(var name: String, var vocation: String) +class Book(var title: String, var author: String, var year: Int) +class Movie(var name: String, var director: String, var year: Int) +``` + +这些例子表明,Scala有一种非常轻量级的方式来声明类。 + +我们的示例类的所有参数都定义为 `var` 字段,这意味着它们是可变的:您可以读取它们,也可以修改它们。 +如果您希望它们是不可变的---仅读取---请改为将它们创建为 `val` 字段,或者使用样例类。 + +在Scala 3之前,您使用 `new` 关键字来创建类的新实例: + +```scala +val p = new Person("Robert Allen Zimmerman", "Harmonica Player") +// --- +``` + +然而,通过[创造者应用][creator],在 Scala 3 里面不要求使用 `new`: + +```scala +val p = Person("Robert Allen Zimmerman", "Harmonica Player") +``` + +一旦你有了一个类的实例,比如`p`,你就可以访问它的字段,在此示例中,这些字段都是构造函数的参数: + +```scala +p.name // "Robert Allen Zimmerman" +p.vocation // "Harmonica Player" +``` + +如前所述,所有这些参数都是作为 `var` 字段创建的,因此您也可以更改它们: + +```scala +p.name = "Bob Dylan" +p.vocation = "Musician" +``` + +### 字段和方法 + +类还可以具有不属于构造函数的方法和其他字段。 +它们在类的主体中定义。 +主体初始化为默认构造函数的一部分: + +```scala +class Person(var firstName: String, var lastName: String): + + println("initialization begins") + val fullName = firstName + " " + lastName + + // a class method + def printFullName: Unit = + // access the `fullName` field, which is created above + println(fullName) + + printFullName + println("initialization ends") +``` + +以下 REPL 会话演示如何使用这个类创建新的 `Person` 实例: + +```` +scala> val john = Person("John", "Doe") +initialization begins +John Doe +initialization ends +val john: Person = Person@55d8f6bb + +scala> john.printFullName +John Doe +```` + +类还可以扩展 traits和抽象类,我们将在下面专门部分中介绍这些内容。 + +### 默认参数值 + +快速浏览一下其他功能,类构造函数参数也可以具有默认值: + +```scala +class Socket(val timeout: Int = 5_000, val linger: Int = 5_000): + override def toString = s"timeout: $timeout, linger: $linger" +``` + +此功能的一大优点是,它允许代码的使用者以各种不同的方式创建类,就好像该类有别的构造函数一样: + +```scala +val s = Socket() // timeout: 5000, linger: 5000 +val s = Socket(2_500) // timeout: 2500, linger: 5000 +val s = Socket(10_000, 10_000) // timeout: 10000, linger: 10000 +val s = Socket(timeout = 10_000) // timeout: 10000, linger: 5000 +val s = Socket(linger = 10_000) // timeout: 5000, linger: 10000 +``` + +创建类的新实例时,还可以使用命名参数。 +当许多参数具有相同的类型时,这特别有用,如以下比较所示: + +```scala +// option 1 +val s = Socket(10_000, 10_000) + +// option 2 +val s = Socket( + timeout = 10_000, + linger = 10_000 +) +``` + +### 辅助构造函数 + +可以为类定义多个构造函数,以便类的使用者用不同的方式来生成这个类。 +例如,假设您需要编写一些代码给大学招生系统中的学生进行建模。 +在分析需求时,您已经看到您需要能够以三种方式构建 `Student` 实例: + +- 当他们第一次开始招生过程时,带有姓名和政府 ID, +- 当他们提交申请时,带有姓名,政府 ID 和额外的申请日期 +- 在他们被录取后,带有姓名,政府 ID 和学生证 + +在 OOP 风格中处理这种情况的一种方法是使用以下代码: + +```scala +import java.time.* + +// [1] the primary constructor +class Student( + var name: String, + var govtId: String +): + private var _applicationDate: Option[LocalDate] = None + private var _studentId: Int = 0 + + // [2] a constructor for when the student has completed + // their application + def this( + name: String, + govtId: String, + applicationDate: LocalDate + ) = + this(name, govtId) + _applicationDate = Some(applicationDate) + + // [3] a constructor for when the student is approved + // and now has a student id + def this( + name: String, + govtId: String, + studentId: Int + ) = + this(name, govtId) + _studentId = studentId +``` + +{% comment %} +// for testing that code +override def toString = s""" +|Name: $name +|GovtId: $govtId +|StudentId: $_studentId +|Date Applied: $_applicationDate +""".trim.stripMargin +{% endcomment %} + +该类有三个构造函数,由代码中编号的注释给出: + +1. 主构造函数,由类定义中的 `name` 和 `govtId` 给出 +2. 具有参数 `name` 、 `govtId` 和 `applicationDate` 的辅助构造函数 +3. 另一个带有参数 `name` 、 `govtId` 和 `studentId` 的辅助构造函数 + +这些构造函数可以这样调用: + +```scala +val s1 = Student("Mary", "123") +val s2 = Student("Mary", "123", LocalDate.now) +val s3 = Student("Mary", "123", 456) +``` + +虽然可以使用此技术,但请记住,构造函数参数也可以具有默认值,这使得一个类看起来具有多个构造函数。 +这在前面的 `Socket` 示例中所示。 + +## 对象 + +对象是一个正好有一个实例的类。 +当其成员是引用类时,它会延迟初始化,类似于 `lazy val` 。 +Scala 中的对象允许在一个命名空间下对方法和字段进行分组,类似于我们在 Java,Javascript(ES6)中使用 `static` 方法或在 Python 中使用 `@staticmethod` 方法。 + +声明 `object` 类似于声明 `class` 。 +下面是一个“字符串实用程序”对象的示例,其中包含一组用于处理字符串的方法: + +```scala +object StringUtils: + def truncate(s: String, length: Int): String = s.take(length) + def containsWhitespace(s: String): Boolean = s.matches(".*\\s.*") + def isNullOrEmpty(s: String): Boolean = s == null || s.trim.isEmpty +``` + +我们可以这样使用对象: + +```scala +StringUtils.truncate("Chuck Bartowski", 5) // "Chuck" +``` + +在 Scala 中导入非常灵活,并允许我们导入对象的 _所有_ 成员: + +```scala +import StringUtils.* +truncate("Chuck Bartowski", 5) // "Chuck" +containsWhitespace("Sarah Walker") // true +isNullOrEmpty("John Casey") // false +``` + +或者只是 _部分_ 成员: + +```scala +import StringUtils.{truncate, containsWhitespace} +truncate("Charles Carmichael", 7) // "Charles" +containsWhitespace("Captain Awesome") // true +isNullOrEmpty("Morgan Grimes") // Not found: isNullOrEmpty (error) +``` + +对象还可以包含字段,这些字段也可以像静态成员一样访问: + +```scala +object MathConstants: + val PI = 3.14159 + val E = 2.71828 + +println(MathConstants.PI) // 3.14159 +``` + + +## 伴生对象 + +与类同名且在与类在相同的文件中声明的 `object` 称为 _“伴生对象”_。 +同样,相应的类称为对象的伴生类。 +伴生类或对象可以访问其伴生的私有成员。 + +伴生对象用于不特定于伴生类实例的方法和值。 +例如,在下面的示例中,类 `Circle` 具有一个名为 `area` 的成员,该成员特定于每个实例,其伴生对象具有一个名为 `calculateArea` 的方法,该方法(a)不特定于实例,并且(b)可用于每个实例: + +```scala +import scala.math.* + +case class Circle(radius: Double): + def area: Double = Circle.calculateArea(radius) + +object Circle: + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) + +val circle1 = Circle(5.0) +circle1.area +``` + +在此示例中,每个实例可用的 `area` 方法使用伴生对象中定义的 `calculateArea` 方法。 +再一次, `calculateArea` 类似于Java中的静态方法。 +此外,由于 `calculateArea` 是私有的,因此其他代码无法访问它,但如图所示,它可以被 `Circle` 类的实例看到。 + +### 其他用途 + +伴生对象可用于多种用途: + +- 如图所示,它们可用于将“静态”方法分组到命名空间下 + - 这些方法可以是公共的,也可以是私有的 + - 如果 `calculateArea` 是公开的,它将被访问为 `Circle.calculateArea` +- 它们可以包含 `apply` 方法,这些方法---感谢一些语法糖---作为工厂方法来构建新实例 +- 它们可以包含 `unapply` 方法,用于解构对象,例如模式匹配 + +下面快速了解如何将 `apply` 方法当作工厂方法来创建新对象: + +```scala +class Person: + var name = "" + var age = 0 + override def toString = s"$name is $age years old" + +object Person: + + // a one-arg factory method + def apply(name: String): Person = + var p = new Person + p.name = name + p + + // a two-arg factory method + def apply(name: String, age: Int): Person = + var p = new Person + p.name = name + p.age = age + p + +end Person + +val joe = Person("Joe") +val fred = Person("Fred", 29) + +//val joe: Person = Joe is 0 years old +//val fred: Person = Fred is 29 years old +``` + +此处不涉及 `unapply` 方法,但在 [参考文档][unapply] 中对此进行了介绍。 + +## Traits + +如果你熟悉Java,Scala trait 类似于Java 8+中的接口。性状可以包含: + +- 抽象方法和成员 +- 具体方法和成员 + +在基本用法中,trait 可以用作接口,仅定义将由其他类实现的抽象成员: + +```scala +trait Employee: + def id: Int + def firstName: String + def lastName: String +``` + +但是,traits 也可以包含具体成员。 +例如,以下 traits定义了两个抽象成员---`numLegs` 和 `walk()`---并且还具有`stop()`方法的具体实现: + +```scala +trait HasLegs: + def numLegs: Int + def walk(): Unit + def stop() = println("Stopped walking") +``` + +下面是另一个具有抽象成员和两个具体实现的 trait: + +```scala +trait HasTail: + def tailColor: String + def wagTail() = println("Tail is wagging") + def stopTail() = println("Tail is stopped") +``` + +请注意,每个 trait 只处理非常特定的属性和行为: `HasLegs` 只处理腿,而 `HasTail` 只处理与尾部相关的功能。 +Traits可以让你构建这样的小模块。 + +在代码的后面部分,类可以混合多个 traits 来构建更大的组件: + +```scala +class IrishSetter(name: String) extends HasLegs, HasTail: + val numLegs = 4 + val tailColor = "Red" + def walk() = println("I’m walking") + override def toString = s"$name is a Dog" +``` + +请注意,`IrishSetter` 类实现了在 `HasLegs` 和 `HasTail` 中定义的抽象成员。 +现在,您可以创建新的 `IrishSetter` 实例: + +```scala +val d = IrishSetter(“Big Red”) // “Big Red is a Dog” +``` + +这只是你对 trait 可以完成的事情的一种体验。 +有关更多详细信息,请参阅这些建模课程的其余部分。 + +## 抽象类 + +{% comment %} +LATER: If anyone wants to update this section, our comments about abstract classes and traits are on Slack. The biggest points seem to be: +- The `super` of a trait is dynamic +- At the use site, people can mix in traits but not classes +- It remains easier to extend a class than a trait from Java, if the trait has at least a field +- Similarly, in Scala.js, a class can be imported from or exported to JavaScript. A trait cannot +- There are also some point that unrelated classes can’t be mixed together, and this can be a modeling advantage +{% endcomment %} + +当你想写一个类,但你知道它将有抽象成员时,你可以创建一个 trait 或一个抽象类。 +在大多数情况下,你会使用 trait,但从历史上看,有两种情况,使用抽象类比使用 trait 更好: + +- 您想要创建一个使用构造函数参数的基类 +- 代码将从 Java 代码调用 + +### 使用构造函数参数的基类 + +在 Scala 3 之前,当基类需要使用构造函数参数时,你可以将其声明为`abstract class`: + +```scala +abstract class Pet(name: String): + def greeting: String + def age: Int + override def toString = s"My name is $name, I say $greeting, and I’m $age" + +class Dog(name: String, age: Int) extends Pet(name): + val greeting = "Woof" + +val d = Dog("Fido", 1) +``` + +但是,在 Scala 3 中,trait 现在可以具有[参数][trait-params],因此您现在可以在相同情况下使用 trait: + +```scala +trait Pet(name: String): + def greeting: String + def age: Int + override def toString = s"My name is $name, I say $greeting, and I’m $age" + +class Dog(name: String, var age: Int) extends Pet(name): + val greeting = "Woof" + +val d = Dog("Fido", 1) +``` + +trait 的组成更加灵活---您可以混合多个 trait,但只能扩展一个类---并且大多数时候应该优先于类和抽象类。 +经验法则是,每当要创建特定类型的实例时,就使用类;如果要分解和重用行为时,应使用trait。 + +## 枚举 + +枚举可用于定义由一组有限的命名值组成的类型(在[FP建模][fp-modeling]一节中,我们将看到枚举比这更灵活)。 +基本枚举用于定义常量集,如一年中的月份、一周中的天数、北/南/东/西方向等。 + +例如,这些枚举定义了与比萨饼相关的属性集: + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` + +若要在其他代码中使用它们,请先导入它们,然后使用它们: + +```scala +import CrustSize.* +val currentCrustSize = Small +``` + +枚举值可以使用等于 (`==`) 进行比较,也可以用匹配的方式: + +```scala +// if/then +if (currentCrustSize == Large) + println("You get a prize!") + +// match +currentCrustSize match + case Small => println("small") + case Medium => println("medium") + case Large => println("large") +``` + +### 其他枚举特性 + +枚举也可以参数化: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +它们还可以具有成员(如字段和方法): + +```scala +enum Planet(mass: Double, radius: Double): + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = + otherMass * surfaceGravity + + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Earth extends Planet(5.976e+24, 6.37814e6) + // more planets here ... +``` + +### 与 Java 枚举的兼容性 + +如果要将 Scala 定义的枚举用作 Java 枚举,可以通过扩展类 `java.lang.Enum`(默认情况下导入)来实现,如下所示: + +```scala +enum Color extends Enum[Color] { case Red, Green, Blue } +``` + +类型参数来自 Java `enum` 定义,并且应与枚举的类型相同。 +在扩展时,无需向`java.lang.Enum`提供构造函数参数(如Java API文档中所定义的那样)---编译器会自动生成它们。 + +像这样定义 `Color` 之后,你可以像使用 Java 枚举一样使用它: + +```` +scala> Color.Red.compareTo(Color.Green) +val res0: Int = -1 +```` + +关于[代数数据类型][adts]和[参考文档][ref-enums]的部分更详细地介绍了枚举。 + +## 样例类 + +样例类用于对不可变数据结构进行建模。 +举个例子: + +```scala +case class Person(name: String, relation: String) +``` + +由于我们将 `Person` 声明为样例类,因此默认情况下,字段 `name` 和 `relation` 是公共的和不可变的。 +我们可以创建 样例类的实例,如下所示: + +```scala +val christina = Person("Christina", "niece") +``` + +请注意,这些字段不能发生更改: + +```scala +christina.name = "Fred" // error: reassignment to val +``` + +由于 样例类的字段被假定为不可变的,因此 Scala 编译器可以为您生成许多有用的方法: + +* 生成一个 `unapply` 方法,该方法允许您对样例类执行模式匹配(即,`case Person(n, r) => ...`)。 +* 在类中生成一个 `copy` 方法,这对于创建实例的修改副本非常有用。 +* 生成使用结构相等的 `equals` 和 `hashCode` 方法,允许您在 `Map` 中使用样例类的实例。 +* 生成默认的 `toString` 方法,对调试很有帮助。 + +以下示例演示了这些附加功能: + +```scala +// Case classes can be used as patterns +christina match + case Person(n, r) => println("name is " + n) + +// `equals` and `hashCode` methods generated for you +val hannah = Person("Hannah", "niece") +christina == hannah // false + +// `toString` method +println(christina) // Person(Christina,niece) + +// built-in `copy` method +case class BaseballTeam(name: String, lastWorldSeriesWin: Int) +val cubs1908 = BaseballTeam("Chicago Cubs", 1908) +val cubs2016 = cubs1908.copy(lastWorldSeriesWin = 2016) +// result: +// cubs2016: BaseballTeam = BaseballTeam(Chicago Cubs,2016) +``` + +### 支持函数式编程 + +如前所述,样例类支持函数式编程 (FP): + +- 在FP中,您尽量避免改变数据结构。 + 因此,构造函数字段默认为 `val` 是有道理的。 + 由于无法更改样例类的实例,因此可以轻松共享它们,而不必担心突变或争用条件。 +- 您可以使用 `copy` 方法作为模板来创建新的(可能已更改的)实例,而不是改变实例。 + 此过程可称为“复制时更新”。 +- 自动为您生成 `unapply` 方法,还允许以模式匹配的高级方式使用样例类。 + +{% comment %} +NOTE: We can use this following text, if desired. If it’s used, it needs to be updated a little bit. + +### 一种 `unapply` 的方法 + +样例类的一大优点是,它可以为您的类自动生成一个 `unapply` 方法,因此您不必编写一个方法。 + +为了证明这一点,假设你有这个 trait: + +```scala +trait Person: + def name: String +``` + +然后,创建以下样例类以扩展该 trait: + +```scala +case class Student(name: String, year: Int) extends Person +case class Teacher(name: String, specialty: String) extends Person +``` + +由于它们被定义为样例类---并且它们具有内置的 `unapply` 方法---因此您可以编写如下匹配表达式: + +```scala +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` 语句中的以下两种模式: + +```scala +case Student(name, year) => +case Teacher(name, whatTheyTeach) => +``` + +这些模式之所以有效,是因为 `Student` 和 `Teacher` 被定义为具有 `unapply` 方法的样例类,其类型签名符合特定标准。 +从技术上讲,这些示例中显示的特定类型的模式匹配称为_结构模式匹配_。 + +> Scala 标准是, `unapply` 方法在包装在 `Option` 中的元组中返回 样例类构造函数字段。 +> 解决方案的“元组”部分在上一课中进行了演示。 + +要显示该代码的工作原理,请创建一个 `Student` 和 `Teacher` 的实例: + +```scala +val s = Student("Al", 1) +val t = Teacher("Bob Donnan", "Mathematics") +``` + +接下来,这是当您使用这两个实例来调用 `getPrintableString` 时,REPL 中的输出如下所示: + +```scala +scala> getPrintableString(s) +res0: String = Al is a student in Year 1. + +scala> getPrintableString(t) +res1: String = Bob Donnan teaches Mathematics. +``` + +>所有这些关于 `unapply` 方法和提取器的内容对于这样的入门书来说都有些先进,但是因为样例类是一个重要的FP主题,所以似乎最好涵盖它们,而不是跳过它们。 + +#### 将模式匹配添加到任何具有 unapply 的类型 + +Scala 的一个很好的特性是,你可以通过编写自己的 `unapply` 方法来向任何类型添加模式匹配。 +例如,此类在其伴生对象中定义了一个 `unapply` 方法: + +```scala +class Person(var name: String, var age: Int) +object Person: + def unapply(p: Person): Tuple2[String, Int] = (p.name, p.age) +``` + +因为它定义了一个 `unapply` 方法,并且因为该方法返回一个元组,所以您现在可以将 `Person` 与 `match` 表达式一起使用: + +```scala +val p = Person("Astrid", 33) + +p match + case Person(n,a) => println(s"name: $n, age: $a") + case null => println("No match") + +// that code prints: "name: Astrid, age: 33" +``` +{% endcomment %} + + +## 样例对象 + +样例对象之于对象,就像 样例类之于类:它们提供了许多自动生成的方法,以使其更加强大。 +每当您需要需要少量额外功能的单例对象时,它们特别有用,例如在 `match` 表达式中与模式匹配一起使用。 + +当您需要传递不可变消息时,样例对象非常有用。 +例如,如果您正在处理音乐播放器项目,您将创建一组命令或消息,如下所示: + +```scala +sealed trait Message +case class PlaySong(name: String) extends Message +case class IncreaseVolume(amount: Int) extends Message +case class DecreaseVolume(amount: Int) extends Message +case object StopPlaying extends Message +``` + +然后在代码的其他部分,你可以编写这样的方法,这些方法使用模式匹配来处理传入的消息(假设方法 `playSong` , `changeVolume` 和 `stopPlayingSong` 在其他地方定义): + +```scala +def handleMessages(message: Message): Unit = message match + case PlaySong(name) => playSong(name) + case IncreaseVolume(amount) => changeVolume(amount) + case DecreaseVolume(amount) => changeVolume(-amount) + case StopPlaying => stopPlayingSong() +``` + +[ref-enums]: {{ site.scala3ref }}/enums/enums.html +[adts]: {% link _overviews/scala3-book/types-adts-gadts.md %} +[fp-modeling]: {% link _overviews/scala3-book/domain-modeling-fp.md %} +[creator]: {{ site.scala3ref }}/other-new-features/creator-applications.html +[unapply]: {{ site.scala3ref }}/changed-features/pattern-matching.html +[trait-params]: {{ site.scala3ref }}/other-new-features/trait-parameters.html From d8cbad31d56ff9104a26b54441316ae95c9a8b93 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Tue, 24 May 2022 15:05:07 +0800 Subject: [PATCH 13/53] add _zh-cn/overviews/scala3-book/domain-modeling-oop.md --- .../scala3-book/domain-modeling-oop.md | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/domain-modeling-oop.md diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md new file mode 100644 index 0000000000..b00e71b62c --- /dev/null +++ b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md @@ -0,0 +1,317 @@ +--- +title: OOP 建模 +type: section +description: This chapter provides an introduction to OOP domain modeling with Scala 3. +num: 21 +previous-page: domain-modeling-tools +next-page: domain-modeling-fp +--- + + +本章介绍了在 Scala 3 中使用面向对象编程 (OOP) 进行领域建模。 + +## 介绍 + +Scala 为面向对象设计提供了所有必要的工具: + +- **Traits** 让您指定(抽象)接口以及具体实现。 +- **Mixin Composition** 为您提供了从较小的部分组成组件的工具。 +- **类**可以实现trait指定的接口。 +- 类的**实例**可以有自己的私有状态。 +- **Subtyping** 允许您在需要超类实例的地方使用一个类的实例。 +- **访问修饰符**允许您控制类的哪些成员可以被代码的哪个部分访问。 + +## Traits + +可能与支持 OOP 的其他语言(例如 Java)不同,Scala 中分解的主要工具不是类,而是trait。 +它们可以用来描述抽象接口,例如: + +```scala +trait Showable: + def show: String +``` + +并且还可以包含具体的实现: + +```scala +trait Showable: + def show: String + def showHtml = "

" + show + "

" +``` + +你可以看到我们用抽象方法 `show` 来定义方法 `showHtml`。 + +[Odersky and Zenger][scalable] 展示了 _面向服务的组件模型_ 和视图: + +- **抽象成员**作为_必须_服务:它们仍然需要由子类实现。 +- **具体成员**作为_提供_服务:它们被提供给子类。 + +我们已经可以在 `Showable` 的示例中看到这一点:定义一个扩展 `Showable` 的类 `Document`,我们仍然必须定义 `show`,但我们提供了 `showHtml`: + +```scala +class Document(text: String) extends Showable: + def show = text +``` + +#### 抽象成员 +抽象方法并不是trait中唯一可以抽象的东西。 +一个trait可以包含: + +- 抽象方法(`def m(): T`) +- 抽象值定义(`val x: T`) +- 抽象类型成员(`type T`),可能有界限(`type T <: S`) +- 抽象给定(`given t: T`) + +上述每个特性都可用于指定对 trait 实现者的某种形式的要求。 + +## 混入组合 + +traits 不仅可以包含抽象和具体的定义,Scala 还提供了一种组合多个 trait 的强大方法:这个特性通常被称为 _混入组合_。 + +让我们假设以下两个(可能独立定义的)traits: + +```scala +trait GreetingService: + def translate(text: String): String + def sayHello = translate("Hello") + +trait TranslationService: + def translate(text: String): String = "..." +``` + +要组合这两个服务,我们可以简单地创建一个扩展它们的新trait: + +```scala +trait ComposedService extends GreetingService, TranslationService +``` + +一个 trait 中的抽象成员(例如 `GreetingService` 中的 `translate`)会自动与另一个 trait 中的具体成员匹配。 +这不仅适用于本例中的方法,而且适用于上述所有其他抽象成员(即类型、值定义等)。 + +## 类 + +Traits 非常适合模块化组件和描述接口(必需和提供)。 +但在某些时候,我们会想要创建它们的实例。 +在 Scala 中设计软件时,只考虑在继承模型的叶子中使用类通常很有帮助: + +{% comment %} +NOTE: I think “leaves” may technically be the correct word to use, but I prefer “leafs.” +{% endcomment %} + +|Traits | `T1`, `T2`, `T3` +|组合 traits | `S extends T1, T2`, `S extends T2, T3` +|类 | `C extends S, T3` +|实例 | `C()` + +在 Scala 3 中更是如此,trait 现在也可以接受参数,进一步消除了对类的需求。 + +#### 定义类 + +像trait一样,类可以扩展多个trait(但只有一个超类): + +```scala +class MyService(name: String) extends ComposedService, Showable: + def show = s"$name says $sayHello" +``` + +#### 子类型化 + +我们可以创建一个 `MyService` 的实例,如下所示: + +```scala +val s1: MyService = MyService("Service 1") +``` + +通过子类型化的方式,我们的实例 `s1` 可以在任何扩展了trait的地方使用: + +```scala +val s2: GreetingService = s1 +val s3: TranslationService = s1 +val s4: Showable = s1 +// ... and so on ... +``` + +#### 扩展计划 + +如前所述,可以扩展另一个类: + +```scala +class Person(name: String) +class SoftwareDeveloper(name: String, favoriteLang: String) + extends Person(name) +``` + +然而,由于 _traits_ 被设计为主要的分解手段,在一个文件中定义的类_不能_在另一个文件中扩展。 +为了允许这样做,需要将基类标记为 `open`: + +```scala +open class Person(name: String) +``` + +用 [`open`][open] 标记类是 Scala 3 的一个新特性。必须将类显式标记为开放可以避免面向对象设计中的许多常见缺陷。 +特别是,它要求库设计者明确计划扩展,例如记录标记为开放的类以及附加的扩展合同。 + +{% comment %} +NOTE/FWIW: In his book, “Effective Java,” Joshua Bloch describes this as “Item 19: Design and document for inheritance or else prohibit it.” +Unfortunately I can’t find any good links to this on the internet. +I only mention this because I think that book and phrase is pretty well known in the Java world. +{% endcomment %} + +## 实例和私有可变状态 + +与其他支持 OOP 的语言一样,Scala 中的trait和类可以定义可变字段: + +```scala +class Counter: + // can only be observed by the method `count` + private var currentCount = 0 + + def tick(): Unit = currentCount += 1 + def count: Int = currentCount +``` + +`Counter` 类的每个实例都有自己的私有状态,只能通过方法 `count` 观察到,如下面的交互所示: + +```scala +val c1 = Counter() +c1.count // 0 +c1.tick() +c1.tick() +c1.count // 2 +``` + +#### 访问修饰符 + +默认情况下,Scala 中的所有成员定义都是公开可见的。 +要隐藏实现细节,可以将成员(方法、字段、类型等)定义为 `private` 或 `protected`。 +通过这种方式,您可以控制访问或覆盖它们的方式。 +私有成员仅对类/trait本身及其伴生对象可见。 +受保护的成员对类的子类也是可见的。 + +## 高级示例:面向服务的设计 + +在下文中,我们展示了 Scala 的一些高级特性,并展示了如何使用它们来构建更大的软件组件。 +这些示例改编自 Martin Odersky 和 ​​Matthias Zenger 的论文 ["Scalable Component Abstractions"][scalable]。 +如果您不了解示例的所有细节,请不要担心;它的主要目的是演示如何使用多种类型特性来构造更大的组件。 + +我们的目标是定义一个具有_类型族_的软件组件,以后可以在组件的实现中对其进行细化。 +具体来说,以下代码将组件 `SubjectObserver` 定义为具有两个抽象类型成员的trait, `S` (用于主题)和 `O` (用于观察者): + +```scala +trait SubjectObserver: + + type S <: Subject + type O <: Observer + + trait Subject { self: S => + private var observers: List[O] = List() + def subscribe(obs: O): Unit = + observers = obs :: observers + def publish() = + for obs <- observers do obs.notify(this) + } + + trait Observer { + def notify(sub: S): Unit + } +``` + +有几件事需要解释。 + +#### 抽象类型成员 + +声明 `type S <: Subject` 表示在 trait `SubjectObserver` 中我们可以引用一些我们称为 `S` 的_未知_(即抽象)类型。 +然而,该类型并不是完全未知的:我们至少知道它是trait `Subject` 的_某个子类型_。 +只要选择的类型是 `Subject` 的子类型,所有扩展自 `SubjectObserver` 的trait和类都可以自由地用于 `S`的类型。 +声明的 `<: Subject` 部分也称为 _`S` 的上界_。 + +#### 嵌套trait + +在 trait `SubjectObserver` _内_,我们定义了另外两个traits。 +让我们从 trait `Observer` 开始,它只定义了一个抽象方法 `notify`,它接受一个类型为 `S` 的参数。 +正如我们稍后将看到的,重要的是参数的类型为 `S` 而不是 `Subject` 类型。 + +第二个trait,`Subject`,定义了一个私有字段`observers`来存储所有订阅这个特定主题的观察者。 +订阅主题只是将对象存储到此列表中。 +同样,参数 `obs` 的类型是 `O`,而不是 `Observer`。 + +#### 自类型注解 + +最后,你可能想知道 trait `Subject` 上的 `self: S =>` 应该是什么意思。 +这称为 _自类型注解_。 +它要求 `Subject` 的子类型也是 `S` 的子类型。 +这对于能够使用 `this` 作为参数调用 `obs.notify` 是必要的,因为它需要 `S` 类型的值。 +如果 `S` 是一个_具体_类型,自类型注解可以被 `trait Subject extends S` 代替。 + +### 实现组件 + +我们现在可以实现上述组件并将抽象类型成员定义为具体类型: + +```scala +object SensorReader extends SubjectObserver: + type S = Sensor + type O = Display + + class Sensor(val label: String) extends Subject: + private var currentValue = 0.0 + def value = currentValue + def changeValue(v: Double) = + currentValue = v + publish() + + class Display extends Observer: + def notify(sub: Sensor) = + println(s"${sub.label} has value ${sub.value}") +``` + +具体来说,我们定义了一个扩展 `SubjectObserver` 的_单例_对象 `SensorReader`。 +在 `SensorReader` 的实现中,我们说 `S` 类型现在被定义为 `Sensor` 类型,`O` 类型被定义为等于 `Display` 类型。 +`Sensor` 和 `Display` 都被定义为 `SensorReader` 中的嵌套类,相应地实现了 `Subject` 和 `Observer` 特性。 + +除了作为面向服务设计的示例之外,这段代码还突出了面向对象编程的许多方面: + +- `Sensor` 类引入了它自己的私有状态(`currentValue`),并在方法`changeValue` 后面封装了对状态的修改。 +- `changeValue` 的实现使用扩展trait中定义的方法 `publish`。 +- 类 `Display` 扩展了 `Observer` 特性,并实现了缺失的方法 `notify`。 + +{% comment %} +NOTE: You might say “the abstract method `notify`” in that last sentence, but I like “missing.” +{% endcomment %} + +有一点很重要,需要指出,`notify` 的实现只能安全地访问 `sub` 的标签和值,因为我们最初将参数声明为 `S` 类型。 + +### 使用组件 + +最后,下面的代码说明了如何使用我们的 `SensorReader` 组件: + +```scala +import SensorReader.* + +// setting up a network +val s1 = Sensor("sensor1") +val s2 = Sensor("sensor2") +val d1 = Display() +val d2 = Display() +s1.subscribe(d1) +s1.subscribe(d2) +s2.subscribe(d1) + +// propagating updates through the network +s1.changeValue(2) +s2.changeValue(3) + +// prints: +// sensor1 has value 2.0 +// sensor1 has value 2.0 +// sensor2 has value 3.0 +``` + +借助我们掌握的所有面向对象的编程工具,在下一节中,我们将演示如何以函数式风格设计程序。 + +{% comment %} +NOTE: One thing I occasionally do is flip things like this around, so I first show how to use a component, and then show how to implement that component. I don’t have a rule of thumb about when to do this, but sometimes it’s motivational to see the use first, and then see how to create the code to make that work. +{% endcomment %} + +[scalable]: https://doi.org/10.1145/1094811.1094815 +[open]: {{ site.scala3ref }}/other-new-features/open-classes.html +[trait-params]: {{ site.scala3ref }}/other-new-features/trait-parameters.html From 799b1f28452ac6053a0bd71c3283badbc862b840 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Tue, 24 May 2022 18:24:46 +0800 Subject: [PATCH 14/53] add and modified as below --- .../scala3-book/domain-modeling-fp.md | 478 ++++++++++++++++++ .../scala3-book/domain-modeling-oop.md | 4 +- 2 files changed, 480 insertions(+), 2 deletions(-) create mode 100644 _zh-cn/overviews/scala3-book/domain-modeling-fp.md diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-fp.md b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md new file mode 100644 index 0000000000..7c43c0a1cb --- /dev/null +++ b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md @@ -0,0 +1,478 @@ +--- +title: FP 建模 +type: section +description: This chapter provides an introduction to FP domain modeling with Scala 3. +num: 22 +previous-page: domain-modeling-oop +next-page: methods-intro +--- + + +本章介绍了在 Scala 3 中使用函数式编程 (FP) 进行域建模。 +当使用 FP 对我们周围的世界进行建模时,您通常会使用以下 Scala 构造: + +- 枚举 +- 样例类 +- Traits + +> 如果您不熟悉代数数据类型 (ADT) 及其泛型版本 (GADT),您可能需要先阅读 [代数数据类型][adts] 部分,然后再阅读本节。 + +## 介绍 + +在 FP 中,*数据* 和*对该数据的操作* 是两个独立的东西;您不必像使用 OOP 那样将它们封装在一起。 + +这个概念类似于数值代数。 +当您考虑值大于或等于零的整数时,您有一*组*可能的值,如下所示: + +```` +0, 1, 2 ... Int.MaxValue +```` + +忽略整数的除法,对这些值可能的*操作*是: + +```` ++, -, * +```` + +FP设计以类似的方式实现: + +- 你描述你的值的集合(你的数据) +- 您描述了对这些值起作用的操作(您的函数) + +> 正如我们将看到的,这种风格的程序推理与面向对象的编程完全不同。 +> FP 中的数据只**是**: +> 将功能与数据分离,让您无需担心行为即可检查数据。 + +在本章中,我们将为比萨店中的“比萨”建模数据和操作。 +您将看到如何实现 Scala/FP 模型的“数据”部分,然后您将看到几种不同的方式来组织对该数据的操作。 + +## 数据建模 + +在 Scala 中,描述编程问题的数据模型很简单: + +- 如果您想使用不同的替代方案对数据进行建模,请使用 `enum` 结构 +- 如果您只想对事物进行分组(或需要更细粒度的控制),请使用 `case` 类 + +### 描述替代方案 + +简单地由不同的选择组成的数据,如面饼大小、面饼类型和馅料,使用 Scala 3 `enum` 结构进行简洁的建模: + +```scala +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions +``` + +> 描述不同选择的数据类型(如 `CrustSize`)有时也称为_聚合类型_。 + +### 描述复合数据 + +可以将比萨饼视为上述不同属性的_组件_容器。 +我们可以使用 `case` 类来描述 `Pizza` 由 `crustSize`、`crustType` 和可能的多个 `Topping` 组成: + +```scala +import CrustSize.* +import CrustType.* +import Topping.* + +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) +``` + +> 聚合多个组件的数据类型(如`Pizza`)有时也称为_产品类型_。 + +就是这样。 +这就是 FP 式比萨系统的数据模型。 +该解决方案非常简洁,因为它不需要将比萨饼上的操作与数据模型相结合。 +数据模型易于阅读,就像声明关系数据库的设计一样。 +创建数据模型的值并检查它们也很容易: + +```scala +val myFavPizza = Pizza(Small, Regular, Seq(Cheese, Pepperoni)) +println(myFavPizza.crustType) // prints Regular +``` + +#### 更多数据模型 + +我们可能会以同样的方式对整个比萨订购系统进行建模。 +下面是一些用于对此类系统建模的其他 `case` 类: + +```scala +case class Address( + street1: String, + street2: Option[String], + city: String, + state: String, + zipCode: String +) + +case class Customer( + name: String, + phone: String, + address: Address +) + +case class Order( + pizzas: Seq[Pizza], + customer: Customer +) +``` + +#### “瘦域对象” + +Debasish Ghosh 在他的《*功能和反应式域建模*》一书中指出,OOP 从业者将他们的类描述为封装数据和行为的“富域模型”,而 FP 数据模型可以被认为是“瘦域对象”。 +这是因为——正如本课所示——数据模型被定义为具有属性但没有行为的 `case` 类,从而产生了简短而简洁的数据结构。 + +## 操作建模 + +这就引出了一个有趣的问题:因为 FP 将数据与对该数据的操作分开,那么如何在 Scala 中实现这些操作? + +答案实际上很简单:您只需编写对我们刚刚建模的数据值进行操作的函数(或方法)。 +例如,我们可以定义一个计算比萨价格的函数。 + +```scala +def pizzaPrice(p: Pizza): Double = p match + case Pizza(crustSize, crustType, toppings) => + val base = 6.00 + val crust = crustPrice(crustSize, crustType) + val tops = toppings.map(toppingPrice).sum + base + crust + tops +``` + +您注意到函数的实现如何简单地遵循数据的样式:由于 `Pizza` 是一个样例类,我们使用模式匹配来提取组件并调用辅助函数来计算各个部分单独的价格。 + +```scala +def toppingPrice(t: Topping): Double = t match + case Cheese | Onions => 0.5 + case Pepperoni | BlackOlives | GreenOlives => 0.75 +``` + +同样,由于 `Topping` 是一个枚举,我们使用模式匹配来区分不同的变量。 +奶酪和洋葱的价格为 50ct,其余的价格为 75ct。 + +```scala +def crustPrice(s: CrustSize, t: CrustType): Double = + (s, t) match + // if the crust size is small or medium, + // the type is not important + case (Small | Medium, _) => 0.25 + case (Large, Thin) => 0.50 + case (Large, Regular) => 0.75 + case (Large, Thick) => 1.00 +``` + +为了计算面饼的价格,我们同时对面饼的大小和类型进行模式匹配。 + +> 关于上面显示的所有函数的重要一点是它们是*纯函数*:它们不会改变任何数据或有其他副作用(如抛出异常或写入文件)。 +> 他们所做的只是简单地接收值并计算结果。 + +{% comment %} +I’ve added this comment per [this Github comment](https://github.com/scalacenter/docs.scala-lang/pull/3#discussion_r543372428). +To that point, I’ve added these definitions here from our Slack conversation, in case anyone wants to update the “pure function” definition. If not, please delete this comment. + +Sébastien: +---------- +A function `f` is pure if, given the same input `x`, it will always return the same output `f(x)`, and it never modifies any state outside of it (therefore potentially causing other functions to behave differently in the future). + +Jonathan: +--------- +We say a function is 'pure' if it does not depend on or modify the context it is called in. + +Wikipedia +--------- +The function always evaluates to the same result value given the same argument value(s). It cannot depend on any hidden state or value, and it cannot depend on any I/O. +Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices. + +Mine (Alvin, now modified, from fp-pure-functions.md): +------------------------------------------------------ +- A function `f` is pure if, given the same input `x`, it always returns the same output `f(x)` +- The function’s output depends *only* on its input variables and its internal algorithm +- It doesn’t modify its input parameters +- It doesn’t mutate any hidden state +- It doesn’t have any “back doors”: It doesn’t read data from the outside world (including the console, web services, databases, files, etc.), or write data to the outside world +{% endcomment %} + +## 如何组织功能 + +在实现上面的 `pizzaPrice` 函数时,我们没有说我们将在*哪里*定义它。 +在 Scala 3 中,在文件的顶层定义它是完全有效的。 +但是,该语言为我们提供了许多很棒的工具在不同命名空间和模块中组织我们的逻辑。 + +有几种不同的方式来实现和组织行为: + +- 在伴生对象中定义您的函数 +- 使用模块化编程风格 +- 使用“函数式对象”方法 +- 在扩展方法中定义功能 + +在本节的其余部分将展示这些不同的解决方案。 + +### 伴生对象 + +第一种方法是在伴生对象中定义行为——函数。 + +> 正如在领域建模 [工具部分][modeling-tools] 中所讨论的,_伴生对象_ 是一个与类同名的 `object` ,并在与类相同的文件中声明。 + +使用这种方法,除了枚举或样例类之外,您还定义了一个包含该行为的同名伴生对象。 + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +// the companion object of case class Pizza +object Pizza: + // the implementation of `pizzaPrice` from above + def price(p: Pizza): Double = ... + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions + +// the companion object of enumeration Topping +object Topping: + // the implementation of `toppingPrice` above + def price(t: Topping): Double = t match + case Cheese | Onions => 0.5 + case Pepperoni | BlackOlives | GreenOlives => 0.75 +``` + +使用这种方法,您可以创建一个 `Pizza` 并计算其价格,如下所示: + +```scala +val pizza1 = Pizza(Small, Thin, Seq(Cheese, Onions)) +Pizza.price(pizza1) +``` + +以这种方式对功能进行分组有几个优点: + +- 它将功能与数据相关联,让程序员(和编译器)更容易找到它。 +- 它创建了一个命名空间,例如让我们使用 `price` 作为方法名称,而不必依赖重载。 +- `Topping.price` 的实现可以访问枚举值,例如 `Cheese` ,而无需导入它们。 + +但是,还应权衡: + +- 它将功能与您的数据模型紧密结合。 + 特别是,伴生对象需要在与您的 `case` 类相同的文件中定义。 +- 可能不清楚在哪里定义像 `crustPrice` 这样同样可以放置在 `CrustSize` 或 `CrustType` 的伴生对象中的函数。 + +## 模块 + +组织行为的第二种方法是使用“模块化”方法。 +这本书,*Programming in Scala*,将 *模块* 定义为“具有良好定义的接口和隐藏实现的‘较小的程序片段’”。 +让我们看看这意味着什么。 + +### 创建一个 `PizzaService` 接口 + +首先要考虑的是 `Pizza` 的“行为”。 +执行此操作时,您可以像这样草拟一个 `PizzaServiceInterface` trait: + +```scala +trait PizzaServiceInterface: + + def price(p: Pizza): Double + + def addTopping(p: Pizza, t: Topping): Pizza + def removeAllToppings(p: Pizza): Pizza + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza + def updateCrustType(p: Pizza, ct: CrustType): Pizza +``` + +如图所示,每个方法都将 `Pizza` 作为输入参数——连同其他参数——然后返回一个 `Pizza` 实例作为结果 + +当你写一个像这样的纯接口时,你可以把它想象成一个约定,“所有扩展这个特性的非抽象类*必须*提供这些服务的实现。” + +此时您还可以做的是想象您是此 API 的使用者。 +当你这样做时,它有助于草拟一些示例“消费者”代码,以确保 API 看起来像你想要的: + +```scala +val p = Pizza(Small, Thin, Seq(Cheese)) + +// how you want to use the methods in PizzaServiceInterface +val p1 = addTopping(p, Pepperoni) +val p2 = addTopping(p1, Onions) +val p3 = updateCrustType(p2, Thick) +val p4 = updateCrustSize(p3, Large) +``` + +如果该代码看起来没问题,您通常会开始草拟另一个 API ——例如用于订单的 API ——但由于我们现在只关注比萨饼,我们将停止考虑接口,然后创建这个接口的具体实现。 + +> 请注意,这通常是一个两步过程。 +> 在第一步中,您将 API 的合同草拟为*接口*。 +> 在第二步中,您创建该接口的具体*实现*。 +> 在某些情况下,您最终会创建基本接口的多个具体实现。 + +### 创建一个具体的实现 + +现在您知道了 `PizzaServiceInterface` 的样子,您可以通过为接口中定义的所有方法体来创建它的具体实现: + +```scala +object PizzaService extends PizzaServiceInterface: + + def price(p: Pizza): Double = + ... // implementation from above + + def addTopping(p: Pizza, t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings(p: Pizza): Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(p: Pizza, cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(p: Pizza, ct: CrustType): Pizza = + p.copy(crustType = ct) + +end PizzaService +``` + +虽然创建接口和实现的两步过程并不总是必要的,但明确考虑 API 及其使用是一种好方法。 + +一切就绪后,您可以使用 `Pizza` 类和 `PizzaService`: + +```scala +import PizzaService.* + +val p = Pizza(Small, Thin, Seq(Cheese)) + +// use the PizzaService methods +val p1 = addTopping(p, Pepperoni) +val p2 = addTopping(p1, Onions) +val p3 = updateCrustType(p2, Thick) +val p4 = updateCrustSize(p3, Large) + +println(price(p4)) // prints 8.75 +``` + +### 函数对象 + +在 *Programming in Scala* 一书中,作者将术语“函数对象”定义为“不具有任何可变状态的对象”。 +`scala.collection.immutable` 中的类型也是如此。 +例如,`List` 上的方法不会改变内部状态,而是创建 `List` 的副本作为结果。 + +您可以将此方法视为“混合 FP/OOP 设计”,因为您: + +- 使用不可变的 `case` 类对数据进行建模。 +- 定义_同类型_数据中的行为(方法)。 +- 将行为实现为纯函数:它们不会改变任何内部状态;相反,他们返回一个副本。 + +> 这确实是一种混合方法:就像在 **OOP 设计**中一样,方法与数据一起封装在类中, +> 但作为典型的 **FP 设计**,方法被实现为纯函数,该函数不改变数据 + +#### 例子 + +使用这种方法,您可以在案例案例中直接实现比萨上的功能: +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +): + + // the operations on the data model + def price: Double = + pizzaPrice(this) // implementation from above + + def addTopping(t: Topping): Pizza = + this.copy(toppings = this.toppings :+ t) + + def removeAllToppings: Pizza = + this.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + this.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + this.copy(crustType = ct) +``` + +请注意,与之前的方法不同,因为这些是 `Pizza` 类上的方法,它们不会将 `Pizza` 引用作为输入参数。 +相反,他们用 `this` 作为当前比萨实例的引用。 + +现在你可以像这样使用这个新设计: + +```scala +Pizza(Small, Thin, Seq(Cheese)) + .addTopping(Pepperoni) + .updateCrustType(Thick) + .price +``` + +### 扩展方法 + +最后,我们展示了一种介于第一个(在伴生对象中定义函数)和最后一个(将函数定义为类型本身的方法)之间的方法。 + +扩展方法让我们创建一个类似于函数对象的 API,而不必将函数定义为类型本身的方法。 +这可以有多个优点: + +- 我们的数据模型再次_非常简洁_并且没有提及任何行为。 +- 我们可以_追溯性地_为类型配备额外的方法,而无需更改原始定义。 +- 除了伴生对象或类型上的直接方法外,扩展方法可以在_外部_另一个文件中定义。 + +让我们再次回顾一下我们的例子。 + +```scala +case class Pizza( + crustSize: CrustSize, + crustType: CrustType, + toppings: Seq[Topping] +) + +extension (p: Pizza) + def price: Double = + pizzaPrice(p) // implementation from above + + def addTopping(t: Topping): Pizza = + p.copy(toppings = p.toppings :+ t) + + def removeAllToppings: Pizza = + p.copy(toppings = Seq.empty) + + def updateCrustSize(cs: CrustSize): Pizza = + p.copy(crustSize = cs) + + def updateCrustType(ct: CrustType): Pizza = + p.copy(crustType = ct) +``` + +在上面的代码中,我们将比萨上的不同方法定义为_扩展方法_。 +对于 `extension (p: Pizza)`,我们想在 `Pizza` 的实例上让方法可用,并在下文中把扩展的实例称为 `p`。 + +这样我们就可以获得和之前一样的API + +```scala +Pizza(Small, Thin, Seq(Cheese)) + .addTopping(Pepperoni) + .updateCrustType(Thick) + .price +``` + +同时能够在任何其他模块中定义扩展。 +通常,如果您是数据模型的设计者,您将在伴生对象中定义您的扩展方法。 +这样,它们已经可供所有用户使用。 +否则,扩展方法需要显式导入才能使用。 + +## 这种方法的总结 + +在 Scala/FP 中定义数据模型往往很简单:只需使用枚举对数据的变体进行建模,并使用 `case` 类对复合数据进行建模。 +然后,为了对行为建模,定义对数据模型的值进行操作的函数。 +我们已经看到了组织函数的不同方法: + +- 你可以把你的方法放在伴生对象中 +- 您可以使用模块化编程风格,分离接口和实现 +- 您可以使用“函数对象”方法并将方法存储在定义的数据类型上 +- 您可以使用扩展方法把函数装配到数据模型上 + +[adts]: {% link _overviews/scala3-book/types-adts-gadts.md %} +[modeling-tools]: {% link _overviews/scala3-book/domain-modeling-tools.md %} diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md index b00e71b62c..1510152aef 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md @@ -98,8 +98,8 @@ Traits 非常适合模块化组件和描述接口(必需和提供)。 NOTE: I think “leaves” may technically be the correct word to use, but I prefer “leafs.” {% endcomment %} -|Traits | `T1`, `T2`, `T3` -|组合 traits | `S extends T1, T2`, `S extends T2, T3` +|Traits | `T1`, `T2`, `T3` +|组合 traits | `S extends T1, T2`, `S extends T2, T3` |类 | `C extends S, T3` |实例 | `C()` From 3dd2ec3f609fe17a171be24f9f5b60ad60d54584 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Tue, 24 May 2022 21:10:37 +0800 Subject: [PATCH 15/53] add all methods* --- _zh-cn/overviews/scala3-book/methods-intro.md | 16 + .../scala3-book/methods-main-methods.md | 135 ++++++ _zh-cn/overviews/scala3-book/methods-most.md | 386 ++++++++++++++++++ .../overviews/scala3-book/methods-summary.md | 22 + 4 files changed, 559 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/methods-intro.md create mode 100644 _zh-cn/overviews/scala3-book/methods-main-methods.md create mode 100644 _zh-cn/overviews/scala3-book/methods-most.md create mode 100644 _zh-cn/overviews/scala3-book/methods-summary.md diff --git a/_zh-cn/overviews/scala3-book/methods-intro.md b/_zh-cn/overviews/scala3-book/methods-intro.md new file mode 100644 index 0000000000..bba0ebfe1c --- /dev/null +++ b/_zh-cn/overviews/scala3-book/methods-intro.md @@ -0,0 +1,16 @@ +--- +title: 方法 +type: chapter +description: This section introduces methods in Scala 3. +num: 23 +previous-page: domain-modeling-fp +next-page: methods-most +--- + + +在 Scala 2 中,_方法_可以在类、traits、对象、样例类和样例对象中定义。 +但它变得更好了:在Scala 3中,可以在这些结构的_外部_定义方法;我们说它们是“顶级”定义,因为它们没有嵌套在另一个定义中。 +简而言之,现在可以在任何地方定义方法。 + +方法的许多功能将在下一节中演示。 +由于 `main` 方法需要更多的解释,因此后面有单独部分对其进行描述。 diff --git a/_zh-cn/overviews/scala3-book/methods-main-methods.md b/_zh-cn/overviews/scala3-book/methods-main-methods.md new file mode 100644 index 0000000000..0457b45cc7 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/methods-main-methods.md @@ -0,0 +1,135 @@ +--- +title: main 方法 +type: section +description: This page describes how 'main' methods and the '@main' annotation work in Scala 3. +num: 25 +previous-page: methods-most +next-page: methods-summary +--- + + + +Scala 3 提供了一种定义可以从命令行调用的程序的新方法:在方法中添加 `@main` 注释会将其变成可执行程序的入口点: + +```scala +@main def hello() = println("Hello, world") +``` + +只需将该行代码保存在一个名为 *Hello.scala* 的文件中——文件名不必与方法名匹配——并使用 `scalac` 编译它: + +```bash +$ scalac Hello.scala +``` + +然后用 `scala` 运行它: + +```bash +$ scala hello +Hello, world +``` + +`@main` 注释方法可以写在顶层(如图所示),也可以写在静态可访问的对象中。 +在任何一种情况下,程序的名称都是方法的名称,没有任何对象前缀。 + +### 命令行参数 + +使用这种方法,您的`@main` 方法可以处理命令行参数,并且这些参数可以有不同的类型。 +例如,给定这个 `@main` 方法,它接受一个 `Int`、一个 `String` 和一个可变参数 `String*` 参数: + +```scala +@main def happyBirthday(age: Int, name: String, others: String*) = + val suffix = (age % 100) match + case 11 | 12 | 13 => "th" + case _ => (age % 10) match + case 1 => "st" + case 2 => "nd" + case 3 => "rd" + case _ => "th" + + val sb = StringBuilder(s"Happy $age$suffix birthday, $name") + for other <- others do sb.append(" and ").append(other) + sb.toString +``` + +当你编译该代码时,它会创建一个名为 `happyBirthday` 的主程序,它的调用方式如下: + +``` +$ scala happyBirthday 23 Lisa Peter +Happy 23rd Birthday, Lisa and Peter! +``` + +如图所示,`@main` 方法可以有任意数量的参数。 +对于每个参数类型,必须有一个 *scala.util.FromString* 类型类的实例,它将参数 `String` 转换为所需的参数类型。 +同样如图所示,主方法的参数列表可以以重复参数结尾,例如 `String*`,它接受命令行中给出的所有剩余参数。 + +从 `@main` 方法实现的程序检查命令行上是否有足够的参数来填充所有参数,以及参数字符串是否可以转换为所需的类型。 +如果检查失败,程序将终止并显示错误消息: + +``` +$ scala happyBirthday 22 +Illegal command line after first argument: more arguments expected + +$ scala happyBirthday sixty Fred +Illegal command line: java.lang.NumberFormatException: For input string: "sixty" +``` + +## 细节 + +Scala 编译器从 `@main` 方法 `f` 生成程序,如下所示: + +- 它在有 `@main` 方法的包中创建一个名为 `f` 的类。 +- 该类有一个静态方法 `main`,具有 Java `main` 方法的通常签名:它以 `Array[String]` 作为参数并返回 `Unit`。 +- 生成的 `main` 方法调用方法 `f` 并使用 `scala.util.CommandLineParser` 对象中的方法转换参数。 + +例如,上面的 `happyBirthday` 方法会生成与以下类等效的附加代码: + +```scala +final class happyBirthday { + import scala.util.{CommandLineParser as CLP} + def main(args: Array[String]): Unit = + try + happyBirthday( + CLP.parseArgument[Int](args, 0), + CLP.parseArgument[String](args, 1), + CLP.parseRemainingArguments[String](args, 2)) + catch { + case error: CLP.ParseError => CLP.showError(error) + } +} +``` + +> **注意**:在这个生成的代码中,`` 修饰符表示 `main` 方法是作为 `happyBirthday` 类的静态方法生成的。 +> 此功能不适用于 Scala 中的用户程序。 +> 常规“静态”成员在 Scala 中使用对象生成。 + +## Scala 3 与 Scala 2 的比较 + +`@main` 方法是在 Scala 3 中生成可以从命令行调用的程序的推荐方法。 +它们取代了 Scala 2 中以前的方法,即创建一个扩展 `App` 类的 `object` : + +```scala +// scala 2 +object happyBirthday extends App { + // needs by-hand parsing of the command line arguments ... +} +``` + +之前依赖于“神奇”的 `DelayedInit` trait 的 `App` 功能不再可用。 +`App` 目前仍以有限的形式存在,但它不支持命令行参数,将来会被弃用。 + +如果程序需要在 Scala 2 和 Scala 3 之间交叉构建,建议使用带有 `Array[String]` 参数的显式 `main` 方法: + +```scala +object happyBirthday: + def main(args: Array[String]) = println("Hello, world") +``` + +如果将该代码放在名为 *happyBirthday.scala* 的文件中,则可以使用 `scalac` 编译它并使用 `scala` 运行它,如前所示: + +```bash +$ scalac happyBirthday.scala + +$ scala happyBirthday +Hello, world +``` + diff --git a/_zh-cn/overviews/scala3-book/methods-most.md b/_zh-cn/overviews/scala3-book/methods-most.md new file mode 100644 index 0000000000..abc8cd96df --- /dev/null +++ b/_zh-cn/overviews/scala3-book/methods-most.md @@ -0,0 +1,386 @@ +--- +title: 方法特性 +type: section +description: This section introduces Scala 3 methods, including main methods, extension methods, and more. +num: 24 +previous-page: methods-intro +next-page: methods-main-methods +--- + + +本节介绍如何在 Scala 3 中定义和调用方法的各个方面。 + +## 定义方法 + +Scala 方法有很多特性,包括: + +- 通用(类型)参数 +- 默认参数值 +- 多个参数组 +- 上下文提供的参数 +- 按名称参数 +- ... + +本节演示了其中一些功能,但是当您定义一个不使用这些功能的“简单”方法时,语法如下所示: + +```scala +def methodName(param1: Type1, param2: Type2): ReturnType = + // the method body + // goes here +end methodName // this is optional +``` + +在该语法中: + +- 关键字 `def` 用于定义方法 +- Scala 标准是使用驼峰式命法来命名方法 +- 方法参数总是和它们的类型一起定义 +- 声明方法返回类型是可选的 +- 方法可以包含多行,也可以只包含一行 +- 在方法体之后提供 `end methodName` 部分也是可选的,仅推荐用于长方法 + +下面是一个名为 `add` 的单行方法的两个示例,它接受两个 `Int` 输入参数。 +第一个版本明确显示方法的 `Int` 返回类型,第二个版本没有: + +```scala +def add(a: Int, b: Int): Int = a + b +def add(a: Int, b: Int) = a + b +``` + +建议使用返回类型注释公开可见的方法。 +声明返回类型可以让您在几个月或几年后查看它或查看其他人的代码时更容易理解它。 + +## 调用方法 + +调用方法很简单: + +```scala +val x = add(1, 2) // 3 +``` + +Scala 集合类有几十个内置方法。 +这些示例显示了如何调用它们: + +```scala +val x = List(1, 2, 3) + +x.size // 3 +x.contains(1) // true +x.map(_ * 10) // List(10, 20, 30) +``` + +注意: + +- `size` 不带参数,并返回列表中元素的数量 +- `contains` 方法接受一个参数,即要搜索的值 +- `map` 接受一个参数,一个函数;在这种情况下,一个匿名函数被传递给它 + +## 多行方法 + +当方法超过一行时,从第二行开始方法体,向右缩进: + +```scala +def addThenDouble(a: Int, b: Int): Int = + // imagine that this body requires multiple lines + val sum = a + b + sum * 2 +``` + +在那个方法中: + +- `sum` 是一个不可变的局部变量;它不能在方法之外访问 +- 最后一行将 `sum` 的值加倍;这个值是从方法返回的 + +当您将该代码粘贴到 REPL 中时,您会看到它按预期工作: + +```scala +scala> addThenDouble(1, 1) +res0: Int = 4 +``` + +请注意,方法末尾不需要 `return` 语句。 +因为在 Scala 中几乎所有的东西都是一个_表达式_——意味着每一行代码都返回(或_执行_)一个值——不需要使用 `return`。 + +当您压缩该方法并将其写在一行上时,这一点变得更加清晰: + +```scala +def addThenDouble(a: Int, b: Int): Int = (a + b) * 2 +``` + +方法的主体可以使用语言的所有不同特性: + +- `if`/`else` 表达式 +- `match` 表达式 +- `while` 循环 +- `for` 循环和 `for` 表达式 +- 变量赋值 +- 调用其他方法 +- 其他方法的定义 + +作为一个真实世界的多行方法的例子,这个 `getStackTraceAsString` 方法将它的 `Throwable` 输入参数转换成一个格式良好的 `String`: + +```scala +def getStackTraceAsString(t: Throwable): String = + val sw = StringWriter() + t.printStackTrace(PrintWriter(sw)) + sw.toString +``` + +在那个方法中: + +- 第一行将 `StringWriter` 的新实例分配给值绑定器 `sw` +- 第二行将堆栈跟踪内容存储到 `StringWriter` +- 第三行产生堆栈跟踪的 `String` 表示 + +## 默认参数值 + +方法参数可以有默认值。 +在此示例中,为 `timeout` 和 `protocol` 参数提供了默认值: + +```scala +def makeConnection(timeout: Int = 5_000, protocol: String = "http") = + println(f"timeout = ${timeout}%d, protocol = ${protocol}%s") + // more code here ... +``` + +由于参数具有默认值,因此可以通过以下方式调用该方法: + +```scala +makeConnection() // timeout = 5000, protocol = http +makeConnection(2_000) // timeout = 2000, protocol = http +makeConnection(3_000, "https") // timeout = 3000, protocol = https +``` + +以下是关于这些示例的一些要点: + +- 在第一个示例中,没有提供任何参数,因此该方法使用默认参数值 `5_000` 和 `http` +- 在第二个示例中,为 `timeout` 值提供了 `2_000`,因此它与 `protocol` 的默认值一起使用 +- 在第三个示例中,为两个参数提供了值,因此它们都被使用 + +请注意,通过使用默认参数值,消费者似乎可以使用三种不同的重载方法。 + +## 命名参数 + +如果您愿意,也可以在调用方法时使用方法参数的名称。 +例如,`makeConnection` 也可以通过以下方式调用: + +```scala +makeConnection(timeout=10_000) +makeConnection(protocol="https") +makeConnection(timeout=10_000, protocol="https") +makeConnection(protocol="https", timeout=10_000) +``` + +在某些框架中,命名参数被大量使用。 +当多个方法参数具有相同类型时,它们也非常有用: + +```scala +engage(true, true, true, false) +``` + +如果没有 IDE 的帮助,代码可能难以阅读,但这段代码更加清晰和明显: + +```scala +engage( + speedIsSet = true, + directionIsSet = true, + picardSaidMakeItSo = true, + turnedOffParkingBrake = false +) +``` + +## 关于不带参数的方法的建议 + +当一个方法不带参数时,它的 _arity_ 级别为 _arity-0_。 +类似地,当一个方法采用一个参数时,它是一个_arity-1_方法。 +当您创建 arity-0 方法时: + +- 如果方法执行副作用,例如调用`println`,用空括号声明方法 +- 如果该方法不执行副作用——例如获取集合的大小,这类似于访问集合上的字段——请去掉括号 + +例如,这个方法会产生副作用,所以它用空括号声明: + +```scala +def speak() = println("hi") +``` + +这样做需要方法的调用者在调用方法时使用括号: + +```scala +speak // error: "method speak must be called with () argument" +speak() // prints "hi" +``` + +虽然这只是一个约定,但遵循它可以显着提高代码的可读性:它可以让您更容易一目了然地理解 arity-0 方法执行副作用。 + +{% comment %} +Some of that wording comes from this page: https://docs.scala-lang.org/style/method-invocation.html +{% endcomment %} + +## 使用 `if` 作为方法体 + +因为 `if`/`else` 表达式返回一个值,所以它们可以用作方法的主体。 +这是一个名为 `isTruthy` 的方法,它实现了 Perl 对 `true` 和 `false` 的定义: + +```scala +def isTruthy(a: Any) = + if a == 0 || a == "" || a == false then + false + else + true +``` + +这些示例显示了该方法的工作原理: + +```scala +isTruthy(0) // false +isTruthy("") // false +isTruthy("hi") // true +isTruthy(1.0) // true +``` + +## 使用 `match` 作为方法体 + +`match` 表达式也可以用作整个方法体,而且经常如此。 +这是 `isTruthy` 的另一个版本,用 `match` 表达式编写: + +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" | false => false + case _ => true +``` + +此方法的工作方式与之前使用 `if`/`else` 表达式的方法一样。我们使用 `Matchable` 而不是 `Any` 作为参数的类型来接受任何支持模式匹配的值。 + +有关 `Matchable` trait 的更多详细信息,请参阅 [参考文档][reference_matchable]。 + +## 控制类中的可见性 + +在类、对象、trait和枚举中,Scala 方法默认是公共的,所以这里创建的 `Dog` 实例可以访问 `speak` 方法: + +```scala +class Dog: + def speak() = println("Woof") + +val d = new Dog +d.speak() // prints "Woof" +``` + +方法也可以标记为 `private`。 +这使得它们对当前类是私有的,因此它们不能在子类中被调用或重载: + +```scala +class Animal: + private def breathe() = println("I’m breathing") + +class Cat extends Animal: + // this method won’t compile + override def breathe() = println("Yo, I’m totally breathing") +``` + +如果你想让一个方法对当前类私有,并且允许子类调用它或覆盖它,将该方法标记为 `protected`,如本例中的 `speak` 方法所示: + +```scala +class Animal: + private def breathe() = println("I’m breathing") + def walk() = + breathe() + println("I’m walking") + protected def speak() = println("Hello?") + +class Cat extends Animal: + override def speak() = println("Meow") + +val cat = new Cat +cat.walk() +cat.speak() +cat.breathe() // won’t compile because it’s private +``` + +`protected` 设置意味着: + +- 方法(或字段)可以被同一类的其他实例访问 +- 对当前包中的其他代码是不可见的 +- 它适用于子类 + +## 对象可以包含方法 + +之前你看到trait和类可以有方法。 +Scala `object` 关键字用于创建单例类,对象也可以包含方法。 +这是对一组“实用程序”方法进行分组的好方法。 +例如,此对象包含一组处理字符串的方法: + +```scala +object StringUtils: + + /** + * Returns a string that is the same as the input string, but + * truncated to the specified length. + */ + def truncate(s: String, length: Int): String = s.take(length) + + /** + * Returns true if the string contains only letters and numbers. + */ + def lettersAndNumbersOnly_?(s: String): Boolean = + s.matches("[a-zA-Z0-9]+") + + /** + * Returns true if the given string contains any whitespace + * at all. Assumes that `s` is not null. + */ + def containsWhitespace(s: String): Boolean = + s.matches(".*\\s.*") + +end StringUtils +``` + +## 扩展方法 + +扩展方法在上下文抽象一章的[扩展方法部分][extension]中讨论。 +它们的主要目的是让您向封闭类添加新功能。 +如该部分所示,假设您有一个 `Circle` 类,但您无法更改其源代码。 +例如,它可以在第三方库中这样定义: + +```scala +case class Circle(x: Double, y: Double, radius: Double) +``` + +当你想给这个类添加方法时,你可以将它们定义为扩展方法,像这样: + +```scala +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 +``` + +现在,当您有一个名为 `aCircle` 的 `Circle` 实例时,您可以像这样调用这些方法: + +```scala +aCircle.circumference +aCircle.diameter +aCircle.area +``` + +请参阅本书的[扩展方法部分][reference_extension_methods],以及[“扩展方法”参考页面][reference]了解更多详细信息。 + +## 更多 + +还有更多关于方法的知识,包括如何: + +- 调用超类的方法 +- 定义和使用按名称参数 +- 编写一个带有函数参数的方法 +- 创建内嵌方法 +- 处理异常 +- 使用可变参数输入参数 +- 编写具有多个参数组的方法(部分应用的函数) +- 创建具有泛型类型参数的方法 + +有关这些功能的更多详细信息,请参阅 [参考文档][reference]。 + +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[reference_extension_methods]: {{ site.scala3ref }}/contextual/extension-methods.html +[reference]: {{ site.scala3ref }}/overview.html +[reference_matchable]: {{ site.scala3ref }}/other-new-features/matchable.html diff --git a/_zh-cn/overviews/scala3-book/methods-summary.md b/_zh-cn/overviews/scala3-book/methods-summary.md new file mode 100644 index 0000000000..6bea602874 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/methods-summary.md @@ -0,0 +1,22 @@ +--- +title: 总结 +type: section +description: This section summarizes the previous sections on Scala 3 methods. +num: 26 +previous-page: methods-main-methods +next-page: fun-intro +--- + + +还有更多关于方法的知识,包括如何: + +- 调用超类的方法 +- 定义和使用按名称参数 +- 编写一个带有函数参数的方法 +- 创建内嵌方法 +- 处理异常 +- 使用可变参数输入参数 +- 编写具有多个参数组的方法(部分应用的函数) +- 创建具有泛型类型参数的方法 + +[reference]: {{ site.scala3ref }}/overview.html From 8390a956fda475592c5342ee46d041012f28d8c8 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Thu, 26 May 2022 22:11:18 +0800 Subject: [PATCH 16/53] add functions --- .../scala3-book/fun-anonymous-functions.md | 133 ++++++++ .../scala3-book/fun-eta-expansion.md | 83 +++++ .../scala3-book/fun-function-variables.md | 112 +++++++ _zh-cn/overviews/scala3-book/fun-hofs.md | 288 ++++++++++++++++++ _zh-cn/overviews/scala3-book/fun-intro.md | 11 + _zh-cn/overviews/scala3-book/fun-summary.md | 34 +++ .../scala3-book/fun-write-map-function.md | 86 ++++++ .../fun-write-method-returns-function.md | 162 ++++++++++ 8 files changed, 909 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/fun-anonymous-functions.md create mode 100644 _zh-cn/overviews/scala3-book/fun-eta-expansion.md create mode 100644 _zh-cn/overviews/scala3-book/fun-function-variables.md create mode 100644 _zh-cn/overviews/scala3-book/fun-hofs.md create mode 100644 _zh-cn/overviews/scala3-book/fun-intro.md create mode 100644 _zh-cn/overviews/scala3-book/fun-summary.md create mode 100644 _zh-cn/overviews/scala3-book/fun-write-map-function.md create mode 100644 _zh-cn/overviews/scala3-book/fun-write-method-returns-function.md diff --git a/_zh-cn/overviews/scala3-book/fun-anonymous-functions.md b/_zh-cn/overviews/scala3-book/fun-anonymous-functions.md new file mode 100644 index 0000000000..e4576252bf --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-anonymous-functions.md @@ -0,0 +1,133 @@ +--- +title: 匿名函数 +type: section +description: This page shows how to use anonymous functions in Scala, including examples with the List class 'map' and 'filter' functions. +num: 28 +previous-page: fun-intro +next-page: fun-function-variables +--- + + +匿名函数(也称为 *lambda*)是作为参数传递给高阶函数的代码块。 +维基百科将 [匿名函数](https://en.wikipedia.org/wiki/Anonymous_function) 定义为“未绑定到标识符的函数定义”。 + +例如,给定这样的列表: + +```scala +val ints = List(1, 2, 3) +``` + +您可以通过使用 `List` 类 `map` 方法和自定义匿名函数将 `ints` 中的每个元素加倍来创建一个新列表: + +```scala +val doubledInts = ints.map(_ * 2) // List(2, 4, 6) +``` + +如注释所示,`doubledInts` 包含列表`List(2, 4, 6)`。 +在该示例中,这部分代码是一个匿名函数: + +```scala +_ * 2 +``` + +这是“将给定元素乘以 2”的简写方式。 + +## 更长的形式 + +一旦你熟悉了 Scala,你就会一直使用这种形式来编写匿名函数,这些函数在函数的一个位置使用一个变量。 +但如果你愿意,你也可以用更长的形式来写它们,所以除了写这段代码: + +```scala +val doubledInts = ints.map(_ * 2) +``` + +您也可以使用以下形式编写它: + +```scala +val doubledInts = ints.map((i: Int) => i * 2) +val doubledInts = ints.map((i) => i * 2) +val doubledInts = ints.map(i => i * 2) +``` + +所有这些行的含义完全相同:将 `ints` 中的每个元素加倍以创建一个新列表 `doubledInts`。 +(稍后会解释每种形式的语法。) + +如果您熟悉 Java,了解这些 `map` 示例与以下 Java 代码等价可能会有所帮助: + +```java +List ints = List.of(1, 2, 3); +List doubledInts = ints.stream() + .map(i -> i * 2) + .collect(Collectors.toList()); +``` + +## 缩短匿名函数 + +当你想要明确时,你可以使用这个长格式编写一个匿名函数: + +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` + +该表达式中的匿名函数是这样的: + +```scala +(i: Int) => i * 2 +``` + +如果您不熟悉这种语法,将 `=>` 符号视为转换器会有所帮助,因为表达式使用 `=>` 符号右侧的算法(在这种情况下,一个将 `Int` 加倍的表达式)把符号左侧的参数列表(名为 `i` 的 `Int` 变量) *转换*为新结果。 + +### 缩短该表达式 + +这种长形式可以缩短,如以下步骤所示。 +首先,这是最长和最明确的形式: + +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` + +因为 Scala 编译器可以从 `ints` 中的数据推断 `i` 是一个 `Int`,所以可以删除 `Int` 声明: + +```scala +val doubledInts = ints.map((i) => i * 2) +``` + +因为只有一个参数,所以不需要在参数 `i` 周围的括号: + +```scala +val doubledInts = ints.map(i => i * 2) +``` + +因为当参数在函数中只出现一次时,Scala 允许您使用 `_` 符号而不是变量名,所以代码可以进一步简化: + +```scala +val doubledInts = ints.map(_ * 2) +``` + +### 变得更短 + +在其他示例中,您可以进一步简化匿名函数。 +例如,从最显式的形式开始,您可以使用带有 `List` 类 `foreach` 方法的匿名函数打印 `ints` 中的每个元素: + +```scala +ints.foreach((i: Int) => println(i)) +``` + +和以前一样,不需要 `Int` 声明,因为只有一个参数,所以不需要 `i` 周围的括号: + +```scala +ints.foreach(i => println(i)) +``` + +因为 `i` 在函数体中只使用一次,表达式可以进一步简化为 `_` 符号: + +```scala +ints.foreach(println(_)) +``` + +最后,如果一个匿名函数由一个接受单个参数的方法调用组成,您不需要显式命名和指定参数,因此您最终可以只写方法的名称(此处为 `println`): + +```scala +ints.foreach(println) +``` + diff --git a/_zh-cn/overviews/scala3-book/fun-eta-expansion.md b/_zh-cn/overviews/scala3-book/fun-eta-expansion.md new file mode 100644 index 0000000000..daff0b8d25 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-eta-expansion.md @@ -0,0 +1,83 @@ +--- +title: Eta 扩展 +type: section +description: This page discusses Eta Expansion, the Scala technology that automatically and transparently converts methods into functions. +num: 30 +previous-page: fun-function-variables +next-page: fun-hofs +--- + + +当你查看 Scala 集合类的 `map` 方法的 Scaladoc 时,你会看到它被定义为接受一个_函数_: + +```scala +def map[B](f: (A) => B): List[B] + ------------ +``` + +事实上,Scaladoc 明确指出,“`f` 是应用于每个元素的_函数_。” +但尽管如此,你可以通过某种方式将_方法_传递给 `map`,它仍然有效: + +```scala +def times10(i: Int) = i * 10 // a method +List(1, 2, 3).map(times10) // List(10,20,30) +``` + +你有没有想过这是如何工作的——如何将_方法_传递给需要_函数_的`map`? + +这背后的技术被称为_Eta Expansion_。 +它将 _方法类型_的表达式转换为 _函数类型_的等效表达式,并且它无缝而安静地完成了。 + +## 方法和函数的区别 + +{% comment %} +NOTE: I got the following “method” definition from this page (https://dotty.epfl.ch/docs/reference/changed-features/eta-expansion-spec.html), but I’m not sure it’s 100% accurate now that 方法 can exist outside of classes/traits/objects. +I’ve made a few changes to that description that I hope are more accurate and up to date. +{% endcomment %} + +从历史上看,_方法_一直是类定义的一部分,尽管在 Scala 3 中您现在可以拥有类之外的方法,例如 [Toplevel definitions][toplevel] 和 [extension 方法][extension]。 + +与方法不同,_函数_本身就是完整的对象,使它们成为第一等的实体。 + +它们的语法也不同。 +此示例说明如何定义执行相同任务的方法和函数,确定给定整数是否为偶数: + +```scala +def isEvenMethod(i: Int) = i % 2 == 0 // a method +val isEvenFunction = (i: Int) => i % 2 == 0 // a function +``` + +该函数确实是一个对象,因此您可以像使用任何其他变量一样使用它,例如将其放入列表中: + +```scala +val functions = List(isEvenFunction) +``` + +相反,从技术上讲,方法不是对象,因此在 Scala 2 中,您不能将方法放入 `List` 中,至少不能直接放入,如下例所示: + +```scala +// this example shows the Scala 2 error message +val methods = List(isEvenMethod) + ^ +error: missing argument list for method isEvenMethod +Unapplied methods are only converted to functions when a function type is expected. +You can make this conversion explicit by writing `isEvenMethod _` or `isEvenMethod(_)` instead of `isEvenMethod`. +``` + +如该错误消息所示,在 Scala 2 中可以手动将方法转换为函数,但 Scala 3 的重要部分是改进了 Eta 扩展技术,所以现在当您尝试将方法当作变量用,它是可以工作的---您不必自己处理手动转换: + +```scala +val functions = List(isEvenFunction) // works +val methods = List(isEvenMethod) // works +``` + +就这本入门书而言,需要了解的重要事项是: + +- Eta Expansion 是 Scala 技术,让您可以像使用函数一样使用方法 +- 该技术在 Scala 3 中得到了改进,几乎完全无缝 + +有关其工作原理的更多详细信息,请参阅参考文档中的 [Eta 扩展页面][eta_expansion]。 + +[eta_expansion]: {{ site.scala3ref }}/changed-features/eta-expansion.html +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[toplevel]: {% link _overviews/scala3-book/taste-toplevel-definitions.md %} diff --git a/_zh-cn/overviews/scala3-book/fun-function-variables.md b/_zh-cn/overviews/scala3-book/fun-function-variables.md new file mode 100644 index 0000000000..d92c0610db --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-function-variables.md @@ -0,0 +1,112 @@ +--- +title: 函数变量 +type: section +description: This page shows how to use anonymous functions in Scala, including examples with the List class 'map' and 'filter' functions. +num: 29 +previous-page: fun-anonymous-functions +next-page: fun-eta-expansion +--- + + +从上一节回到这个例子: + +```scala +val doubledInts = ints.map((i: Int) => i * 2) +``` + +我们注意到这部分表达式是一个匿名函数: + +```scala +(i: Int) => i * 2 +``` + +它被称为 *匿名* 的原因是它没有分配给变量,因此没有名称。 + +但是,可以将匿名函数(也称为*函数字面量*)分配给变量以创建*函数变量*: + +```scala +val double = (i: Int) => i * 2 +``` + +这将创建一个名为 `double` 的函数变量。 +在这个表达式中,原始函数字面量在 `=` 符号的右侧: + +```scala +val double = (i: Int) => i * 2 + ----------------- +``` + +新变量名在左侧: + +```scala +val double = (i: Int) => i * 2 + ------ +``` + +并且函数的参数列表在此处加下划线: + +```scala +val double = (i: Int) => i * 2 + -------- +``` + +就像方法的参数列表一样,这意味着 `double` 函数有一个参数,一个名为 `i` 的 `Int`。 +你可以在 REPL 中看到 `double` 的类型为 `Int => Int`,这意味着它接受一个 `Int` 参数并返回一个 `Int`: + +```scala +scala> val double = (i: Int) => i * 2 +val double: Int => Int = ... +``` + +### 调用函数 + +现在你可以像这样调用`double`函数: + +```scala +val x = double(2) // 4 +``` + +您还可以将 `double` 传递给 `map` 调用: + +```scala +List(1, 2, 3).map(double) // List(2, 4, 6) +``` + +此外,当您有 `Int => Int` 类型的其他函数时: + +```scala +val triple = (i: Int) => i * 3 +``` + +您可以将它们存储在 `List` 或 `Map` 中: + +```scala +val functionList = List(double, triple) + +val functionMap = Map( + "2x" -> double, + "3x" -> triple +) +``` + +如果将这些表达式粘贴到 REPL 中,您会看到它们具有以下类型: + +```` +// a List that contains functions of the type `Int => Int` +functionList: List[Int => Int] + +// a Map whose keys have the type `String`, and whose +// values have the type `Int => Int` +functionMap: Map[String, Int => Int] +```` + +## 关键点 + +这里的重要部分是: + +- 要创建函数变量,只需将变量名分配给函数字面量 +- 一旦你有了一个函数,你可以像对待任何其他变量一样对待它,即像一个`String`或`Int`变量 + +并且由于 Scala 3 中改进的 [Eta Expansion][eta_expansion] 函数式,您可以以相同的方式处理 *方法*。 + +[eta_expansion]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_zh-cn/overviews/scala3-book/fun-hofs.md b/_zh-cn/overviews/scala3-book/fun-hofs.md new file mode 100644 index 0000000000..5fd9bb9c72 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-hofs.md @@ -0,0 +1,288 @@ +--- +title: 高阶函数 +type: section +description: This page demonstrates how to create and use higher-order functions in Scala. +num: 31 +previous-page: fun-eta-expansion +next-page: fun-write-map-function +--- + + +高阶函数 (HOF) 通常定义为这类函数,它 (a) 将其他函数作为输入参数或 (b) 返回函数作为结果。 +在 Scala 中,HOF 是可能的,因为函数是一等值。 + +需要注意的是,虽然我们在本文档中使用了常见的行业术语“高阶函数”,但在 Scala 中,该短语同时适用于 *方法* 和 *函数*。 +得益于 Scala 的 [Eta 扩展技术][eta_expansion],它们通常可以在相同的地方使用。 + +## 从消费者到创造者 + +在本书到目前为止的示例中,您已经了解了如何成为方法的*消费者*,该方法将其他函数作为输入参数,例如使用诸如 `map` 和 `filter` 之类的 HOF。 +在接下来的几节中,您将了解如何成为 HOF 的*创造者*,包括: + +- 如何编写将函数作为输入参数的方法 +- 如何从方法中返回函数 + +在这个过程中你会看到: + +- 用于定义函数输入参数的语法 +- 引用函数后如何调用它 + +作为本次讨论的一个有益的副作用,一旦您对这种语法感到满意,您将使用它来定义函数参数、匿名函数和函数变量,并且也更容易阅读有关高阶函数的 Scaladoc。 + +## 理解过滤器的 Scaladoc + +要了解高阶函数的工作原理,深入研究一个示例会有所帮助。 +例如,您可以通过查看 Scaladoc 来了解 `filter` 接受的函数类型。 +这是 `List[A]` 类中的 `filter` 定义: + +```scala +def filter(p: (A) => Boolean): List[A] +``` + +这表明 `filter` 是一个接受名为 `p` 的函数参数的方法。 +按照惯例,`p` 代表 *谓词(predicate)*,它只是一个返回 `Boolean` 值的函数。 +所以 `filter` 将谓词 `p` 作为输入参数,并返回一个 `List[A]`,其中 `A` 是列表中保存的类型;如果你在 `List[Int]` 上调用 `filter`,`A` 是 `Int` 类型。 + +在这一点上,如果你不知道 `filter` 方法的用途,你只知道它的算法以某种方式使用谓词 `p` 创建并返回 `List[A]`。 + +具体看函数参数 `p`,这部分 `filter` 的描述: + +```scala +p: (A) => Boolean +``` + +意味着您传入的任何函数都必须将类型 `A` 作为输入参数并返回一个 `Boolean` 。 +因此,如果您的列表是 `List[Int]`,则可以将通用类型 `A` 替换为 `Int`,并像这样读取该签名: + +```scala +p: (Int) => Boolean +``` + +因为 `isEven` 具有这种类型——它将输入 `Int` 转换为结果 `Boolean`——它可以与 `filter` 一起使用。 + +{% comment %} +NOTE: (A low-priority issue): The next several sections can be condensed. +{% endcomment %} + +## 编写接受函数参数的方法 + +鉴于此背景,让我们开始编写将函数作为输入参数的方法。 + +**注意:**为了使下面的讨论更清楚,我们将您编写的代码称为*方法*,将您作为输入参数接受的代码称为*函数*。 + +### 第一个例子 + +要创建一个接受函数参数的方法,您所要做的就是: + +1. 在方法的参数列表中,定义要接受的函数的签名 +2. 在你的方法中使用那个函数 + +为了证明这一点,这里有一个方法,它接受一个名为 `f` 的输入参数,其中 `f` 是一个函数: + +```scala +def sayHello(f: () => Unit): Unit = f() +``` + +这部分代码---*类型签名*---声明 `f` 是一个函数,并定义了 `sayHello` 方法将接受的函数类型: + +```scala +f: () => Unit +``` + +这是它的工作原理: + +- `f` 是函数输入参数的名称。 + 这就像将 `String` 参数命名为 `s` 或 `Int` 参数命名为 `i`。 +- `f` 的类型签名指定此方法将接受的函数的 *类型*。 +- `f` 签名的 `()` 部分(在 `=>` 符号的左侧)表明 `f` 不接受输入参数。 +- 签名的 `Unit` 部分(在 `=>` 符号的右侧)表示 `f` 不应返回有意义的结果。 +- 回顾 `sayHello` 方法的主体(在 `=` 符号的右侧),那里的 `f()` 语句调用传入的函数。 + +现在我们已经定义了 `sayHello`,让我们创建一个函数来匹配 `f` 的签名,以便我们可以测试它。 +以下函数不接受任何输入参数并且不返回任何内容,因此它匹配 `f` 的类型签名: + +```scala +def helloJoe(): Unit = println("Hello, Joe") +``` + +因为类型签名匹配,你可以将 `helloJoe` 传递给 `sayHello`: + +```scala +sayHello(helloJoe) // prints "Hello, Joe" +``` + +如果您以前从未这样做过,那么恭喜您: +您刚刚定义了一个名为 `sayHello` 的方法,它接受一个函数作为输入参数,然后在其方法体中调用该函数。 + +### sayHello 可以带很多函数 + +重要的是要知道这种方法的美妙之处并不是说​​ `sayHello` 可以将 *一个* 函数作为输入参数;而在于它可以采用与 `f` 签名匹配的 *任意一个* 函数。 +例如,因为下一个函数没有输入参数并且不返回任何内容,所以它也适用于 `sayHello`: + +```scala +def bonjourJulien(): Unit = println("Bonjour, Julien") +``` + +它在 REPL 中: + +```` +scala> sayHello(bonjourJulien) +Bonjour, Julien +```` + +这是一个好的开始。 +现在唯一要做的就是查看更多示例,了解如何为函数参数定义不同的类型签名。 + +## 定义函数输入参数的通用语法 + +在这种方法中: + +```scala +def sayHello(f: () => Unit): Unit +``` + +我们注意到 `f` 的类型签名是: + +```scala +() => Unit +``` + +我们知道这意味着,“一个没有输入参数并且不返回任何有意义的东西的函数(由 `Unit` 给出)。” + +为了演示更多类型签名示例,这里有一个函数,它接受一个 `String` 参数并返回一个 `Int`: + +```scala +f: (String) => Int +``` + +什么样的函数接受一个字符串并返回一个整数? +“字符串长度”和校验和等函数就是两个例子。 + +同样,此函数接受两个 `Int` 参数并返回一个 `Int`: + +```scala +f: (Int, Int) => Int +``` + +你能想象什么样的函数匹配那个签名? + +答案是任何接受两个 `Int` 输入参数并返回 `Int` 的函数都与该签名匹配,因此所有这些“函数”(实际上是方法)都是匹配的: + +```scala +def add(a: Int, b: Int): Int = a + b +def subtract(a: Int, b: Int): Int = a - b +def multiply(a: Int, b: Int): Int = a * b +``` + +正如您可以从这些示例中推断出的,定义函数参数类型签名的一般语法是: + +```scala +variableName: (parameterTypes ...) => returnType +``` + +> 因为函数式编程就像创建和组合一系列代数方程,所以在设计函数和应用程序时通常会考虑*很多*类型。 +> 你可能会说你“在类型中思考”。 + +## 将函数参数与其他参数一起使用 + +为了使 HOF 真正有用,它们还需要一些数据来处理。 +对于像 `List` 这样的类,它的 `map` 方法已经有数据可以处理:`List` 中的数据。 +但是对于没有自己数据的独立 HOF,它也应该接受数据作为其他输入参数。 + +例如,这是一个名为 `executeNTimes` 的方法,它有两个输入参数:一个函数和一个 `Int`: + +```scala +def executeNTimes(f: () => Unit, n: Int): Unit = + for i <- 1 to n do f() +``` + +如代码所示,`executeNTimes` 执行了`f` 函数 `n` 次。 +因为像这样的简单 `for` 循环没有返回值,`executeNTimes` 返回 `Unit`。 + +要测试 `executeNTimes`,请定义一个匹配 `f` 签名的方法: + +```scala +// a method of type `() => Unit` +def helloWorld(): Unit = println("Hello, world") +``` + +然后将该方法与 `Int` 一起传递给`executeNTimes`: + +```` +scala> executeNTimes(helloWorld, 3) +Hello, world +Hello, world +Hello, world +```` + +优秀。 +`executeNTimes` 方法执行 `helloWorld` 函数 3 次。 + +### 需要多少参数 + +您的方法可以继续变得尽可能复杂。 +例如,此方法采用类型为 `(Int, Int) => Int` 的函数,以及两个输入参数: + +```scala +def executeAndPrint(f: (Int, Int) => Int, i: Int, j: Int): Unit = + println(f(i, j)) +``` + +因为这些 `sum` 和 `multiply` 方法与该类型签名匹配,所以它们可以与两个 `Int` 值一起传递到 `executeAndPrint` 中: + +```scala +def sum(x: Int, y: Int) = x + y +def multiply(x: Int, y: Int) = x * y + +executeAndPrint(sum, 3, 11) // prints 14 +executeAndPrint(multiply, 3, 9) // prints 27 +``` + +## 函数类型签名一致性 + +学习 Scala 的函数类型签名的一个好处是,用于定义函数输入参数的语法与用于编写函数字面量的语法相同。 + +例如,如果你要编写一个计算两个整数之和的函数,你可以这样写: + +```scala +val f: (Int, Int) => Int = (a, b) => a + b +``` + +该代码由类型签名组成: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ----------------- +```` + +输入参数: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ------ +```` + +和函数体: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ----- +```` + +这里展示了 Scala 的一致性,这里的函数类型: + +```` +val f: (Int, Int) => Int = (a, b) => a + b + ----------------- +```` + +与用于定义函数输入参数的类型签名相同: + +```` +def executeAndPrint(f: (Int, Int) => Int, ... + ----------------- +```` + +一旦你熟悉了这种语法,你就会用它来定义函数参数、匿名函数和函数变量,而且当你阅读 Scaladoc 中有关高阶函数的内容时,这些内容变得更容易了。 + +[eta_expansion]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_zh-cn/overviews/scala3-book/fun-intro.md b/_zh-cn/overviews/scala3-book/fun-intro.md new file mode 100644 index 0000000000..2935e0b0e1 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-intro.md @@ -0,0 +1,11 @@ +--- +title: 函数 +type: chapter +description: This chapter looks at all topics related to functions in Scala 3. +num: 27 +previous-page: methods-summary +next-page: fun-anonymous-functions +--- + +上一章介绍了 Scala *方法*,本章深入研究 *函数*。 +涵盖的主题包括匿名函数、函数变量和高阶函数 (HOF),包括如何创建自己的 HOF。 diff --git a/_zh-cn/overviews/scala3-book/fun-summary.md b/_zh-cn/overviews/scala3-book/fun-summary.md new file mode 100644 index 0000000000..017d9d1742 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-summary.md @@ -0,0 +1,34 @@ +--- +title: 总结 +type: section +description: This page shows how to use anonymous functions in Scala, including examples with the List class 'map' and 'filter' functions. +num: 34 +previous-page: fun-write-method-returns-function +next-page: packaging-imports +--- + + +这是一个很长的章节,所以让我们回顾一下所涵盖的关键点。 + +我们通常这样定义高阶函数 (HOF),它以其他函数作为输入参数或将函数作为其值。 +在 Scala 中这是可能的,因为函数是一等值。 + +浏览这些部分,首先您会看到: + +- 您可以将匿名函数编写为小代码片段 +- 您可以将它们传递给集合类上的几十个 HOF(方法),即像 `filter`、`map` 等方法。 +- 使用这些小代码片段和强大的 HOF,您只需少量代码即可创建大量的函数 + +在查看了匿名函数和 HOF 之后,您看到了: + +- 函数变量只是绑定到变量的匿名函数 + +在了解如何成为 HOF 的*消费者*之后,您将了解如何成为 HOF 的*创造者*。 +具体来说,您看到了: + +- 如何编写将函数作为输入参数的方法 +- 如何从方法中返回函数 + +本章的一个有益的副作用是您看到了许多关于如何为函数声明类型签名的示例。 +这样做的好处是,您可以使用相同的语法来定义函数参数、匿名函数和函数变量,而且对于 `map`、`filter` 等高阶函数,阅读 Scaladoc 也变得更容易。 + diff --git a/_zh-cn/overviews/scala3-book/fun-write-map-function.md b/_zh-cn/overviews/scala3-book/fun-write-map-function.md new file mode 100644 index 0000000000..5183a1dd0b --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-write-map-function.md @@ -0,0 +1,86 @@ +--- +title: 写你自己的 map 方法 +type: section +description: This page demonstrates how to create and use higher-order functions in Scala. +num: 32 +previous-page: fun-hofs +next-page: fun-write-method-returns-function +--- + + +现在您已经了解了如何编写自己的高阶函数,让我们快速浏览一个更真实的示例。 + +想象一下,`List` 类没有自己的 `map` 方法,而您想编写自己的方法。 +创建函数的第一步是准确地陈述问题。 +只关注 `List[Int]`,你说: + +> 我想编写一个 `map` 方法,该方法可用于将函数应用于给定的 `List[Int]` 中的每个元素, +> 并将转换后的元素作为新列表返回。 + +鉴于该声明,您开始编写方法签名。 +首先,您知道您想接受一个函数作为参数,并且该函数应该将 `Int` 转换为某种通用类型 `A`,因此您编写: + +```scala +def map(f: (Int) => A) +``` + +使用泛型类型的语法要求在参数列表之前声明该类型符号,因此您添加: + +```scala +def map[A](f: (Int) => A) +``` + +接下来,您知道 `map` 也应该接受 `List[Int]`: + +```scala +def map[A](f: (Int) => A, xs: List[Int]) +``` + +最后,您还知道 `map` 返回一个转换后的 `List`,其中包含泛型类型 `A` 的元素: + +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = ??? +``` + +这负责方法签名。 +现在您所要做的就是编写方法体。 +`map` 方法将它赋予的函数应用于它赋予的列表中的每个元素,以生成一个新的、转换的列表。 +一种方法是使用 `for` 表达式: + +```scala +for x <- xs yield f(x) +``` + +`for` 表达式通常使代码出奇地简单,对于我们的目的,它最终成为整个方法体。 + +把它和方法签名放在一起,你现在有了一个独立的 `map` 方法,它与 `List[Int]` 一起工作: + +```scala +def map[A](f: (Int) => A, xs: List[Int]): List[A] = + for x <- xs yield f(x) +``` + +### 使其通型化 + +作为奖励,请注意 `for` 表达式不做任何取决于 `List` 中的类型为 `Int` 的事情。 +因此,您可以将类型签名中的 `Int` 替换为泛型类型参数 `B`: + +```scala +def map[A, B](f: (B) => A, xs: List[B]): List[A] = + for x <- xs yield f(x) +``` + +现在你有了一个适用于任何 `List` 的 `map` 方法。 + +这些示例表明 `map` 可以按需要工作: + +```scala +def double(i : Int) = i * 2 +map(double, List(1, 2, 3)) // List(2, 4, 6) + +def strlen(s: String) = s.length +map(strlen, List("a", "bb", "ccc")) // List(1, 2, 3) +``` + +现在您已经了解了如何编写接受函数作为输入参数的方法,让我们看看返回函数的方法。 + diff --git a/_zh-cn/overviews/scala3-book/fun-write-method-returns-function.md b/_zh-cn/overviews/scala3-book/fun-write-method-returns-function.md new file mode 100644 index 0000000000..fc7dbf7a4f --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fun-write-method-returns-function.md @@ -0,0 +1,162 @@ +--- +title: 创建可以返回函数的方法 +type: section +description: This page demonstrates how to create and use higher-order functions in Scala. +num: 33 +previous-page: fun-write-map-function +next-page: fun-summary +--- + + +由于 Scala 的一致性,编写一个返回函数的方法与您在前几节中看到的所有内容相似。 +例如,假设您想编写一个返回函数的 `greet` 方法。 +我们再次从问题陈述开始: + +> 我想创建一个返回函数的 `greet` 方法。 +> 该函数将接受一个字符串参数并使用 `println` 打印它。 +> 为了简化第一个示例,`greet` 不会接受任何输入参数;它只会构建一个函数并返回它。 + +鉴于该声明,您可以开始构建 `greet`。 +你知道这将是一种方法: + +```scala +def greet() +``` + +您还知道此方法将返回一个函数,该函数 (a) 采用 `String` 参数,并且 (b) 使用 `println` 打印该字符串。 +因此,该函数的类型为 `String => Unit`: + +```scala +def greet(): String => Unit = ??? + ---------------- +``` + +现在你只需要一个方法体。 +您知道该方法需要返回一个函数,并且该函数接受一个“字符串”并打印它。 +此匿名函数与该描述匹配: + +```scala +(name: String) => println(s"Hello, $name") +``` + +现在您只需从方法中返回该函数: + +```scala +// a method that returns a function +def greet(): String => Unit = + (name: String) => println(s"Hello, $name") +``` + +因为这个方法返回一个函数,所以你可以通过调用`greet()`来得到这个函数。 +这是在 REPL 中做的一个很好的步骤,因为它验证了新函数的类型: + +```` +scala> val greetFunction = greet() +val greetFunction: String => Unit = Lambda.... + ----------------------------------------- +```` + +现在你可以调用`greetFunction`了: + +```scala +greetFunction("Joe") // prints "Hello, Joe" +``` + +恭喜,您刚刚创建了一个返回函数的方法,然后执行了该函数。 + +## 改进方法 + +如果您可以传递问候语,我们的方法会更有用,所以让我们这样做。 +您所要做的就是将问候语作为参数传递给 `greet` 方法,并在 `println` 中的字符串中使用它: + +```scala +def greet(theGreeting: String): String => Unit = + (name: String) => println(s"$theGreeting, $name") +``` + +现在,当您调用您的方法时,该过程更加灵活,因为您可以更改问候语。 +当您从此方法创建函数时,它是这样的: + +```` +scala> val sayHello = greet("Hello") +val sayHello: String => Unit = Lambda..... + ---------------------- +```` + +REPL 类型签名输出显示 `sayHello` 是一个接受 `String` 输入参数并返回 `Unit`(无)的函数。 +所以现在当你给 `sayHello` 一个 `String` 时,它会打印问候语: + +```scala +sayHello("Joe") // prints "Hello, Joe" +``` + +您还可以根据需要更改问候语以创建新函数: + +```scala +val sayCiao = greet("Ciao") +val sayHola = greet("Hola") + +sayCiao("Isabella") // prints "Ciao, Isabella" +sayHola("Carlos") // prints "Hola, Carlos" +``` + +## 一个更真实的例子 + +当您的方法返回许多可能的函数之一时,这种技术会更加有用,例如返回自定义构建函数的工厂。 + +例如,假设您想编写一个方法,该方法返回用不同语言问候人们的函数。 +我们将其限制为使用英语或法语问候的函数,具体取决于传递给方法的参数。 + +您知道的第一件事是,您想要创建一个方法,该方法 (a) 将“所需语言”作为输入,并且 (b) 返回一个函数作为其结果。 +此外,由于该函数会打印给定的字符串,因此您知道它的类型为 `String => Unit`。 +使用该信息编写方法签名: + +```scala +def createGreetingFunction(desiredLanguage: String): String => Unit = ??? +``` + +接下来,因为您知道可能返回的函数接受一个字符串并打印它,所以您可以为英语和法语编写两个匿名函数: + +```scala +(name: String) => println(s"你好,$name") +(name: String) => println(s"Bonjour, $name") +``` + +在方法内部,如果你给这些匿名函数起一些名字,它可能会更易读,所以让我们将它们分配给两个变量: + +```scala +val englishGreeting = (name: String) => println(s"Hello, $name") +val frenchGreeting = (name: String) => println(s"Bonjour, $name") +``` + +现在您需要做的就是 (a) 如果 `desiredLanguage` 是英语,则返回 `englishGreeting`,并且 (b) 如果 `desiredLanguage` 是法语,则返回 `frenchGreeting`。 +一种方法是使用 `match` 表达式: + +```scala +def createGreetingFunction(desiredLanguage: String): String => Unit = + val englishGreeting = (name: String) => println(s"Hello, $name") + val frenchGreeting = (name: String) => println(s"Bonjour, $name") + desiredLanguage match + case "english" => englishGreeting + case "french" => frenchGreeting +``` + +这是最后的方法。 +请注意,从方法返回函数值与返回字符串或整数没有什么不同呃值。 + +这就是 `createGreetingFunction` 构建法语问候函数的方式: + +```scala +val greetInFrench = createGreetingFunction("french") +greetInFrench("Jonathan") // prints "Bonjour, Jonathan" +``` + +这就是它构建英语问候功能的方式: + +```scala +val greetInEnglish = createGreetingFunction("english") +greetInEnglish("Joe") // prints "Hello, Joe" +``` + +如果你对这段代码感到满意——恭喜——你现在知道如何编写返回函数的方法了。 + From b48eb7c2be35437bd17ef1021574f366f27d69d2 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sat, 28 May 2022 13:13:15 +0800 Subject: [PATCH 17/53] add _zh-cn/overviews/scala3-book/packaging-imports.md --- .../scala3-book/packaging-imports.md | 378 ++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/packaging-imports.md diff --git a/_zh-cn/overviews/scala3-book/packaging-imports.md b/_zh-cn/overviews/scala3-book/packaging-imports.md new file mode 100644 index 0000000000..a4adc04ba8 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/packaging-imports.md @@ -0,0 +1,378 @@ +--- +title: 打包和导入 +type: chapter +description: A discussion of using packages and imports to organize your code, build related modules of code, control scope, and help prevent namespace collisions. +num: 35 +previous-page: fun-summary +next-page: collections-intro +--- + + +Scala 使用 *包* 创建命名空间,让您可以模块化程序并帮助防止命名空间冲突。 +Scala 支持 Java 使用的包命名样式,也支持 C++ 和 C# 等语言使用的“花括号”命名空间表示法。 + +Scala 导入成员的方法也类似于 Java,并且更灵活。 +使用 Scala,您可以: + +- 导入包、类、对象、traits 和方法 +- 将导入语句放在任何地方 +- 导入成员时隐藏和重命名成员 + +这些特性在以下示例中进行了演示。 + +## 创建一个包 + +通过在 Scala 文件的顶部声明一个或多个包名称来创建包。 +例如,当您的域名是 _acme.com_ 并且您正在使用名为 _myapp_ 的应用程序中的 _model_ 包中工作时,您的包声明如下所示: + +```scala +package com.acme.myapp.model + +class Person ... +``` + +按照约定,包名应全部小写,正式命名约定为 *\.\.\.\*。 + +虽然不是必需的,但包名称通常遵循目录结构名称,因此如果您遵循此约定,则此项目中的 `Person` 类将在 *MyApp/src/main/scala/com/acme/myapp/model/Person.scala* 文件中找到。 + +### 在同一个文件中使用多个包 + +上面显示的语法适用于整个源文件:文件中的所有定义 +`Person.scala` 属于 `com.acme.myapp.model` 包,根据包子句 +在文件的开头。 + +或者,可以编写仅适用于定义的包子句 +他们包含: + +```scala +package users: + + package administrators: // the full name of this package is users.administrators + class AdminUser // the full name of this class is users.administrators.AdminUser + + package normalusers: // the full name of this package is users.normalusers + class NormalUser // the full name of this class is users.normalusers.NormalUser +``` + +请注意,包名称后跟一个冒号,并且其中的定义 +一个包是缩进的。 + +这种方法的优点是它允许包嵌套,并提供更明显的范围和封装控制,尤其是在同一个文件中。 + +## 导入语句,第 1 部分 + +导入语句用于访问其他包中的实体。 +导入语句分为两大类: + +- 导入类、trait、对象、函数和方法 +- 导入 `given` 子句 + +如果您习惯于 Java 之类的语言,则第一类 import 语句与 Java 使用的类似,只是语法略有不同,因此具有更大的灵活性。 +这些示例展示了其中的一些灵活性: + +```` +import users.* // import everything from the `users` package +import users.User // import only the `User` class +import users.{User, UserPreferences} // import only two selected members +import users.{UserPreferences as UPrefs} // rename a member as you import it +```` + +这些示例旨在让您了解第一类 `import` 语句的工作原理。 +在接下来的小节中对它们进行了更多解释。 + +导入语句还用于将 `given` 实例导入本范围。 +这些将在本章末尾讨论。 + +继续之前的注意事项: + +> 访问同一包的成员不需要导入子句。 + +### 导入一个或多个成员 + +在 Scala 中,您可以从包中导入一个成员,如下所示: + +```scala +import scala.concurrent.Future +``` + +和这样的多个成员: + +```scala +import scala.concurrent.Future +import scala.concurrent.Promise +import scala.concurrent.blocking +``` + +导入多个成员时,您可以像这样更简洁地导入它们: + +```scala +import scala.concurrent.{Future, Promise, blocking} +``` + +当您想从 *scala.concurrent* 包中导入所有内容时,请使用以下语法: + +```scala +import scala.concurrent.* +``` + +### 在导入时重命名成员 + +有时,在导入实体时重命名实体会有所帮助,以避免名称冲突。 +例如,如果您想同时使用 Scala `List` 类和 *java.util.List* 类,可以在导入时重命名 *java.util.List* 类: + +```scala +import java.util.{List as JavaList} +``` + +现在您使用名称 `JavaList` 来引用该类,并使用 `List` 来引用 Scala 列表类。 + +您还可以使用以下语法一次重命名多个成员: + +```scala +import java.util.{Date as JDate, HashMap as JHashMap, *} +``` + +那行代码说,“重命名 `Date` 和 `HashMap` 类,如图所示,并导入 _java.util_ 包中的所有其他内容,而不重命名任何其他成员。” + +### 在导入时隐藏成员 + +您还可以在导入过程中*隐藏*成员。 +这个 `import` 语句隐藏了 *java.util.Random* 类,同时导入 *java.util 中的所有其他内容* 包裹: + +```scala +import java.util.{Random as _, *} +``` + +如果您尝试访问 `Random` 类,它将无法正常工作,但您可以访问该包中的所有其他成员: + +```scala +val r = new Random // won’t compile +new ArrayList // works +``` + +#### 隐藏多个成员 + +要在导入过程中隐藏多个成员,请在使用最终通配符导入之前列出它们: + +```scala +scala> import java.util.{List as _, Map as _, Set as _, *} +``` + +这些类再次被隐藏,但您可以使用 *java.util* 中的所有其他类: + +```scala +scala> new ArrayList[String] +val res0: java.util.ArrayList[String] = [] +``` + +因为这些 Java 类是隐藏的,所以您也可以使用 Scala 的 `List`、`Set` 和 `Map` 类而不会发生命名冲突: + +```scala +scala> val a = List(1, 2, 3) +val a: List[Int] = List(1, 2, 3) + +scala> val b = Set(1, 2, 3) +val b: Set[Int] = Set(1, 2, 3) + +scala> val c = Map(1 -> 1, 2 -> 2) +val c: Map[Int, Int] = Map(1 -> 1, 2 -> 2) +``` + +### 在任何地方使用导入 + +在 Scala 中,`import` 语句可以在任何地方。 +它们可以在源代码文件的顶部使用: + +```scala +package foo + +import scala.util.Random + +class ClassA: + def printRandom: + val r = new Random // use the imported class + // more code here... +``` + +如果您愿意,您还可以使用更接近需要它们的点的 `import` 语句: + +```scala +package foo + +class ClassA: + import scala.util.Random // inside ClassA + def printRandom { + val r = new Random + // more code here... + +class ClassB: + // the Random class is not visible here + val r = new Random // this code will not compile +``` + +### “静态”导入 + +当您想以类似于 Java “静态导入”方法的方式导入成员时——因此您可以直接引用成员名称,而不必在它们前面加上类名——使用以下方法。 + +使用此语法导入 Java `Math` 类的所有静态成员: + +```scala +import java.lang.Math.* +``` + +现在您可以访问静态的 `Math` 类方法,例如 `sin` 和 `cos`,而不必在它们前面加上类名: + +```scala +import java.lang.Math.* + +val a = sin(0) // 0.0 +val b = cos(PI) // -1.0 +``` + +### 默认导入的包 + +两个包被隐式导入到所有源代码文件的范围内: + +- java.lang.* +- scala.* + +Scala 对象 `Predef` 的成员也是默认导入的。 + +> 如果您想知道为什么可以使用 `List`、`Vector`、`Map` 等类,而无需导入它们,它们是可用的,因为 `Predef` 对象中的定义。 + +### 处理命名冲突 + +在极少数情况下会出现命名冲突,您需要从项目的根目录导入一些东西,在包名前加上 `_root_`: + +``` +package accounts + +import _root_.accounts.* +``` + +## 导入 `given` 实例 + +正如您将在 [上下文抽象][contextual] 一章中看到的,`import` 语句的一种特殊形式用于导入 `given` 实例。 +基本形式如本例所示: + +```scala +object A: + class TC + given tc as TC + def f(using TC) = ??? + +object B: + import A.* // import all non-given members + import A.given // import the given instance +``` + +在此代码中,对象 `B` 的 `import A.*` 子句导入了 `A` 的所有成员 *除了* `given` 实例 `tc`。 +相反,第二个导入,`import A.given`,*仅*导入那个 `given` 实例。 +两个 `import` 子句也可以合并为一个: + +```scala +object B: + import A.{given, *} +``` + +### 讨论 + +通配符选择器 `*` 将除给定或扩展之外的所有定义带入范围,而 `given` 选择器将所有*给定*——包括那些由扩展产生的定义——带入范围。 + +这些规则有两个主要好处: + +- 范围内的给定来自哪里更清楚。 + 特别是,不可能在一长串其他通配符导入中隐藏导入的给定。 +- 它可以在不导入任何其他内容的情况下导入所有给定。 + 这一点特别重要,因为给定可以是匿名的,所以通常使用命名导入是不切实际的。 + +### 按类型导入 + +由于给定可以是匿名的,因此按名称导入它们并不总是可行的,通常使用通配符导入。 +*按类型导入* 为通配符导入提供了更具体的替代方案,这使得导入的内容更加清晰: + +```scala +import A.{given TC} +``` + +这会在 `A` 中导入任何具有符合 `TC` 的类型的 `given`。 +导入多种类型的给定 `T1,...,Tn` 由多个 `given` 选择器表示: + +```scala +import A.{given T1, ..., given Tn} +``` + +导入参数化类型的所有 `given` 实例由通配符参数表示。 +例如,当你有这个 `object` 时: + +```scala +object Instances: + given intOrd as Ordering[Int] + given listOrd[T: Ordering] as Ordering[List[T]] + given ec as ExecutionContext = ... + given im as Monoid[Int] +``` + +此导入语句导入 `intOrd`、`listOrd` 和 `ec` 实例,但省略了 `im` 实例,因为它不符合任何指定的边界: + +```scala +import Instances.{given Ordering[?], given ExecutionContext} +``` + +按类型导入可以与按名称导入混合。 +如果两者都存在于导入子句中,则按类型导入排在最后。 +例如,这个 import 子句导入了 `im`、`intOrd` 和 `listOrd`,但省略了 `ec`: + +```scala +import Instances.{im, given Ordering[?]} +``` + +### 一个例子 + +作为一个具体的例子,假设你有这个 `MonthConversions` 对象,它包含两个 `given` 定义: + +```scala +object MonthConversions: + trait MonthConverter[A]: + def convert(a: A): String + + given intMonthConverter: MonthConverter[Int] with + def convert(i: Int): String = + i match + case 1 => "January" + case 2 => "February" + // more cases here ... + + given stringMonthConverter: MonthConverter[String] with + def convert(s: String): String = + s match + case "jan" => "January" + case "feb" => "February" + // more cases here ... +``` + +要将这些给定导入当前范围,请使用以下两个 `import` 语句: + +```scala +import MonthConversions.* +import MonthConversions.{given MonthConverter[?]} +``` + +现在您可以创建一个使用这些 `given` 实例的方法: + +```scala +def genericMonthConverter[A](a: A)(using monthConverter: MonthConverter[A]): String = + monthConverter.convert(a) +``` + +然后您可以在您的应用程序中使用该方法: + +```scala +@main def main = + println(genericMonthConverter(1)) // January + println(genericMonthConverter("jan")) // January +``` + +如前所述, `import given` 语法的主要设计优势之一是明确范围内的给定来自何处,并且在这些 `import` 语句中,很清楚地表明给定是来自 `MonthConversions` 对象。 + +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} From c11754c24be7b6f29a0fd465035e04f096f105c3 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sat, 28 May 2022 16:56:04 +0800 Subject: [PATCH 18/53] add all collections --- .../scala3-book/collections-classes.md | 609 ++++++++++++++++++ .../scala3-book/collections-intro.md | 24 + .../scala3-book/collections-methods.md | 401 ++++++++++++ .../scala3-book/collections-summary.md | 31 + 4 files changed, 1065 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/collections-classes.md create mode 100644 _zh-cn/overviews/scala3-book/collections-intro.md create mode 100644 _zh-cn/overviews/scala3-book/collections-methods.md create mode 100644 _zh-cn/overviews/scala3-book/collections-summary.md diff --git a/_zh-cn/overviews/scala3-book/collections-classes.md b/_zh-cn/overviews/scala3-book/collections-classes.md new file mode 100644 index 0000000000..7617c934a3 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/collections-classes.md @@ -0,0 +1,609 @@ +--- +title: 集合类型 +type: section +description: This page introduces the common Scala 3 collections types and some of their methods. +num: 37 +previous-page: collections-intro +next-page: collections-methods +--- + + +{% comment %} +TODO: mention Array, ArrayDeque, ListBuffer, Queue, Stack, StringBuilder? +LATER: note that methods like `+`, `++`, etc., are aliases for other methods +LATER: add links to the Scaladoc for the major types shown here +{% endcomment %} + +本页演示了常见的 Scala 3 集合及其附带的方法。 +Scala 提供了丰富的集合类型,但您可以从其中的几个开始,然后根据需要使用其他的。 +同样,每种集合类型都有数十种方法可以让您的生活更轻松,但您可以从其中的少数几个开始使用,就可以有很多收获。 + +因此,本节介绍并演示了在开始时,需要使用的最常见的类型和方法。 +当您需要更大的灵活性时,请参阅本节末尾的这些页面以获取更多细节。 + +## 三大类集合 + +从高层次看 Scala 集合,有三个主要类别可供选择: + +- **序列**是元素的顺序集合,可以是_有索引的_(如数组)或_线性的_(如链表) +- **影射** 包含键/值对的集合,例如 Java `Map`、Python 字典或 Ruby `Hash` +- **集合** 是无重复元素的无序集合 + +所有这些都是基本类型,并且具有用于特定目的的子类型,例如并发、缓存和流式传输。 +除了这三个主要类别之外,还有其他有用的集合类型,包括范围、堆栈和队列。 + +### 集合层次结构 + +作为简要概述,接下来的三个图显示了 Scala 集合中类和 trait 的层次结构。 + +第一张图显示了_scala.collection_包中的集合类型。 +这些都是高级抽象类或 traits,它们 +通常有_不可变_和_可变_的实现。 + +![一般集合层次结构][collections1] + +此图显示包 _scala.collection.immutable_ 中的所有集合: + +![不可变集合层次结构][collections2] + +此图显示包 _scala.collection.mutable_ 中的所有集合: + +![可变集合层次结构][collections3] + +在查看了所有集合类型的详细视图后,以下部分将介绍一些经常使用的常见类型。 + +{% comment %} +NOTE: those images come from this page: https://docs.scala-lang.org/overviews/collections-2.13/overview.html +{% endcomment %} + +## 常用集合 + +您经常使用的主要集合是: + +| 集合类型 | 不可变 | 可变 | 说明 | +| ------------- | --------- | -------- | ------------ | +| `List` | ✓ | | 线性(链表)、不可变序列 | +| `Vector` | ✓ | | 一个索引的、不可变的序列 | +| `LazyList` | ✓ | | 一个惰性不可变链表,它的元素仅在需要时才计算;适用于大型或无限序列。 | +| `ArrayBuffer` | | ✓ | 可变索引序列的首选类型 | +| `ListBuffer` | | ✓ | 当你想要一个可变的 `List` 时使用;通常转换为“列表” | +| `Map` | ✓ | ✓ | 由键和值对组成的可迭代集合。 | +| `Set` | ✓ | ✓ | 没有重复元素的可迭代集合 | + +如图所示,`Map` 和 `Set` 有不可变和可变版本。 + +以下部分演示了每种类型的基础知识。 + +> 在 Scala 中,_缓冲_——例如 `ArrayBuffer` 和 `ListBuffer`——是一个可以增长和缩小的序列。 + +### 关于不可变集合的说明 + +在接下来的部分中,无论何时使用_不可变_这个词,都可以安全地假设该类型旨在用于_函数性编程_(FP) 风格。 +使用这些类型,您无需修改​​集合;您将功能方法应用于该集合以创建新的结果。 + +## 选择序列 + +选择_序列_---一个顺序集合元素时---您有两个主要决定: + +- 是否应该对序列进行索引(如数组),允许快速访问任何元素,还是应该将其实现为线性链表? +- 你想要一个可变的还是不可变的集合? + +此处显示了推荐的通用顺序集合,用于可变/不可变和索引/线性组合: + +| 类型/类别 | 不可变 | 可变 | +| --------------------- | --------- | ------------ | +|索引 | `Vector` |`ArrayBuffer` | +|线性(链表) | `List` |`ListBuffer` | + +例如,如果您需要一个不可变的索引集合,通常您应该使用 `Vector`。 +相反,如果您需要一个可变的索引集合,请使用 `ArrayBuffer`。 + +> `List` 和 `Vector` 在以函数式风格编写代码时经常使用。 +> `ArrayBuffer` 通常在以命令式风格编写代码时使用。 +> `ListBuffer` 用于混合样式时,例如构建列表。 + +接下来的几节简要介绍了 `List`、`Vector` 和 `ArrayBuffer` 类型。 + +## `List` + +[列表类型](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) 是一个线性的、不可变的序列。 +这只是意味着它是一个您无法修改的链表。 +任何时候你想添加或删除 `List` 元素,你都可以从现有的 `List` 中创建一个新的 `List`。 + +### 创建列表 + +这是创建初始“列表”的方式: + +```scala +val ints = List(1, 2, 3) +val names = List("Joel", "Chris", "Ed") + +// another way to construct a List +val namesAgain = "Joel" :: "Chris" :: "Ed" :: Nil +``` + +如果您愿意,也可以声明 `List` 的类型,但通常不是必需的: + +```scala +val ints: List[Int] = List(1, 2, 3) +val names: List[String] = List("Joel", "Chris", "Ed") +``` + +一个例外是集合中有混合类型时。在这种情况下,您可能需要明确指定其类型: + +```scala +val things: List[Any] = List(1, "two", 3.0) +``` + +### 将元素添加到列表 + +因为 `List` 是不可变的,所以你不能向它添加新元素。 +相反,您可以通过将元素添加到现有 `List` 来创建新列表。 +例如,给定这个 `List`: + +```scala +val a = List(1, 2, 3) +``` + +使用 `List` 时,用 `::` 来_附加_一个元素,用 `:::` 把另一个 `List` 插在这个 `List` 之前,如下所示: + +```scala +val b = 0 :: a // List(0, 1, 2, 3) +val c = List(-1, 0) ::: a // List(-1, 0, 1, 2, 3) +``` + +你也可以在 `List` 中添加元素,但是因为 `List` 是一个单链表,你通常应该只在它前面添加元素; +在它的后面添加元素是一个相对较慢的操作,尤其是在处理大型序列时。 + +> 提示:如果您想将元素添加到不可变序列的前面或者后面时,请改用 `Vector`。 + +因为 `List` 是一个链表,你不应该尝试通过索引值来访问大列表的元素。 +例如,如果您有一个包含一百万个元素的 `List` ,则访问像 `myList(999_999)` 这样的元素将花费相对较长的时间,因为该请求必须遍历所有这些元素。 +如果您有一个大型集合并希望通过索引访问元素,请改用 `Vector` 或 `ArrayBuffer`。 + +### 如何记住方法名 + +现在 IDE 为我们提供了极大的帮助,但是记住这些方法名称的一种方法是,认为 `:` 字符代表序列所在的一侧,因此当您使用 `+:` 时,您知道列表需要在右边,像这样: + +```scala +0 +: a +``` + +同样,当您使用 `:+` 时,您知道列表需要在左侧: + +```scala +a :+ 4 +``` + +有更多的技术方法可以考虑这一点,但这可能是记住方法名称的有用方法。 + +{% comment %} +LATER: Add a discussion of `:` on method names, right-associativity, and infix operators. +{% endcomment %} + +此外,这些符号方法名称的一个好处是它们是一致的。 +相同的方法名称用于其他不可变序列,例如 `Seq` 和 `Vector`。 +如果您愿意,还可以使用非符号方法名称来附加元素和在头部插入元素。 + +### 如何遍历列表 + +给定一个名称 `List`: + +```scala +val names = List("Joel", "Chris", "Ed") +``` + +您可以像这样打印每个字符串: + +```scala +for name <- names do println(name) +``` + +这是它在 REPL 中的样子: + +```scala +scala> for name <-names do println(name) +Joel +Chris +Ed +``` + +将 `for` 循环与集合一起使用的一个好处是 Scala 是一致的,并且相同的方法适用于所有序列,包括 `Array`、`ArrayBuffer`、`List`、`Seq`、`Vector`、`Map` ,`Set` 等。 + +### 一点历史 + +对于那些对历史感兴趣的人,Scala `List` 类似于 [Lisp 编程语言](https://en.wikipedia.org/wiki/Lisp_(programming_language)) 中的 `List`,它是最初于 1958 年确定的。 +实际上,除了像这样创建一个 `List` 之外: + +```scala +val ints = List(1, 2, 3) +``` + +您也可以通过这种方式创建完全相同的列表: + +```scala +val list = 1 :: 2 :: 3 :: Nil +``` + +REPL 展示了它是如何工作的: + +```scala +scala> val list = 1 :: 2 :: 3 :: Nil +list: List[Int] = List(1, 2, 3) +``` + +这是因为 `List` 是一个以 `Nil` 元素结尾的单链表,而 `::` 是一个 `List` 方法,其工作方式类似于 Lisp 的“cons”运算符。 + +### 旁白:LazyList + +Scala 集合还包括一个 [LazyList](https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html),它是一个 _惰性_不可变链表。 +它被称为“惰性”——或非严格——因为它仅在需要时计算其元素。 + +你可以看到 REPL 中的 `LazyList` 有多懒惰: + +```scala +val x = LazyList.range(1, Int.MaxValue) +x.take(1) // LazyList() +x.take(5) // LazyList() +x.map(_ + 1) // LazyList() +``` + +在所有这些例子中,什么都没有发生。 +事实上,除非你强迫它发生,否则什么都不会发生,例如通过调用它的 `foreach` 方法: + +```` +scala> x.take(1).foreach(println) +1 +```` + +有关严格和非严格的用途、好处和缺点的更多信息严格(惰性)集合,请参阅 [The Architecture of Scala 2.13's Collections][strict] 页面上的“严格”和“非严格”讨论。 + + + +## 向量 + +[向量](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html) 是一个索引的、不可变的序列。 +描述的“索引”部分意味着它提供了在有效恒定时间内随机访问和更新向量,因此您可以通过索引值快速访问 `Vector` 元素,例如访问 `listOfPeople(123_456_789)` 。 + +一般来说,除了 (a) `Vector` 有索引而 `List` 没有索引,以及 (b) `List` 有 `::` 方法这两个不同外,这两种类型的工作方式相同,所以我们将快速过一下示例。 + +以下是创建“向量”的几种方法: + +```scala +val nums = Vector(1, 2, 3, 4, 5) + +val strings = Vector("one", "two") + +case class Person(name: String) +val people = Vector( + Person("Bert"), + Person("Ernie"), + Person("Grover") +) +``` + +因为 `Vector` 是不可变的,所以你不能向它添加新元素。 +相反,您通过将元素附加或插入头部到现有的 `Vector`,从而创建新序列。 +这些示例展示了如何将元素_附加_到 `Vector`: + +```scala +val a = Vector(1,2,3) // Vector(1, 2, 3) +val b = a :+ 4 // Vector(1, 2, 3, 4) +val c = a ++ Vector(4, 5) // Vector(1, 2, 3, 4, 5) +``` + +这就是你_插入头部_元素的方式: + +```scala +val a = Vector(1,2,3) // Vector(1, 2, 3) +val b = 0 +: a // Vector(0, 1, 2, 3) +val c = Vector(-1, 0) ++: a // Vector(-1, 0, 1, 2, 3) +``` + +除了快速的随机访问和更新之外,`Vector` 还提供了快速的追加和前置时间,因此您可以根据需要使用这些功能。 + +> 请参阅 [集合性能特性](https://docs.scala-lang.org/overviews/collections-2.13/performance-characteristics.html) 了解有关 `Vector` 和其他集合的性能详细信息。 + +最后,您可以在 `for` 循环中使用 `Vector`,就像 `List`、`ArrayBuffer` 或任何其他序列一样: + +```scala +scala> val names = Vector("Joel", "Chris", "Ed") +val names: Vector[String] = Vector(Joel, Chris, Ed) + +scala> for name <- names do println(name) +Joel +Chris +Ed +``` + +## 数组缓冲区 + +当您在 Scala 应用程序中需要一个通用的、可变的索引序列时,请使用 `ArrayBuffer`。 +它是可变的,所以你可以改变它的元素,也可以调整它的大小。 +因为它是索引的,所以元素的随机访问很快。 + +### 创建一个数组缓冲区 + +要使用 `ArrayBuffer`,首先导入它: + +```scala +import scala.collection.mutable.ArrayBuffer +``` + +如果您需要从一个空的 `ArrayBuffer` 开始,只需指定其类型: + +```scala +var strings = ArrayBuffer[String]() +var ints = ArrayBuffer[Int]() +var people = ArrayBuffer[Person]() +``` + +如果您知道 `ArrayBuffer` 最终需要的大致大小,则可以使用初始大小创建它: + +```scala +// ready to hold 100,000 ints +val buf = new ArrayBuffer[Int](100_000) +``` + +要创建具有初始元素的新 `ArrayBuffer`,只需指定其初始元素,就像 `List` 或 `Vector` 一样: + +```scala +val nums = ArrayBuffer(1, 2, 3) +val people = ArrayBuffer( + Person("Bert"), + Person("Ernie"), + Person("Grover") +) +``` + +### 将元素添加到数组缓冲区 + +使用 `+=` 和 `++=` 方法将新元素附加到 `ArrayBuffer`。 +或者,如果您更喜欢具有文本名称的方法,您也可以使用 `append`、`appendAll`、`insert`、`insertAll`、`prepend` 和 `prependAll`。 + +以下是 `+=` 和 `++=` 的一些示例: + +```scala +val nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3) +nums += 4 // ArrayBuffer(1, 2, 3, 4) +nums ++= List(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6) +``` + +### 从数组缓冲区中移除元素 + +`ArrayBuffer` 是可变的,所以它有 `-=`、`--=`、`clear`、`remove` 等方法。 +这些示例演示了 `-=` 和 `--=` 方法: + +```scala +val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g) +a -= 'a' // ArrayBuffer(b, c, d, e, f, g) +a --= Seq('b', 'c') // ArrayBuffer(d, e, f, g) +a --= Set('d', 'e') // ArrayBuffer(f, g) +``` + +### 更新数组缓冲区元素 + +通过重新分配所需元素或使用 `update` 方法来更新 `ArrayBuffer` 中的元素: + +```scala +val a = ArrayBuffer.range(1,5) // ArrayBuffer(1, 2, 3, 4) +a(2) = 50 // ArrayBuffer(1, 2, 50, 4) +a.update(0, 10) // ArrayBuffer(10, 2, 50, 4) +``` + +## 映射 + +`Map` 是由键值对组成的可迭代集合。 +Scala 有可变和不可变的 `Map` 类型,本节演示如何使用_不可变_ `Map`。 + +### 创建不可变映射 + +像这样创建一个不可变的`Map`: + +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) +``` + +一旦你有了一个`Map`,你可以像这样在`for`循环中遍历它的元素: + +```scala +for (k, v) <- states do println(s"key: $k, value: $v") +``` + +REPL 展示了它是如何工作的: + +```` +scala> for (k, v) <- states do println(s"key: $k, value: $v") +key: AK, value: Alaska +key: AL, value: Alabama +key: AZ, value: Arizona +```` + +### 访问映射元素 + +通过在括号中指定所需的键值来访问映射元素: + +```scala +val ak = states("AK") // ak: String = Alaska +val al = states("AL") // al: String = Alabama +``` + +在实践中,您还将使用诸如 `keys`、`keySet`、`keysIterator`、`for` 循环之类的方法以及 `map` 之类的高阶函数来处理 `Map` 键和值。 + +### 向映射添加元素 + +使用 `+` 和 `++` 将元素添加到不可变映射中,记住将结果分配给新变量: + +```scala +val a = Map(1 -> "one") // a: Map(1 -> one) +val b = a + (2 -> "two") // b: Map(1 -> one, 2 -> two) +val c = b ++ Seq( + 3 -> "three", + 4 -> "four" +) +// c: Map(1 -> one, 2 -> two, 3 -> three, 4 -> four) +``` + +### 从映射中删除元素 + +使用 `-` 或 `--` 和要删除的键值从不可变映射中删除元素,记住将结果分配给新变量: + +```scala +val a = Map( + 1 -> "one", + 2 -> "two", + 3 -> "three", + 4 -> "four" +) + +val b = a - 4 // b: Map(1 -> one, 2 -> two, 3 -> three) +val c = a - 4 - 3 // c: Map(1 -> one, 2 -> two) +``` + +### 更新映射元素 + +要更新不可变映射中的元素,请在将结果分配给新变量时使用 `updated` 方法(或 `+` 运算符): + +```scala +val a = Map( + 1 -> "one", + 2 -> "two", + 3 -> "three" +) + +val b = a.updated(3, "THREE!") // b: Map(1 -> one, 2 -> two, 3 -> THREE!) +val c = a + (2 -> "TWO...") // c: Map(1 -> one, 2 -> TWO..., 3 -> three) +``` + +### 遍历映射 + +如前所述,这是使用 `for` 循环手动遍历映射中元素的常用方法: + +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) + +for (k, v) <- states do println(s"key: $k, value: $v") +``` + +话虽如此,有_许多_方法可以使用映射中的键和值。 +常见的 `Map` 方法包括 `foreach`、`map`、`keys` 和 `values`。 + +Scala 有许多更专业的`Map` 类型,包括`CollisionProofHashMap`、`HashMap`、`LinkedHashMap`、`ListMap`、`SortedMap`、`TreeMap`、`WeakHashMap` 等等。 + +## 使用集合 + +Scala [集合]({{site.baseurl}}/overviews/collections-2.13/sets.html) 是一个没有重复元素的可迭代集合。 + +Scala 有可变和不可变的 `Set` 类型。 +本节演示_不可变_ `Set`。 + +### 创建一个集合 + +像这样创建新的空集: + +```scala +val nums = Set[Int]() +val letters = Set[Char]() +``` + +使用初始数据创建集合,如下: + +```scala +val nums = Set(1, 2, 3, 3, 3) // Set(1, 2, 3) +val letters = Set('a', 'b', 'c', 'c') // Set('a', 'b', 'c') +``` + +### 向集合中添加元素 + +使用 `+` 和 `++` 将元素添加到不可变的 `Set`,记住将结果分配给一个新变量: + +```scala +val a = Set(1, 2) // Set(1, 2) +val b = a + 3 // Set(1, 2, 3) +val c = b ++ Seq(4, 1, 5, 5) // HashSet(5, 1, 2, 3, 4) +``` + +请注意,当您尝试添加重复元素时,它们会被悄悄删除。 + +另请注意,元素的迭代顺序是任意的。 + +### 从集合中删除元素 + +使用 `-` 和 `--` 从不可变集合中删除元素,再次将结果分配给新变量: + +```scala +val a = Set(1, 2, 3, 4, 5) // HashSet(5, 1, 2, 3, 4) +val b = a - 5 // HashSet(1, 2, 3, 4) +val c = b -- Seq(3, 4) // HashSet(1, 2) +``` + +## 范围 + +Scala `Range` 通常用于填充数据结构和迭代 `for` 循环。 +这些 REPL 示例演示了如何创建范围: + +{% comment %} +LATER: the dotty repl currently shows results differently +{% endcomment %} + +```scala +1 to 5 // Range(1, 2, 3, 4, 5) +1 until 5 // Range(1, 2, 3, 4) +1 to 10 by 2 // Range(1, 3, 5, 7, 9) +'a' to 'c' // NumericRange(a, b, c) +``` + +您可以使用范围来填充集合: + +```scala +val x = (1 to 5).toList // List(1, 2, 3, 4, 5) +val x = (1 to 5).toBuffer // ArrayBuffer(1, 2, 3, 4, 5) +``` + +它们也用于 `for` 循环: + +```` +scala> for i <- 1 to 3 do println(i) +1 +2 +3 +```` + +还有 `range` 方法: + +```scala +Vector.range(1, 5) // Vector(1, 2, 3, 4) +List.range(1, 10, 2) // List(1, 3, 5, 7, 9) +Set.range(1, 10) // HashSet(5, 1, 6, 9, 2, 7, 3, 8, 4) +``` + +当您运行测试时,范围对于生成​​测试集合也很有用: + +```scala +val evens = (0 to 10 by 2).toList // List(0, 2, 4, 6, 8, 10) +val odds = (1 to 10 by 2).toList // List(1, 3, 5, 7, 9) +val doubles = (1 to 5).map(_ * 2.0) // Vector(2.0, 4.0, 6.0, 8.0, 10.0) + +// create a Map +val map = (1 to 3).map(e => (e,s"$e")).toMap + // map: Map[Int, String] = Map(1 -> "1", 2 -> "2", 3 -> "3") +``` + +## 更多细节 + +当您需要特定集合更多的信息,请参阅以下资源: + +- [具体的不可变集合类](https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html) +- [具体的可变集合类](https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html) +- [集合是如何构造的? 我应该选择哪一个?](https://docs.scala-lang.org/tutorials/FAQ/collections.html) + diff --git a/_zh-cn/overviews/scala3-book/collections-intro.md b/_zh-cn/overviews/scala3-book/collections-intro.md new file mode 100644 index 0000000000..e8ee13baed --- /dev/null +++ b/_zh-cn/overviews/scala3-book/collections-intro.md @@ -0,0 +1,24 @@ +--- +title: Scala 集合 +type: chapter +description: This page provides and introduction to the common collections classes and their methods in Scala 3. +num: 36 +previous-page: packaging-imports +next-page: collections-classes +--- + + +本章介绍了最常见的 Scala 3 集合及其附带的方法。 +Scala 提供了丰富的集合类型,但您可以从其中的几个开始,然后根据需要使用其他的。 +同样,每种类型都有数十种方法可以让您的生活更轻松,但您可以从少数几种方法开始使用,就可以有很多收获。 + +因此,本节介绍并演示您开始时,需要使用的最常见的集合类型和方法。 + +{% comment %} +LATER: Use more of the content from this page: + https://docs.scala-lang.org/overviews/index.html +{% endcomment %} + + + + diff --git a/_zh-cn/overviews/scala3-book/collections-methods.md b/_zh-cn/overviews/scala3-book/collections-methods.md new file mode 100644 index 0000000000..7d0d19f368 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/collections-methods.md @@ -0,0 +1,401 @@ +--- +title: 集合方法 +type: section +description: This page demonstrates the common methods on the Scala 3 collections classes. +num: 38 +previous-page: collections-classes +next-page: collections-summary +--- + + +Scala 集合的一大优势在于它们提供了许多开箱即用的方法,并且这些方法在不可变和可变集合类型中始终可用。 +这样做的好处是,您不用在每次需要使用集合时编写自定义的 `for` 循环,并且当您从一个项目转到另一个项目时,您会发现更多地使用这些相同的方法,而不是使用自定义 `for` 循环。 + +有*几十*种方法可供您使用,因此此处并未全部显示。 +相反,只显示了一些最常用的方法,包括: + +- `map` +- `filter` +- `foreach` +- `head` +- `tail` +- `take`, `takeWhile` +- `drop`, `dropWhile` +- `reduce` + +以下方法适用于所有序列类型,包括 `List`、`Vector`、`ArrayBuffer` 等,但除非另有说明,否则这些示例使用 `List`。 + +> 作为一个非常重要的说明,`List` 上的任何方法都不会改变列表。 +> 它们都以函数式风格工作,这意味着它们返回带有修改结果的新集合。 + +## 常用方法示例 + +为了让您大致了解在后面章节中将看到的内容,这些示例展示了一些最常用的集合方法。 +首先,这里有一些不使用 lambda 的方法: + +```scala +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.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.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) +``` + +### 高阶函数和 lambda + +接下来,我们将展示一些常用的接受 lambda(匿名函数)的高阶函数 (HOF)。 +首先,这里有几个 lambda 语法的变体,从最长的形式开始,逐步过渡最简洁的形式: + +```scala +// these functions are all equivalent and return +// the same data: List(10, 20, 10) + +a.filter((i: Int) => i < 25) // 1. most explicit form +a.filter((i) => i < 25) // 2. `Int` is not required +a.filter(i => i < 25) // 3. the parens are not required +a.filter(_ < 25) // 4. `i` is not required +``` + +在那些编号的例子中: + +1. 第一个例子显示了最长的形式。 + _很少_需要这么多的冗长,并且只在最复杂的用法中需要。 +2. 编译器知道 `a` 包含 `Int`,所以这里没有必要重述。 +3. 只有一个参数时不需要括号,例如`i`。 +4. 当你有一个参数并且它在你的匿名函数中只出现一次时,你可以用 `_` 替换参数。 + +[匿名函数][lambdas] 提供了与缩短 lambda 表达式相关的规则的更多详细信息和示例。 + +现在您已经看到了简洁的形式,下面是使用短形式 lambda 语法的其他 HOF 的示例: + +```scala +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ > 100) // List() +a.filterNot(_ < 25) // List(30, 40) +a.find(_ > 20) // Some(30) +a.takeWhile(_ < 30) // List(10, 20) +``` + +值得注意的是,HOF 也接受方法和函数作为参数——不仅仅是 lambda 表达式。 +下面是一些使用名为 `double` 的方法的`map` HOF 示例。 +再次显示了 lambda 语法的几种变体: + +```scala +def double(i: Int) = i * 2 + +// these all return `List(20, 40, 60, 80, 20)` +a.map(i => double(i)) +a.map(double(_)) +a.map(double) +``` + +在最后一个示例中,当匿名函数由一个接受单个参数的函数调用组成时,您不必命名参数,因此甚至不需要 `_`。 + +最后,您可以根据需要组合 HOF 来解决问题: + +```scala +// yields `List(100, 200)` +a.filter(_ < 40) + .takeWhile(_ < 30) + .map(_ * 10) +``` + +## 例子数据 + +以下部分中的示例使用这些列表: + +```scala +val oneToTen = (1 to 10).toList +val names = List("adam", "brandy", "chris", "david") +``` + +## `map` + +`map` 方法遍历现有列表中的每个元素,将您提供的函数应用于每个元素,一次一个; +然后它返回一个包含所有修改元素的新列表。 + +这是一个将 `map` 方法应用于 `oneToTen` 列表的示例: + +```scala +scala> val doubles = oneToTen.map(_ * 2) +doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) +``` + +您还可以使用长格式编写匿名函数,如下所示: + +```scala +scala> val doubles = oneToTen.map(i => i * 2) +doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) +``` + +但是,在本课中,我们将始终使用第一种较短的形式。 + +以下是更多应用于 `oneToTen` 和 `names` 列表的 `map` 方法的示例: + +```scala +scala> val capNames = names.map(_.capitalize) +capNames: List[String] = List(Adam, Brandy, Chris, David) + +scala> val nameLengthsMap = names.map(s => (s, s.length)).toMap +nameLengthsMap: Map[String, Int] = Map(adam -> 4, brandy -> 6, chris -> 5, david -> 5) + +scala> val isLessThanFive = oneToTen.map(_ < 5) +isLessThanFive: List[Boolean] = List(true, true, true, true, false, false, false, false, false, false) +``` + +如最后两个示例所示,使用 `map` 返回与原始类型不同类型的集合是完全合法的(并且很常见)。 + +## `filter` + +`filter` 方法创建一个新列表,其中包含满足所提供谓词的元素。 +谓词或条件是返回 `Boolean`(`true` 或 `false`)的函数。 +这里有一些例子: + +```scala +scala> val lessThanFive = oneToTen.filter(_ < 5) +lessThanFive: List[Int] = List(1, 2, 3, 4) + +scala> val evens = oneToTen.filter(_ % 2 == 0) +evens: List[Int] = List(2, 4, 6, 8, 10) + +scala> val shortNames = names.filter(_.length <= 4) +shortNames: List[String] = List(adam) +``` + +集合上的函数式方法的一个优点是您可以将它们链接在一起以解决问题。 +例如,这个例子展示了如何链接 `filter` 和 `map`: + +```scala +oneToTen.filter(_ < 4).map(_ * 10) +``` + +REPL 显示结果: + +```scala +scala> oneToTen.filter(_ < 4).map(_ * 10) +val res1: List[Int] = List(10, 20, 30) +``` + +## `foreach` + +`foreach` 方法用于遍历集合中的所有元素。 +请注意,`foreach` 用于副作用,例如打印信息。 +这是一个带有 `names` 列表的示例: + +```scala +scala> names.foreach(println) +adam +brandy +chris +david +``` + +## `head` + +`head` 方法来自 Lisp 和其他早期的函数式编程语言。 +它用于访问列表的第一个元素(头元素): + +```scala +oneToTen.head // 1 +names.head // adam +``` + +因为 `String` 可以看作是一个字符序列,所以你也可以把它当作一个列表。 +这就是 `head` 在这些字符串上的工作方式: + +```scala +"foo".head // 'f' +"bar".head // 'b' +``` + +`head` 是一个很好的方法,但需要注意的是,在空集合上调用它时也会抛出异常: + +```scala +val emptyList = List[Int]() // emptyList: List[Int] = List() +emptyList.head // java.util.NoSuchElementException: head of empty list +``` + +因此,您可能希望使用 `headOption` 而不是 `head`,尤其是在以函数式编程时: + +```scala +emptyList.headOption // None +``` + +如图所示,它不会抛出异常,它只是返回值为 `None` 的类型 `Option`。 +您可以在 [函数式编程][fp-intro] 章节中了解有关这种编程风格的更多信息。 + +## `tail` + +`tail` 方法也来自 Lisp,它用于打印列表头元素之后的每个元素。 +几个例子展示了这一点: + +```scala +oneToTen.head // 1 +oneToTen.tail // List(2, 3, 4, 5, 6, 7, 8, 9, 10) + +names.head // adam +names.tail // List(brandy, chris, david) +``` + +就像 `head` 一样,`tail` 也适用于字符串: + +```scala +"foo".tail // "oo" +"bar".tail // "ar" +``` + +如果列表为空,`tail` 会抛出 _java.lang.UnsupportedOperationException_,所以就像 `head` 和 `headOption` 一样,还有一个 `tailOption` 方法,这是函数式编程的首选方法。 + +也可以匹配一个列表,因此您可以编写如下表达式: + +```scala +val x :: xs = names +``` + +将该代码放在 REPL 中显示 `x` 分配给列表的头部,而 `xs` 分配给列表尾部: + +```scala +scala> val x :: xs = names +val x: String = adam +val xs: List[String] = List(brandy, chris, david) +``` + +像这样的模式匹配在许多情况下都很有用,例如使用递归编写一个 `sum` 方法: + +```scala +def sum(list: List[Int]): Int = list match + case Nil => 0 + case x :: xs => x + sum(xs) +``` + +## `take`、`takeRight`、`takeWhile` + +`take`、`takeRight` 和 `takeWhile` 方法为您提供了一种从列表中“获取”要用于创建新列表的元素的好方法。 +这是 `take` 和 `takeRight`: + +```scala +oneToTen.take(1) // List(1) +oneToTen.take(2) // List(1, 2) + +oneToTen.takeRight(1) // List(10) +oneToTen.takeRight(2) // List(9, 10) +``` + +注意这些方法是如何处理“临界”情况的,当我们要求比序列中更多的元素,或者要求零元素的时候: + +```scala +oneToTen.take(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.takeRight(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.take(0) // List() +oneToTen.takeRight(0) // List() +``` + +这是`takeWhle`,它与谓词函数一起使用: + +```scala +oneToTen.takeWhile(_ < 5) // List(1, 2, 3, 4) +names.takeWhile(_.length < 5) // List(adam) +``` + +## `drop`、`dropRight`、`dropWhile` + +`drop`、`dropRight` 和 `dropWhile` 本质上与它们对应的“取”相反,从列表中删除元素。 +这里有些例子: + +```scala +oneToTen.drop(1) // List(2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.drop(5) // List(6, 7, 8, 9, 10) + +oneToTen.dropRight(8) // List(1, 2) +oneToTen.dropRight(7) // List(1, 2, 3) +``` + +再次注意这些方法如何处理临界情况: + +```scala +oneToTen.drop(Int.MaxValue) // List() +oneToTen.dropRight(Int.MaxValue) // List() +oneToTen.drop(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.dropRight(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +``` + +这是 `dropWhile`,它与谓词函数一起使用: + +```scala +oneToTen.dropWhile(_ < 5) // List(5, 6, 7, 8, 9, 10) +names.dropWhile(_ != "chris") // List(chris, david) +``` + +## `reduce` + +当您听到“映射归约”这个术语时,“归约”部分指的是诸如 `reduce` 之类的方法。 +它接受一个函数(或匿名函数)并将该函数应用于列表中的连续元素。 + +解释 `reduce` 的最好方法是创建一个可以传递给它的小辅助方法。 +例如,这是一个将两个整数相加的 `add` 方法,还为我们提供了一些不错的调试输出: + +```scala +def add(x: Int, y: Int): Int = + val theSum = x + y + println(s"received $x and $y, their sum is $theSum") + theSum +``` + +有上面的方法和下面的列表: + +```scala +val a = List(1,2,3,4) +``` + +这就是将 `add` 方法传递给 `reduce` 时发生的情况: + +```scala +scala> a.reduce(add) +received 1 and 2, their sum is 3 +received 3 and 3, their sum is 6 +received 6 and 4, their sum is 10 +res0: Int = 10 +``` + +如该结果所示,`reduce` 使用`add` 将列表 `a` 归约为单个值,在这种情况下,是列表中整数的总和。 + +一旦你习惯了 `reduce`,你会写一个像这样的“求和”算法: + +```scala +scala> a.reduce(_ + _) +res0: Int = 10 +``` + +类似地,“连乘”算法如下所示: + +```scala +scala> a.reduce(_ * _) +res1: Int = 24 +``` + +> 关于 `reduce` 的一个重要概念是——顾名思义——它用于将集合_归约_为单个值。 + +## 更多 + +在 Scala 集合类型上确实有几十个额外的方法,可以让你不再需要编写另一个 `for` 循环。有关 Scala 集合的更多详细信息,请参阅[可变和不可变集合][mut-immut-colls]和[Scala集合的构架][architecture]。 + +> 最后一点,如果您在 Scala 项目中使用 Java 代码,您可以将 Java 集合转换为 Scala 集合。 +> 通过这样做,您可以在 `for` 表达式中使用这些集合,还可以利用 Scala 的函数式集合方法。 +> 请参阅 [与 Java 交互][interacting] 部分了解更多详细信息。 + +[interacting]: {% link _overviews/scala3-book/interacting-with-java.md %} +[lambdas]: {% link _overviews/scala3-book/fun-anonymous-functions.md %} +[fp-intro]: {% link _overviews/scala3-book/fp-intro.md %} +[mut-immut-colls]: {% link _overviews/collections-2.13/overview.md %} +[architecture]: {% link _overviews/core/architecture-of-scala-213-collections.md %} + diff --git a/_zh-cn/overviews/scala3-book/collections-summary.md b/_zh-cn/overviews/scala3-book/collections-summary.md new file mode 100644 index 0000000000..4a47cb2ffd --- /dev/null +++ b/_zh-cn/overviews/scala3-book/collections-summary.md @@ -0,0 +1,31 @@ +--- +title: 总结 +type: section +description: This page provides a summary of the Collections chapter. +num: 39 +previous-page: collections-methods +next-page: fp-intro +--- + + +本章总结了常见的 Scala 3 集合及其附带的方法。 +如图所示,Scala 带有丰富的集合和方法。 + +当您需要查看本章中显示的集合类型的更多详细信息时,请参阅他们的 Scaladoc 页面: + +- [List](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) +- [Vector](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html) +- [ArrayBuffer](https://www.scala-lang.org/api/current/scala/collection/mutable/ArrayBuffer.html) +- [Range](https://www.scala-lang.org/api/current/scala/collection/immutable/Range.html) + +也提到了不可变的 `Map` 和 `Set`: + +- [Map](https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html) +- [Set](https://www.scala-lang.org/api/current/scala/collection/immutable/Set.html) + +和可变的 `Map` 和 `Set`: + +- [Map](https://www.scala-lang.org/api/current/scala/collection/mutable/Map.html) +- [Set](https://www.scala-lang.org/api/current/scala/collection/mutable/Set.html) + + From f751a72c38639a08f11d2e4c6b235e15c3d06964 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 29 May 2022 20:55:50 +0800 Subject: [PATCH 19/53] add all fp files --- .../fp-functional-error-handling.md | 303 ++++++++++++++++++ .../scala3-book/fp-functions-are-values.md | 99 ++++++ .../scala3-book/fp-immutable-values.md | 70 ++++ _zh-cn/overviews/scala3-book/fp-intro.md | 24 ++ .../scala3-book/fp-pure-functions.md | 106 ++++++ _zh-cn/overviews/scala3-book/fp-summary.md | 24 ++ _zh-cn/overviews/scala3-book/fp-what-is-fp.md | 27 ++ 7 files changed, 653 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/fp-functional-error-handling.md create mode 100644 _zh-cn/overviews/scala3-book/fp-functions-are-values.md create mode 100644 _zh-cn/overviews/scala3-book/fp-immutable-values.md create mode 100644 _zh-cn/overviews/scala3-book/fp-intro.md create mode 100644 _zh-cn/overviews/scala3-book/fp-pure-functions.md create mode 100644 _zh-cn/overviews/scala3-book/fp-summary.md create mode 100644 _zh-cn/overviews/scala3-book/fp-what-is-fp.md diff --git a/_zh-cn/overviews/scala3-book/fp-functional-error-handling.md b/_zh-cn/overviews/scala3-book/fp-functional-error-handling.md new file mode 100644 index 0000000000..effde2f6c8 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-functional-error-handling.md @@ -0,0 +1,303 @@ +--- +title: 函数式错误处理 +type: section +description: This section provides an introduction to functional error handling in Scala 3. +num: 45 +previous-page: fp-functions-are-values +next-page: fp-summary +--- + + +函数式编程就像写一系列代数方程,因为代数没有空值或抛出异常,所以你不用在 FP 中使用这些特性。 +这带来了一个有趣的问题:在 OOP 代码中通常可能使用空值或异常的情况下,您会怎么做? + +Scala 的解决方案是使用类似 `Option`/`Some`/`None` 类的结构。 +本课介绍如何使用这些技术。 + +在我们开始之前有两个注意事项: + +- `Some` 和 `None` 类是 `Option` 的子类。 +- 下面的文字一般只指“`Option`”或“`Option`类”,而不是重复说“`Option`/`Some`/`None`”。 + +## 第一个例子 + +虽然第一个示例不处理空值,但它是引入 `Option` 类的好方法,所以我们将从它开始。 + +想象一下,您想编写一个方法,可以轻松地将字符串转换为整数值,并且您想要一种优雅的方法来处理异常,这个是异常是该方法获取类似“Hello”而不是“1”的字符串时引发的。 +对这种方法的初步猜测可能如下所示: + +```scala +def makeInt(s: String): Int = + try + Integer.parseInt(s.trim) + catch + case e: Exception => 0 +``` + +如果转换成功,则此方法返回正确的 `Int` 值,但如果失败,则该方法返回 `0`。 +出于某些目的,这可能是可以的,但它并不准确。 +例如,该方法可能收到了`"0"`,但它也可能收到了 `"foo"`、`"bar"` 或无数其他将引发异常的字符串。 +这是一个真正的问题:您如何知道该方法何时真正收到 `"0"`,或者何时收到其他内容? +答案是,用这种方法,没有办法知道。 + +## 使用 Option/Some/None + +Scala 中这个问题的一个常见解决方案是使用三个类,称为 `Option`、`Some` 和 `None` 。 +`Some` 和 `None` 类是 `Option` 的子类,因此解决方案的工作原理如下: + +- 你声明 `makeInt` 返回一个 `Option` 类型 +- 如果 `makeInt` 接收到一个字符串,它*可以* 转换为 `Int`,答案将包含在 `Some` 中 +- 如果 `makeInt` 接收到一个它*无法*转换的字符串,它返回一个 `None` + +这是 `makeInt` 的修订版: + +```scala +def makeInt(s: String): Option[Int] = + try + Some(Integer.parseInt(s.trim)) + catch + case e: Exception => None +``` + +这段代码可以理解为,“当给定的字符串转换为整数时,返回包裹在 `Some` 中的 `Int`,例如 `Some(1)`。 +当字符串无法转换为整数时,会抛出并捕获异常,并且该方法返回一个 `None` 值。” + +这些示例展示了 `makeInt` 的工作原理: + +```scala +val a = makeInt("1") // Some(1) +val b = makeInt("one") // None +``` + +如图所示,字符串`"1"`产生一个 `Some(1)`,而字符串 `"one"` 产生一个 `None`。 +这是错误处理的 `Option` 方法的本质。 +如图所示,使用了这种技术,因此方法可以返回 *值* 而不是 *异常*。 +在其他情况下,`Option` 值也用于替换 `null` 值。 + +两个注意事项: + +- 你会发现这种方法在整个 Scala 库类和第三方 Scala 库中使用。 +- 这个例子的一个关键点是函数式方法不会抛出异常;相反,它们返回类似 `Option` 的值。 + +## 成为 makeInt 的消费者 + +现在假设您是 `makeInt` 方法的使用者。 +你知道它返回一个 `Option[Int]` 的子类,所以问题就变成了,你如何处理这些返回类型? + +根据您的需要,有两个常见的答案: + +- 使用 `match` 表达式 +- 使用 `for` 表达式 + +## 使用 `match` 表达式 + +一种可能的解决方案是使用 `match` 表达式: + +```scala +makeInt(x) match + case Some(i) => println(i) + case None => println("That didn’t work.") +``` + +在本例中,如果 `x` 可以转换为 `Int`,则计算第一个 `case` 子句右侧的表达式;如果 `x` 不能转换为 `Int`,则计算第二个 `case` 子句右侧的表达式。 + +## 使用 `for` 表达式 + +另一种常见的解决方案是使用 `for` 表达式,即本书前面显示的 `for`/`yield` 组合。 +例如,假设您要将三个字符串转换为整数值,然后将它们相加。 +这就是你使用 `for` 表达式和 `makeInt` 的方法: + +```scala +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` + +在该表达式运行后,`y` 将是以下两种情况之一: + +- 如果*所有*三个字符串都转换为 `Int` 值,`y` 将是 `Some[Int]`,即包裹在 `Some` 中的整数 +- 如果三个字符串中*任意一个*字符串不能转换为 `Int`,则 `y` 将是 `None` + +你可以自己测试一下: + +```scala +val stringA = "1" +val stringB = "2" +val stringC = "3" + +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` + +使用该样本数据,变量 `y` 的值将是 `Some(6)` 。 + +要查看失败案例,请将这些字符串中的任何一个更改为不会转换为整数的字符串。 +当你这样做时,你会看到 `y` 是 `None`: + +```scala +y: Option[Int] = None +``` + +## 将 Option 视为容器 + +心智模型通常可以帮助我们理解新情况,因此,如果您不熟悉 `Option` 类,可以将它们视为*容器*: + +- `Some` 是一个容器,里面有一个项目 +- `None` 是一个容器,但里面什么都没有 + +如果您更愿意将 `Option` 类想象成一个盒子,`None` 就像一个空盒子。 +它可能有一些东西,但它没有。 + +{% comment %} +NOTE: I commented-out this subsection because it continues to explain Some and None, and I thought it was probably too much for this book. + +## Using `foreach` with `Option` + +Because `Some` and `None` can be thought of containers, they’re also like collections classes. +They have many of the methods you’d expect from a collection class, including `map`, `filter`, `foreach`, etc. + +This raises an interesting question: What will these two values print, if anything? + +```scala +makeInt("1").foreach(println) +makeInt("x").foreach(println) +``` + +Answer: The first example prints the number `1`, and the second example doesn’t print anything. +The first example prints `1` because: + +- `makeInt("1")` evaluates to `Some(1)` +- The expression becomes `Some(1).foreach(println)` +- The `foreach` method on the `Some` class knows how to reach inside the `Some` container and extract the value (`1`) that’s inside it, so it passes that value to `println` + +Similarly, the second example prints nothing because: + +- `makeInt("x")` evaluates to `None` +- The `foreach` method on the `None` class knows that `None` doesn’t contain anything, so it does nothing + +In this regard, `None` is similar to an empty `List`. + +### The happy and unhappy paths + +Somewhere in Scala’s history, someone noted that the first example (the `Some`) represents the “Happy Path” of the `Option` approach, and the second example (the `None`) represents the “Unhappy Path.” +*But* despite having two different possible outcomes, the great thing with `Option` is that there’s really just one path: The code you write to handle the `Some` and `None` possibilities is the same in both cases. +The `foreach` examples look like this: + +```scala +makeInt(aString).foreach(println) +``` + +And the `for` expression looks like this: + +```scala +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` + +With exceptions you have to worry about handling branching logic, but because `makeInt` returns a value, you only have to write one piece of code to handle both the Happy and Unhappy Paths, and that simplifies your code. + +Indeed, the only time you have to think about whether the `Option` is a `Some` or a `None` is when you handle the result value, such as in a `match` expression: + +```scala +makeInt(x) match + case Some(i) => println(i) + case None => println("That didn't work.") +``` + +> There are several other ways to handle `Option` values. +> See the reference documentation for more details. +{% endcomment %} + +## 使用 `Option` 替换 `null` + +回到 `null` 值,`null` 值可以悄悄地潜入你的代码的地方是这样的类: + +```scala +class Address( + var street1: String, + var street2: String, + var city: String, + var state: String, + var zip: String +) +``` + +虽然地球上的每个地址都有一个 `street1` 值,但 `street2` 值是可选的。 +因此,`street2` 字段可以被分配一个 `null` 值: + +```scala +val santa = Address( + "1 Main Street", + null, // <-- D’oh! A null value! + "North Pole", + "Alaska", + "99705" +) +``` + +从历史上看,开发人员在这种情况下使用了空白字符串和空值,这两种方法都是使用技巧来解决基础性的问题,这个问题是:`street2` 是一个*可选*字段。 +在 Scala 和其他现代语言中,正确的解决方案是预先声明 `street2` 是可选的: + +```scala +class Address( + var street1: String, + var street2: Option[String], // an optional value + var city: String, + var state: String, + var zip: String +) +``` + +现在开发人员可以编写更准确的代码,如下所示: + +```scala +val santa = Address( + "1 Main Street", + None, // 'street2' has no value + "North Pole", + "Alaska", + "99705" +) +``` + +或这个: + +```scala +val santa = Address( + "123 Main Street", + Some("Apt. 2B"), + "Talkeetna", + "Alaska", + "99676" +) +``` + +## `Option` 不是唯一的解决方案 + +虽然本节关注的是 `Option` 类,但 Scala 还有一些其他选择。 + +例如,称为 `Try`/`Success`/`Failure` 的三个类以相同的方式工作,但是 (a) 当您的代码可以抛出异常时,您主要使用这些类,并且 (b) 您想要使用`Failure` 类,因为它使您可以访问异常消息。 +例如,在编写与文件、数据库和 Internet 服务交互的方法时,通常会使用这些 `Try` 类,因为这些函数很容易引发异常。 + +## 快速回顾 + +这部分很长,让我们快速回顾一下: + +- 函数式程序员不使用 `null` 值 +- `null` 值的主要替代品是使用 `Option` 类 +- 函数式方法不会抛出异常; 相反,它们返回诸如 `Option` 、 `Try` 或 `Either` 之类的值 +- 使用 `Option` 值的常用方法是 `match` 和 `for` 表达式 +- Option 可以被认为是一个项目(`Some`)和没有项目(`None`)的容器 +- option 也可用于可选的构造函数或方法参数 + diff --git a/_zh-cn/overviews/scala3-book/fp-functions-are-values.md b/_zh-cn/overviews/scala3-book/fp-functions-are-values.md new file mode 100644 index 0000000000..a4e3c6df72 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-functions-are-values.md @@ -0,0 +1,99 @@ +--- +title: 函数是值 +type: section +description: This section looks at the use of functions as values in functional programming. +num: 44 +previous-page: fp-pure-functions +next-page: fp-functional-error-handling +--- + + +虽然曾经创建的每种编程语言都可能允许您编写纯函数,但 Scala FP 的第二个重要特性是*您可以将函数创建为值*,就像您创建 `String` 和 `Int` 值一样。 + +这个特性有很多好处,其中最常见的是(a)您可以定义方法来接受函数参数,以及(b)您可以将函数作为参数传递给方法。 +你已经在本书的多个地方看到了这一点,每当演示像 `map` 和 `filter` 这样的方法时: + +```scala +val nums = (1 to 10).toList + +val doubles = nums.map(_ * 2) // double each value +val lessThanFive = nums.filter(_ < 5) // List(1,2,3,4) +``` + +在这些示例中,匿名函数被传递到 `map` 和 `filter` 中。 + +> 匿名函数也称为 *lambdas*。 + +除了将匿名函数传递给 `filter` 和 `map` 之外,您还可以为它们提供 *方法*: + +```scala +// two methods +def double(i: Int): Int = i * 2 +def underFive(i: Int): Boolean = i < 5 + +// pass those methods into filter and map +val doubles = nums.filter(underFive).map(double) +``` + +这种将方法和函数视为值的能力是函数式编程语言提供的强大特性。 + +> 从技术上讲,将另一个函数作为输入参数的函数称为 *高阶函数*。 +> (如果你喜欢幽默,就像有人曾经写过的那样,这就像说一个将另一个类的实例作为构造函数参数的类是一个高阶类。) + +## 函数、匿名函数和方法 + +正如您在这些示例中看到的,这是一个匿名函数: + +```scala +_ * 2 +``` + +如 [高阶函数][hofs] 讨论中所示,上面的写法是下面语法的简写版本: + +```scala +(i: Int) => i * 2 +``` + +像这样的函数被称为“匿名”,因为它们没有名字。 +如果你想给一个名字,只需将它分配给一个变量: + +```scala +val double = (i: Int) => i * 2 +``` + +现在你有了一个命名函数,一个分配给变量的函数。 +您可以像使用方法一样使用此函数: + +```scala +double(2) // 4 +``` + +在大多数情况下,`double` 是函数还是方法并不重要。 Scala 允许您以同样的方式对待它们。 +在幕后,让您像对待函数一样对待方法的 Scala 技术被称为 [Eta 表达式][eta]。 + +这种将函数作为变量无缝传递的能力是 Scala 等函数式编程语言的一个显着特征。 +正如您在本书中的 `map` 和 `filter` 示例中所见,将函数传递给其他函数的能力有助于您创建简洁且仍然可读的代码---*富有表现力*。 + +如果您对将函数作为参数传递给其他函数的过程不适应,可以尝试以下几个示例: + +```scala +List("bob", "joe").map(_.toUpperCase) // List(BOB, JOE) +List("bob", "joe").map(_.capitalize) // List(Bob, Joe) +List("plum", "banana").map(_.length) // List(4, 6) + +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(5, 1, 3, 11, 7) +nums.map(_ * 2) // List(10, 2, 6, 22, 14) +nums.filter(_ > 3) // List(5, 11, 7) +nums.takeWhile(_ < 6) // List(5, 1, 3) +nums.sortWith(_ < _) // List(1, 3, 5, 7, 11) +nums.sortWith(_ > _) // List(11, 7, 5, 3, 1) + +nums.takeWhile(_ < 6).sortWith(_ < _) // List(1, 3, 5) +``` + +[hofs]: {% link _overviews/scala3-book/fun-hofs.md %} +[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_zh-cn/overviews/scala3-book/fp-immutable-values.md b/_zh-cn/overviews/scala3-book/fp-immutable-values.md new file mode 100644 index 0000000000..d176af3a75 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-immutable-values.md @@ -0,0 +1,70 @@ +--- +title: 不可变值 +type: section +description: This section looks at the use of immutable values in functional programming. +num: 42 +previous-page: fp-what-is-fp +next-page: fp-pure-functions +--- + + +在纯函数式编程中,只使用不可变的值。 +在 Scala 中,这意味着: + +- 所有变量都创建为 `val` 字段 +- 仅使用不可变的集合类,例如 `List`、`Vector` 和不可变的 `Map` 和 `Set` 类 + +只使用不可变变量会引发一个有趣的问题:如果一切都是不可变的,那么任何东西都如何改变? + +当谈到使用集合时,一个答案是你不会改变现有的集合。相反,您将函数应用于现有集合以创建新集合。 +这就是像 `map` 和 `filter` 这样的高阶函数的用武之地。 + +例如,假设你有一个名字列表——一个 `List[String]`——都是小写的,你想找到所有以字母 `"j"` 开头的名字,并且把找出来的名字大写。 +在 FP 中,您编写以下代码: + +```scala +val a = List("jane", "jon", "mary", "joe") +val b = a.filter(_.startsWith("j")) + .map(_.capitalize) +``` + +如图所示,您不会改变原始列表 `a`。 +相反,您将过滤和转换函数应用于 `a` 以创建一个新集合,并将该结果分配给新的不可变变量 `b` 。 + +同样,在 FP 中,您不会创建具有可变 `var` 构造函数参数的类。 +也就是说,你不要这样写: + +```scala +```scala +// don’t do this in FP +class Person(var firstName: String, var lastName: String) + --- --- +``` + +相反,您通常创建 `case` 类,其构造函数参数默认为 `val`: + +```scala +case class Person(firstName: String, lastName: String) +``` + +现在你创建一个 `Person` 实例作为 `val` 字段: + +```scala +val reginald = Person("Reginald", "Dwight") +``` + +然后,当您需要对数据进行更改时,您可以使用 `case` 类附带的 `copy` 方法来“在制作副本时更新数据”,如下所示: + +```scala +val elton = reginald.copy( + firstName = "Elton", // update the first name + lastName = "John" // update the last name +) +``` + +还有其他处理不可变集合和变量的技术,但希望这些示例能让您尝试一下这些技术。 + +> 根据您的需要,您可以创建枚举、traits 或类,而不是 `case` 类。 +> 有关详细信息,请参阅[数据建模][modeling]一章。 + +[modeling]: {% link _overviews/scala3-book/domain-modeling-intro.md %} diff --git a/_zh-cn/overviews/scala3-book/fp-intro.md b/_zh-cn/overviews/scala3-book/fp-intro.md new file mode 100644 index 0000000000..83e1c6c08e --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-intro.md @@ -0,0 +1,24 @@ +--- +title: 函数式编程 +type: chapter +description: This chapter provides an introduction to functional programming in Scala 3. +num: 40 +previous-page: collections-summary +next-page: fp-what-is-fp +--- + + +Scala 允许您以面向对象编程 (OOP) 风格、函数式编程 (FP) 风格以及混合风格编写代码——结合使用这两种方法。 +[正如 Martin Odersky 所说](https://twitter.com/alexelcu/status/996408359514525696),Scala 的本质是在类型化设置中融合了函数式编程和面向对象编程: + +- 逻辑函数 +- 模块化的对象 + +本章假设您熟悉 OOP 而不太熟悉 FP,因此它简要介绍了几个主要的函数式编程概念: + +- 什么是函数式编程? +- 不可变值 +- 纯函数 +- 函数是值 +- 函数式错误处理 + diff --git a/_zh-cn/overviews/scala3-book/fp-pure-functions.md b/_zh-cn/overviews/scala3-book/fp-pure-functions.md new file mode 100644 index 0000000000..3a88f34431 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-pure-functions.md @@ -0,0 +1,106 @@ +--- +title: 纯函数 +type: section +description: This section looks at the use of pure functions in functional programming. +num: 43 +previous-page: fp-immutable-values +next-page: fp-functions-are-values +--- + + +Scala 提供的另一个帮助您编写函数式代码的特性是编写纯函数的能力。 +一个_纯函数_可以这样定义: + +- 函数 `f` 是纯函数,如果给定相同的输入 `x`,它总是返回相同的输出 `f(x)` +- 函数的输出_只_取决于它的输入变量和它的实现 +- 它只计算输出而不修改周围的世界 + +这意味着: +- 它不修改其输入参数 +- 它不会改变任何隐藏状态 +- 没有任何“后门”:不从外界读取数据(包括控制台、Web服务、数据库、文件等),也不向外界写入数据 + +作为这个定义的结果,任何时候你调用一个具有相同输入值的纯函数,你总是会得到相同的结果。 +例如,您可以使用输入值 `2` 无限次调用 `double` 函数,并且始终得到结果 `4`。 + +## 纯函数示例 + +给定这个定义,你可以想象, *scala.math._* 包中的这些方法是纯函数: + +- `abs` +- `ceil` +- `max` + +这些 `String` 方法也是纯函数: + +-`isEmpty` +- `length` +- `substring` + +Scala 集合类上的大多数方法也可以作为纯函数工作,包括 `drop`、`filter`、`map` 等等。 + +> 在 Scala 中,_函数_和_方法_几乎可以完全互换,因此即使我们使用行业通用术语“纯函数”,这个术语也可以用来描述函数和方法。 +> 如果您对如何像函数一样使用方法感兴趣,请参阅 [Eta 表达式][eta] 的讨论。 + +## 不纯函数示例 + +相反,以下函数是_不纯的_,因为它们违反了纯函数的定义。 + +- `println` -- 与控制台、文件、数据库、Web 服务、传感器等交互的方法都是不纯的。 +- `currentTimeMillis ` -- 与日期和时间相关的方法都是不纯的,因为它们的输出取决于输入参数以外的其他东西 +- `sys.error` -- 异常抛出方法是不纯的,因为它们不简单地返回结果 + +不纯函数通常会执行以下一项或多项操作: + +- 从隐藏状态读取,即它们访问的变量和数据不是作为输入参数明确地传递给函数 +- 写入隐藏状态 +- 改变他们给定的参数,或改变隐藏变量,例如类中包涵的字段 +- 与外界执行某种 I/O + +> 通常,您应该注意返回类型为 `Unit` 的函数。 +> 因为这些函数不返回任何东西,逻辑上你调用它的唯一原因是实现一些副作用。 +> 因此,这些功能的使用通常是不纯的。 + +## 但是需要不纯的函数... + +当然,如果一个应用程序不能读取或写入外部世界,它就不是很有用,所以人们提出以下建议: + +> 使用纯函数编写应用程序的核心,然后围绕该核心编写一个不纯的“包装器”以与外部世界交互。 +> 正如有人曾经说过的,这就像在纯蛋糕上加了一层不纯的糖霜。 + +重要的是要注意,有一些方法可以让与外界的不纯粹互动感觉更纯粹。 +例如,你会听说使用 `IO` Monad 来处理输入和输出。 +这些主题超出了本文档的范围,所以为了简单起见,这样认识 FP 会有所帮助:FP 应用程序有一个纯函数核心,还有其他一些函数把这些纯函数包装起来与外部世界交互。 + +## 编写纯函数 + +**注意**:在本节中,常见的行业术语“纯函数”通常用于指代 Scala 方法。 + +要在 Scala 中编写纯函数,只需使用 Scala 的方法语法编写它们(尽管您也可以使用 Scala 的函数语法)。 +例如,这是一个将给定输入值加倍的纯函数: + +```scala +def double(i: Int): Int = i * 2 +``` + +如果您对递归感到满意,这是一个计算整数列表之和的纯函数: + +```scala +def sum(xs: List[Int]): Int = xs match + case Nil => 0 + case head :: tail => head + sum(tail) +``` + +如果您理解该代码,您会发现它符合纯函数定义。 + +## 要点 + +本节的第一个要点是纯函数的定义: + +> _纯函数_是仅依赖于其声明的输入及其实现来产生其输出的函数。 +> 它只计算其输出,不依赖或修改外部世界。 + +第二个要点是每个现实世界的应用程序都与外部世界交互。 +因此,考虑函数式程序的一种简化方法是,它们由一个纯函数核心组成,其他一些函数把这些纯函数包装起来与外部世界交互。 + +[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_zh-cn/overviews/scala3-book/fp-summary.md b/_zh-cn/overviews/scala3-book/fp-summary.md new file mode 100644 index 0000000000..47a3019a51 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-summary.md @@ -0,0 +1,24 @@ +--- +title: 总结 +type: section +description: This section summarizes the previous functional programming sections. +num: 46 +previous-page: fp-functional-error-handling +next-page: types-introduction +--- + + +本章在比较高的层次上,对 Scala 中的函数式编程进行了介绍。 +涵盖的主题是: + +- 什么是函数式编程? +- 不可变值 +- 纯函数 +- 函数是值 +- 函数式错误处理 + +如前所述,函数式编程是一个很大的话题,所以我们在本书中所能做的就是触及这些介绍性概念。 +有关详细信息,请参阅 [参考文档][reference]。 + +[reference]: {{ site.scala3ref }}/overview.html + diff --git a/_zh-cn/overviews/scala3-book/fp-what-is-fp.md b/_zh-cn/overviews/scala3-book/fp-what-is-fp.md new file mode 100644 index 0000000000..314b39fce5 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/fp-what-is-fp.md @@ -0,0 +1,27 @@ +--- +title: 什么是函数式编程? +type: section +description: This section provides an answer to the question, what is functional programming? +num: 41 +previous-page: fp-intro +next-page: fp-immutable-values +--- + + +[维基百科定义_函数式编程_](https://en.wikipedia.org/wiki/Functional_programming)如下: + +
+

函数式编程是一种编程范式,通过应用和组合函数来构建程序。 +它是一种声明式编程范例,其中函数定义是表达式树,每个表达式都返回一个值,而不是改变程序状态的命令式语句序列。

+

 

+

在函数式编程中,函数被视为一等公民,这意味着它们可以绑定到名称(包括本地标识符)、作为参数传递以及从其他函数返回,就像任何其他数据类型一样。 +这允许以声明式和可组合的方式编写程序,其中小函数以模块化方式组合。

+
+ +知道这样的情况对你很有帮助:有经验的函数式程序员非常倾向于将他们的代码视为数学,将纯函数组合在一起就像组合一系列代数方程一样。 + +当你编写函数式代码时,你会感觉自己像个数学家,一旦你理解了范式,你就会想编写总是返回_值_---而不是异常或空值---的纯函数,这样你就可以将它们组合(结合)在一起以创建解决方案。 +编写类似数学的方程式(表达式)的感觉是驱使您_只_使用纯函数和不可变值的动力,因为这就是您在代数和其他形式的数学中使用的东西。 + +函数式编程是一个很大的主题,没有简单的方法可以将整个主题浓缩为一章,但希望以下部分能够概述主要主题,并展示 Scala 为编写函数式代码提供的一些工具。 + From d811abadddd44817103b8a62b4f43303250c843c Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 5 Jun 2022 22:44:35 +0800 Subject: [PATCH 20/53] add all types files --- .../overviews/scala3-book/types-adts-gadts.md | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/types-adts-gadts.md diff --git a/_zh-cn/overviews/scala3-book/types-adts-gadts.md b/_zh-cn/overviews/scala3-book/types-adts-gadts.md new file mode 100644 index 0000000000..5c504d0165 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-adts-gadts.md @@ -0,0 +1,207 @@ +--- +title: 代数数据类型 +type: section +description: This section introduces and demonstrates algebraic data types (ADTs) in Scala 3. +num: 52 +previous-page: types-union +next-page: types-variance +--- + + +代数数据类型 (ADT) 可以使用 `enum` 构造创建,因此我们将在查看 ADT 之前简要回顾一下枚举。 + +## 枚举 + +_enumeration_ 用于定义由一组命名值组成的类型: + +```scala +enum Color: + case Red, Green, Blue +``` + +这可以看作是以下的简写: + +```scala +enum Color: + case Red extends Color + case Green extends Color + case Blue extends Color +``` + +#### 参数 + +枚举可以参数化: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +这样,每个不同的变体都有一个值成员 `rgb`,它被分配了相应的值: + +```scala +println(Color.Green.rgb) // prints 65280 +``` + +#### 自定义 + +枚举也可以有自定义: + +```scala +enum Planet(mass: Double, radius: Double): + + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity + + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Venus extends Planet(4.869e+24, 6.0518e6) + case Earth extends Planet(5.976e+24, 6.37814e6) + // 5 or 6 more planets ... +``` + +像类和 `case` 类一样,你也可以为枚举定义一个伴生对象: + +```scala +object Planet: + def main(args: Array[String]) = + val earthWeight = args(0).toDouble + val mass = earthWeight / Earth.surfaceGravity + for (p <- values) + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") +``` + +## 代数数据类型 (ADTs) + +`enum` 概念足够通用,既支持_代数数据类型_(ADT)和它的通用版本(GADT)。 +本示例展示了如何将 `Option` 类型表示为 ADT: + +```scala +enum Option[+T]: + case Some(x: T) + case None +``` + +这个例子创建了一个带有协变类型参数 `T` 的 `Option` 枚举,它由两种情况组成, `Some` 和 `None`。 +`Some` 是_参数化_的,它带有值参数 `x`;它是从 `Option` 继承的 `case` 类的简写。 +由于 `None` 没有参数化,它被视为普通的 `enum` 值。 + +前面示例中省略的 `extends` 子句也可以显式给出: + +```scala +enum Option[+T]: + case Some(x: T) extends Option[T] + case None extends Option[Nothing] +``` + +与普通的 `enum` 值一样,`enum` 的情况是在 `enum` 的伴生对象中定义的,因此它们被引用为 `Option.Some` 和 `Option.None`(除非定义是在导入时单独列出): + +```scala +scala> Option.Some("hello") +val res1: t2.Option[String] = Some(hello) + +scala> Option.None +val res2: t2.Option[Nothing] = None +``` + +与其他枚举用途一样,ADT 可以定义更多的方法。 +例如,这里又是一个 `Option`,它的伴生对象中有一个 `isDefined` 方法和一个 `Option(...)` 构造函数: + +```scala +enum Option[+T]: + case Some(x: T) + case None + + def isDefined: Boolean = this match + case None => false + case Some(_) => true + +object Option: + def apply[T >: Null](x: T): Option[T] = + if (x == null) None else Some(x) +``` + +枚举和 ADT 共享相同的句法结构,因此它们可以 +被简单地视为光谱的两端,把二者混搭是完全可能的。 +例如,下面的代码给出了一个 +`Color` 的实现,可以使用三个枚举值或使用 +RGB 值的参数化情况: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) + case Mix(mix: Int) extends Color(mix) +``` + +#### 递归枚举 + +到目前为止,我们定义的所有枚举都由值或样例类的不同变体组成。 +枚举也可以是递归的,如下面的自然数编码示例所示: + +```scala +enum Nat: + case Zero + case Succ(n: Nat) +``` + +例如,值 `Succ(Succ(Zero))` 表示一元编码中的数字 `2`。 +列表可以以非常相似的方式定义: + +```scala +enum List[+A]: + case Nil + case Cons(head: A, tail: List[A]) +``` + +## 广义代数数据类型 (GADT) + +上面的枚举表示法非常简洁,可以作为建模数据类型的完美起点。 +由于我们总是可以更明确,因此也可以表达更强大的类型:广义代数数据类型 (GADT)。 + +这是一个 GADT 示例,其中类型参数 (`T`) 指定存储在框中的内容: + +```scala +enum Box[T](contents: T): + case IntBox(n: Int) extends Box[Int](n) + case BoolBox(b: Boolean) extends Box[Boolean](b) +``` + +特定构造函数(`IntBox` 或 `BoolBox`)上的模式匹配可恢复类型信息: + +```scala +def extract[T](b: Box[T]): T = b match + case IntBox(n) => n + 1 + case BoolBox(b) => !b +``` + +只有在第一种情况下返回一个 `Int` 才是安全的,因为我们从 pattern 匹配输入是一个“IntBox”。 + +## 去除语法糖的枚举 + +_从概念上讲_,枚举可以被认为是定义一个密封类及其伴生对象。 +让我们看看上面的 `Color` 枚举的无语法糖版本: + +```scala +sealed abstract class Color(val rgb: Int) extends scala.reflect.Enum +object Color: + case object Red extends Color(0xFF0000) { def ordinal = 0 } + case object Green extends Color(0x00FF00) { def ordinal = 1 } + case object Blue extends Color(0x0000FF) { def ordinal = 2 } + case class Mix(mix: Int) extends Color(mix) { def ordinal = 3 } + + def fromOrdinal(ordinal: Int): Color = ordinal match + case 0 => Red + case 1 => Green + case 2 => Blue + case _ => throw new NoSuchElementException(ordinal.toString) +``` + +请注意,上面的去除语法糖被简化了,我们故意省略了[一些细节][desugar-enums]。 + +虽然枚举可以使用其他构造手动编码,但使用枚举更简洁,并且还附带了一些额外的实用程序(例如 `fromOrdinal` 方法)。 + +[desugar-enums]: {{ site.scala3ref }}/enums/desugarEnums.html From b9c6ac030f24b850f83894d30583e3b700e85166 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 5 Jun 2022 22:45:04 +0800 Subject: [PATCH 21/53] add all types files --- .../scala3-book/types-dependent-function.md | 169 ++++++++++++++++++ .../overviews/scala3-book/types-generics.md | 46 +++++ .../overviews/scala3-book/types-inferred.md | 40 +++++ .../scala3-book/types-intersection.md | 43 +++++ .../scala3-book/types-introduction.md | 47 +++++ .../scala3-book/types-opaque-types.md | 154 ++++++++++++++++ _zh-cn/overviews/scala3-book/types-others.md | 24 +++ .../overviews/scala3-book/types-structural.md | 104 +++++++++++ .../scala3-book/types-type-classes.md | 52 ++++++ _zh-cn/overviews/scala3-book/types-union.md | 99 ++++++++++ .../overviews/scala3-book/types-variance.md | 164 +++++++++++++++++ 11 files changed, 942 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/types-dependent-function.md create mode 100644 _zh-cn/overviews/scala3-book/types-generics.md create mode 100644 _zh-cn/overviews/scala3-book/types-inferred.md create mode 100644 _zh-cn/overviews/scala3-book/types-intersection.md create mode 100644 _zh-cn/overviews/scala3-book/types-introduction.md create mode 100644 _zh-cn/overviews/scala3-book/types-opaque-types.md create mode 100644 _zh-cn/overviews/scala3-book/types-others.md create mode 100644 _zh-cn/overviews/scala3-book/types-structural.md create mode 100644 _zh-cn/overviews/scala3-book/types-type-classes.md create mode 100644 _zh-cn/overviews/scala3-book/types-union.md create mode 100644 _zh-cn/overviews/scala3-book/types-variance.md diff --git a/_zh-cn/overviews/scala3-book/types-dependent-function.md b/_zh-cn/overviews/scala3-book/types-dependent-function.md new file mode 100644 index 0000000000..20550ffabd --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-dependent-function.md @@ -0,0 +1,169 @@ +--- +title: 依赖函数类型 +type: section +description: This section introduces and demonstrates dependent function types in Scala 3. +num: 56 +previous-page: types-structural +next-page: types-others +--- + + +*依赖函数类型*描述函数类型,其中结果类型可能取决于函数的参数值。 +依赖类型和依赖函数类型的概念更高级,您通常只会在设计自己的库或使用高级库时遇到它。 + +## 依赖方法类型 + +让我们考虑以下可以存储不同类型值的异构数据库示例。 +键包含有关相应值的类型的信息: + +```scala +trait Key { type Value } + +trait DB { + def get(k: Key): Option[k.Value] // a dependent method +} +``` + +给定一个键,`get` 方法允许我们访问地图并可能返回类型为 `k.Value` 的存储值。 +我们可以将这个_路径依赖类型_解读为:“根据参数 `k` 的具体类型,我们返回一个匹配值”。 + +例如,我们可以有以下键: + +```scala +object Name extends Key { type Value = String } +object Age extends Key { type Value = Int } +``` + +以下对方法 `get` 的调用现在将键入检查: + +```scala +val db: DB = ... +val res1: Option[String] = db.get(Name) +val res2: Option[Int] = db.get(Age) +``` + +调用方法 `db.get(Name)` 返回一个 `Option[String]` 类型的值,而调用 `db.get(Age)` 返回一个 `Option[Int]` 类型的值。 +返回类型_依赖_于传递给 `get` 的参数的具体类型---因此名称为_依赖类型_。 + +## 依赖函数类型 + +如上所示,Scala 2 已经支持依赖方法类型。 +但是,创建 `DB` 类型的值非常麻烦: + +```scala +// a user of a DB +def user(db: DB): Unit = + db.get(Name) ... db.get(Age) + +// creating an instance of the DB and passing it to `user` +user(new DB { + def get(k: Key): Option[k.Value] = ... // implementation of DB +}) +``` + +我们需要手动创建一个匿名的 `DB` 内部类,实现 `get` 方法。 +对于依赖于创建许多不同的 `DB` 实例的代码,这是非常乏味的。 + + `DB` 只有一个抽象方法 `get` 。 +如果我们可以使用 lambda 语法,那不是很好吗? + +```scala +user { k => + ... // implementation of DB +``` + +事实上,现在这在 Scala 3 中是可能的!我们可以将 `DB` 定义为_依赖函数类型_: + +```scala +type DB = (k: Key) => Option[k.Value] +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// A dependent function type +``` + +鉴于 `DB` 的这个定义,上面对 `user` 类型的调用按原样检查。 + +您可以在 [参考文档][ref] 中阅读有关依赖函数类型内部结构的更多信息。 + +## 案例研究:数值表达式 + +假设我们要定义一个抽象数字内部表示的模块。 +例如,这对于实现用于自动派生的库很有用。 + +我们首先为数字定义我们的模块: + +```scala +trait Nums: + // the type of numbers is left abstract + type Num + + // some operations on numbers + def lit(d: Double): Num + def add(l: Num, r: Num): Num + def mul(l: Num, r: Num): Num +``` + +> 我们省略了 `Nums` 的具体实现,但作为练习,您可以通过分配 `type Num = Double` 来实现 `Nums` 并相应地实现方法。 + +使用我们的数字抽象的程序现在具有以下类型: + +```scala +type Prog = (n: Nums) => n.Num => n.Num + +val ex: Prog = nums => x => nums.add(nums.lit(0.8), x) +``` + +计算诸如 `ex` 之类的程序的导数的函数的类型是: + +```scala +def derivative(input: Prog): Double +``` + +鉴于依赖函数类型的便利,用不同的程序调用这个函数非常方便: + +```scala +derivative { nums => x => x } +derivative { nums => x => nums.add(nums.lit(0.8), x) } +// ... +``` + +回想一下,上面编码中的相同程序将是: + +```scala +derivative(new Prog { + def apply(nums: Nums)(x: nums.Num): nums.Num = x +}) +derivative(new Prog { + def apply(nums: Nums)(x: nums.Num): nums.Num = nums.add(nums.lit(0.8), x) +}) +// ... +``` + +#### 上下文组合函数 + +扩展方法、[上下文函数][ctx-fun]和依赖函数的组合为库设计者提供了强大的工具。 +例如,我们可以从上面优化我们的库,如下所示: + +```scala +trait NumsDSL extends Nums: + extension (x: Num) + def +(y: Num) = add(x, y) + def *(y: Num) = mul(x, y) + +def const(d: Double)(using n: Nums): n.Num = n.lit(d) + +type Prog = (n: NumsDSL) ?=> n.Num => n.Num +// ^^^ +// prog is now a context function that implicitly +// assumes a NumsDSL in the calling context + +def derivative(input: Prog): Double = ... + +// notice how we do not need to mention Nums in the examples below? +derivative { x => const(1.0) + x } +derivative { x => x * x + const(2.0) } +// ... +``` + + +[ref]: {{ site.scala3ref }}/new-types/dependent-function-types.html +[ctx-fun]: {{ site.scala3ref }}/contextual/context-functions.html diff --git a/_zh-cn/overviews/scala3-book/types-generics.md b/_zh-cn/overviews/scala3-book/types-generics.md new file mode 100644 index 0000000000..7871b4c70e --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-generics.md @@ -0,0 +1,46 @@ +--- +title: 泛型 +type: section +description: This section introduces and demonstrates generics in Scala 3. +num: 49 +previous-page: types-inferred +next-page: types-intersection +--- + + +泛型类(或 traits)把在方括号 `[...]` 中的类型作为_参数_进行调用。 +Scala 约定是使用单个字母(如 `A`)来命名这些类型参数。 +然后当需要时,该类型可以在类中用于方法实例参数,或返回类型: + +```scala +// here we declare the type parameter A +// v +class Stack[A]: + private var elements: List[A] = Nil + // ^ + // Here we refer to the type parameter + // v + def push(x: A): Unit = { elements = elements.prepended(x) } + def peek: A = elements.head + def pop(): A = + val currentTop = peek + elements = elements.tail + currentTop +``` + +`Stack` 类的这个实现采用任何类型作为参数。 +泛型的美妙之处在于您现在可以创建一个 `Stack[Int]`、`Stack[String]` 等,允许您将 `Stack` 的实现重复用于任意元素类型。 + +这是创建和使用 `Stack[Int]` 的方式: + +``` +val stack = Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop()) // prints 2 +println(stack.pop()) // prints 1 +``` + +> 有关如何用泛型类型表达可变的详细信息,请参阅[可变(Variance)部分][variance]。 + +[variance]: {% link _overviews/scala3-book/types-variance.md %} diff --git a/_zh-cn/overviews/scala3-book/types-inferred.md b/_zh-cn/overviews/scala3-book/types-inferred.md new file mode 100644 index 0000000000..645af43dbc --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-inferred.md @@ -0,0 +1,40 @@ +--- +title: 推断类型 +type: section +description: This section introduces and demonstrates inferred types in Scala 3 +num: 48 +previous-page: types-introduction +next-page: types-generics +--- + + +与其他静态类型编程语言一样,在 Scala 中,您可以在创建新变量时_声明_类型: + +```scala +val x: Int = 1 +val y: Double = 1 +``` + +在这些示例中,类型分别_明确地_声明为 `Int` 和 `Double` 。 +但是,在 Scala 中,您通常不必在定义值绑定器时声明类型: + +```scala +val a = 1 +val b = List(1, 2, 3) +val m = Map(1 -> "one", 2 -> "two") +``` + +当你这样做时,Scala _推断_类型,如下面的 REPL 交互所示: + +```scala +scala> val a = 1 +val a: Int = 1 + +scala> val b = List(1, 2, 3) +val b: List[Int] = List(1, 2, 3) + +scala> val m = Map(1 -> "one", 2 -> "two") +val m: Map[Int, String] = Map(1 -> one, 2 -> two) +``` + +事实上,大多数变量都是这样定义的,而 Scala 自动推断类型的能力是使它_感觉_像一种动态类型语言的一个特性。 diff --git a/_zh-cn/overviews/scala3-book/types-intersection.md b/_zh-cn/overviews/scala3-book/types-intersection.md new file mode 100644 index 0000000000..d716d1b00e --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-intersection.md @@ -0,0 +1,43 @@ +--- +title: 交集类型 +type: section +description: This section introduces and demonstrates intersection types in Scala 3. +num: 50 +previous-page: types-generics +next-page: types-union +--- + + +用于类型,`&` 运算符创建一个所谓的_交集类型_。 +`A & B` 类型表示同时是 `A` 类型和 `B` 类型**两者**的值。 +例如,以下示例使用交集类型 `Resettable & Growable[String]`: + +```scala +trait Resettable: + def reset(): Unit + +trait Growable[A]: + def add(a: A): Unit + +def f(x: Resettable & Growable[String]): Unit = + x.reset() + x.add("first") +``` + +在本例中的方法 `f` 中,参数 `x` 必须*同时*既是 `Resettable` 也是 `Growable[String]`。 + +交集类型 `A & B` 的_成员_既有 `A` 的所有成员,也有 `B` 的所有成员。 +因此,如图所示,`Resettable & Growable[String]` 具有成员方法 `reset` 和 `add`。 + +交集类型可用于_结构性_地描述需求。 +也就是说,在我们的示例 `f` 中,我们直接表示只要 `x` 是 `Resettable` 和 `Growable` 的子类型的任意值, 我们就感到满意。 +我们**不**需要创建一个_通用_的辅助 trait,如下所示: + +```scala +trait Both[A] extends Resettable, Growable[A] +def f(x: Both[String]): Unit +``` + +定义 `f` 的两种选择之间有一个重要区别:虽然两者都允许使用 `Both` 的实例调用 `f`,但只有前者允许传递属于 `Resettable` 和 `Growable[String]` 子类型的实例,后者 `Both[String]` _不允许_。 + +> 请注意,`&` 是_可交换的_:`A & B` 与 `B & A` 的类型相同。 diff --git a/_zh-cn/overviews/scala3-book/types-introduction.md b/_zh-cn/overviews/scala3-book/types-introduction.md new file mode 100644 index 0000000000..ee8eeba7ea --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-introduction.md @@ -0,0 +1,47 @@ +--- +title: 类型和类型系统 +type: chapter +description: This chapter provides an introduction to Scala 3 types and the type system. +num: 47 +previous-page: fp-summary +next-page: types-inferred +--- + + +Scala 是一种独特的语言,因为它是静态类型的,但通常_感觉_它灵活和动态。 +例如,由于类型推断,您可以编写这样的代码而无需显式指定变量类型: + +```scala +val a = 1 +val b = 2.0 +val c = "Hi!" +``` + +这使代码感觉是动态类型的。 +并且由于新特性,例如 Scala 3 中的 [联合类型][union-types],您还可以编写如下代码,非常简洁地表达出期望哪些值作为参数,哪些值作为返回的类型: + +```scala +def isTruthy(a: Boolean | Int | String): Boolean = ??? +def dogCatOrWhatever(): Dog | Plant | Car | Sun = ??? +``` + +正如例子所暗示的,当使用联合类型时,这些类型不必共享一个公共层次结构,您仍然可以接受它们作为参数或从方法中返回它们。 + +如果您是应用程序开发人员,您将每天使用类型推断和每周使用泛型等功能。 +当您阅读 Scaladoc 中的类和方法时,您还需要对_可变的(variance)_有所了解。 +希望您会发现使用类型可以相当简单,而且使用类型可以为库开发人员提供了很多表达能力、灵活性和控制力。 + +## 类型的好处 + +静态类型的编程语言提供了许多好处,包括: + +- 帮助提供强大的 IDE 支持 +- 在编译时消除许多类的潜在错误 +- 协助重构 +- 提供强大的文档,因为它经过类型检查,所以不会过时 + +## Scala 类型系统的特性介绍 + +鉴于此简要介绍,以下部分将概述 Scala 类型系统的特性。 + +[union-types]: {% link _overviews/scala3-book/types-union.md %} diff --git a/_zh-cn/overviews/scala3-book/types-opaque-types.md b/_zh-cn/overviews/scala3-book/types-opaque-types.md new file mode 100644 index 0000000000..bd66bb4f95 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-opaque-types.md @@ -0,0 +1,154 @@ +--- +title: 不透明类型 +type: section +description: This section introduces and demonstrates opaque types in Scala 3. +num: 54 +previous-page: types-variance +next-page: types-structural +--- + + +Scala 3 _不透明类型别名_提供没有任何**开销**的类型抽象。 + +## 抽象开销 + +假设我们要定义一个提供数字算术运算的模块,这些数字由它们的对数表示。 +当涉及的数值非常大或接近于零时,使用对数有利于提高精度。 + +把“常规”双精度值与存储为对数的值区分开来很重要,我们引入了一个类 `Logarithm`: + +```scala +class Logarithm(protected val underlying: Double): + def toDouble: Double = math.exp(underlying) + def + (that: Logarithm): Logarithm = + // here we use the apply method on the companion + Logarithm(this.toDouble + that.toDouble) + def * (that: Logarithm): Logarithm = + new Logarithm(this.underlying + that.underlying) + +object Logarithm: + def apply(d: Double): Logarithm = new Logarithm(math.log(d)) +``` + +伴生对象上的 apply 方法让我们可以创建 `Logarithm` 类型的值,我们可用如下方式使用: + +```scala +val l2 = Logarithm(2.0) +val l3 = Logarithm(3.0) +println((l2 * l3).toDouble) // prints 6.0 +println((l2 + l3).toDouble) // prints 4.999... +``` + +虽然 `Logarithm` 类为以这种特殊对数形式存储的 `Double` 值提供了一个很好的抽象,但它带来了严重的性能开销:对于每一个数学运算,我们需要提取基础值,然后将其再次包装在一个 `Logarithm` 的新实例中。 + +## 模块抽象 + +让我们考虑另一种实现相同库的方法。 +这次我们没有将 `Logarithm` 定义为一个类,而是使用_类型别名_来定义它。 +首先,我们定义模块的抽象接口: + +```scala +trait Logarithms: + + type Logarithm + + // operations on Logarithm + def add(x: Logarithm, y: Logarithm): Logarithm + def mul(x: Logarithm, y: Logarithm): Logarithm + + // functions to convert between Double and Logarithm + def make(d: Double): Logarithm + def extract(x: Logarithm): Double + + // extension methods to use `add` and `mul` as "methods" on Logarithm + extension (x: Logarithm) + def toDouble: Double = extract(x) + def + (y: Logarithm): Logarithm = add(x, y) + def * (y: Logarithm): Logarithm = mul(x, y) +``` + +现在,让我们通过说类型 `Logarithm` 等于 `Double` 来实现这个抽象接口: + +```scala +object LogarithmsImpl extends Logarithms: + + type Logarithm = Double + + // operations on Logarithm + def add(x: Logarithm, y: Logarithm): Logarithm = make(x.toDouble + y.toDouble) + def mul(x: Logarithm, y: Logarithm): Logarithm = x + y + + // functions to convert between Double and Logarithm + def make(d: Double): Logarithm = math.log(d) + def extract(x: Logarithm): Double = math.exp(x) +``` + +在 `LogarithmsImpl` 的实现中,等式 `Logarithm = Double` 允许我们实现各种方法。 + +#### 泄漏抽象 + +但是,这种抽象有点泄漏。 +我们必须确保_只_针对抽象接口 `Logarithms` 进行编程,并且永远不要直接使用 `LogarithmsImpl`。 +直接使用 `LogarithmsImpl` 会使等式 `Logarithm = Double` 对用户可见,用户可能会意外使用 `Double`,而实际上是需要 对数双精度。 +例如: + +```scala +import LogarithmsImpl.* +val l: Logarithm = make(1.0) +val d: Double = l // type checks AND leaks the equality! +``` + +必须将模块分离为抽象接口和实现可能很有用,但只为了隐藏 `Logarithm` 的实现细节,就需要付出很多努力。 +针对抽象模块 `Logarithm` 进行编程可能非常乏味,并且通常需要使用像路径依赖类型这样的高级特性,如下例所示: + +```scala +def someComputation(L: Logarithms)(init: L.Logarithm): L.Logarithm = ... +``` + +#### 装箱的开销 + +类型抽象,例如 `type Logarithm` [抹去](https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#type-erasure) 到它们的界限(在我们的例子中是 `Any`)。 +也就是说,虽然我们不需要手动包装和解包 `Double` 值,但仍然会有一些与装箱原始类型 `Double` 相关的装箱开销。 + +## 不透明类型 + +我们可以简单地使用 Scala 3 中的不透明类型来实现类似的效果,而不是手动将我们的 `Logarithms` 组件拆分为抽象部分和具体实现: + +```scala +object Logarithms: +//vvvvvv this is the important difference! + opaque type Logarithm = Double + + object Logarithm: + def apply(d: Double): Logarithm = math.log(d) + + extension (x: Logarithm) + def toDouble: Double = math.exp(x) + def + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y)) + def * (y: Logarithm): Logarithm = x + y +``` + +`Logarithm` 与 `Double` 相同的事实仅在定义 `Logarithm` 的范围内已知,在上面的示例中对应于对象 `Logarithms`。 +类型相等 `Logarithm = Double` 可用于实现方法(如 `*` 和 `toDouble`)。 + +然而,在模块之外, `Logarithm` 类型是完全封装的,或者说是“不透明的”。 对于 `Logarithm` 的用户来说,不可能发现 `Logarithm` 实际上是作为 `Double` 实现的: + +```scala +import Logarithms.* +val l2 = Logarithm(2.0) +val l3 = Logarithm(3.0) +println((l2 * l3).toDouble) // prints 6.0 +println((l2 + l3).toDouble) // prints 4.999... + +val d: Double = l2 // ERROR: Found Logarithm required Double +``` + +尽管我们抽象了 `Logarithm`,但抽象是免费的: +由于只有一种实现,在运行时对于像 `Double` 这样的原始类型将_没有装箱开销_。 + +### 不透明类型总结 + +不透明类型提供了对实现细节的合理抽象,而不会增加性能开销。 +如上图所示,不透明类型使用起来很方便,并且与 [扩展方法][extension] 功能很好地集成在一起。 + +[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %} diff --git a/_zh-cn/overviews/scala3-book/types-others.md b/_zh-cn/overviews/scala3-book/types-others.md new file mode 100644 index 0000000000..fb1f979676 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-others.md @@ -0,0 +1,24 @@ +--- +title: 其他类型 +type: section +description: This section mentions other advanced types in Scala 3. +num: 57 +previous-page: types-dependent-function +next-page: ca-contextual-abstractions-intro +--- + + +Scala还有其他几种高级类型,本书中没有介绍,包括: + +- 类型 lambdas +- 匹配类型 +- 存在类型 +- 高等类型 +- 单例类型 +- 精简类型 +- 种类多态性 + +有关这些类型的更多详细信息,请参阅[参考文档][reference]。 + + +[reference]: {{ site.scala3ref }}/overview.html diff --git a/_zh-cn/overviews/scala3-book/types-structural.md b/_zh-cn/overviews/scala3-book/types-structural.md new file mode 100644 index 0000000000..049c20ae77 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-structural.md @@ -0,0 +1,104 @@ +--- +title: 构造类型 +type: section +description: This section introduces and demonstrates structural types in Scala 3. +num: 55 +previous-page: types-opaque-types +next-page: types-dependent-function +--- + + +{% comment %} +NOTE: It would be nice to simplify this more. +{% endcomment %} + + +一些用例,例如建模数据库访问,在静态类型语言中比在动态类型语言中更尴尬。 +使用动态类型语言,很自然地将行建模为记录或对象,并使用简单的点表示法选择条目,例如 `row.columnName`。 + +要在静态类型语言中获得相同的体验,需要为数据库操作产生的每个可能的行定义一个类——包括连接和投影产生的行——并设置一个方案以在行和代表它的类之间进行映射。 + +这需要大量样板文件,这导致开发人员将静态类型的优势换成更简单的方案,其中列名表示为字符串并传递给其他运算符,例如 `row.select("columnName")`。 +这种方法即便放弃了静态类型的优点,也仍然不如动态类型的版本自然。 + +在您希望在动态上下文中支持简单的点表示法而又不失静态类型优势的情况下,构造类型会有所帮助。 +它们允许开发人员使用点表示法并配置应如何解析字段和方法。 + +## 例子 + +这是一个构造类型 `Person` 的示例: + +```scala +class Record(elems: (String, Any)*) extends Selectable: + private val fields = elems.toMap + def selectDynamic(name: String): Any = fields(name) + +type Person = Record { + val name: String + val age: Int +} +``` + +`Person` 类型在其父类型 `Record` 中添加了一个_精细的改进_,它定义了 `name` 和 `age` 字段。 +我们精细的改进是_构造的_,因为 `name` 和 `age` 没有在父类型中定义。 +但是它们仍然作为 `Person` 类的成员存在。 +例如,以下程序将打印 `"Emma is 42 years old."`: + +```scala +val person = Record( + "name" -> "Emma", + "age" -> 42 +).asInstanceOf[Person] + +println(s"${person.name} is ${person.age} years old.") +``` + +本例中的父类型 `Record` 是一个通用类,可以在其 `elems` 参数中表示任意记录。 +该参数是一个序列,该序列的元素是 `String` 类型的标签和 `Any` 类型的值组成的对。 +当您将 `Person` 创建为 `Record` 时,您必须使用类型转换断言该记录定义了正确类型的正确字段。 +`Record` 本身的类型太弱了,所以编译器在没有用户帮助的情况下无法知道这一点。 +实际上,构造类型与其底层通用表示之间的连接很可能由数据库层完成,因此最终用户没必要关注。 + +`Record` 扩展了标记 trait `scala.Selectable` 并定义了一个方法 `selectDynamic`,它将字段名称映射到其值。 +通过调用此方法来选择构造类型成员。 +Scala 编译器把选择 `person.name` 和 `person.age` 翻译成: + +```scala +person.selectDynamic("name").asInstanceOf[String] +person.selectDynamic("age").asInstanceOf[Int] +``` + +## 第二个例子 + +为了强化您刚刚看到的内容,这里有另一个名为 `Book` 的构造类型,它表示您可能从数据库中读取的一本书: + +```scala +type Book = Record { + val title: String + val author: String + val year: Int + val rating: Double +} +``` + +与 `Person` 一样,这是创建 `Book` 实例的方式: + +```scala +val book = Record( + "title" -> "The Catcher in the Rye", + "author" -> "J. D. Salinger", + "year" -> 1951, + "rating" -> 4.5 +).asInstanceOf[Book] +``` + +## 可选类 + +除了 `selectDynamic` 之外,`Selectable`类有时还会定义 `applyDynamic` 方法。 +然后可以使用它来翻译是函数调用的结构成员。 +因此,如果 `a` 是 `Selectable` 的一个实例,则像 `a.f(b, c)` 这样的结构调用将转换为: + +```scala +a.applyDynamic("f")(b, c) +``` + diff --git a/_zh-cn/overviews/scala3-book/types-type-classes.md b/_zh-cn/overviews/scala3-book/types-type-classes.md new file mode 100644 index 0000000000..f0391f3806 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-type-classes.md @@ -0,0 +1,52 @@ +--- +title: Type Classes +type: section +description: This section introduces type classes in Scala 3. +num: 60 +previous-page: ca-given-using-clauses +next-page: ca-context-bounds +--- + +A _type class_ is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. +If you are coming from Java, you can think of type classes as something like [`java.util.Comparator[T]`][comparator]. + +> The paper [“Type Classes as Objects and Implicits”][typeclasses-paper] (2010) by Oliveira et al. discusses the basic ideas behind type classes in Scala. +> Even though the paper uses an older version of Scala, the ideas still hold to the current day. + +This style of programming is useful in multiple use-cases, for example: + +- Expressing how a type you don’t own---such as from the standard library or a third-party library---conforms to such behavior +- Adding behavior to multiple types without introducing sub-typing relationships between those types (i.e., one `extends` another) + +In Scala 3, _type classes_ are just _traits_ with one or more type parameters, like the following: +``` +trait Show[A]: + def show(a: A): String +``` +Instances of `Show` for a particular type `A` witness that we can show (i.e., produce a text representation of) an instance of type `A`. +For example, let’s look at the following `Show` instance for `Int` values: + +```scala +class ShowInt extends Show[Int]: + def show(a: Int) = s"The number is ${a}!" +``` +We can write methods that work on arbitrary types `A` _constrained_ by `Show` as follows: + +```scala +def toHtml[A](a: A)(showA: Show[A]): String = + "

" + showA.show(a) + "

" +``` +That is, `toHtml` can be called with arbitrary `A` _as long_ as you can also provide an instance of `Show[A]`. +For example, we can call it like: +```scala +toHtml(42)(ShowInt()) +// results in "

The number is 42!

" +``` + +#### Automatically passing type class instances +Since type classes are a very important way to structure software, Scala 3 offers additional features that make working with them very convenient. +We discuss these additional features (which fall into the category of *Contextual Abstractions*) in a [later chapter][typeclasses-chapter] of this book. + +[typeclasses-paper]: https://infoscience.epfl.ch/record/150280/files/TypeClasses.pdf +[typeclasses-chapter]: {% link _overviews/scala3-book/ca-type-classes.md %} +[comparator]: https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html diff --git a/_zh-cn/overviews/scala3-book/types-union.md b/_zh-cn/overviews/scala3-book/types-union.md new file mode 100644 index 0000000000..18a8a5f8a5 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-union.md @@ -0,0 +1,99 @@ +--- +title: 联合类型 +type: section +description: This section introduces and demonstrates union types in Scala 3. +num: 51 +previous-page: types-intersection +next-page: types-adts-gadts +--- + + +用于类型,`|` 操作符创建一个所谓的_联合类型_。 +类型 `A | B` 表示**要么是** `A` 类型的值,**要么是** `B` 类型的值。 + +在下面的例子中,`help` 方法接受一个名为 `id` 的联合类型 `Username | Password`,可以是 `Useername` 或 `Password`: + +```scala +case class Username(name: String) +case class Password(hash: Hash) + +def help(id: Username | Password) = + val user = id match + case Username(name) => lookupName(name) + case Password(hash) => lookupPassword(hash) + // more code here ... +``` + +我们通过使用模式匹配区分二者,从而实现 `help` 方法。 + +此代码是一种灵活且类型安全的解决方案。 +如果您尝试传入`Useername` 或 `Password` 以外的类型,编译器会将其标记为错误: + +```scala +help("hi") // error: Found: ("hi" : String) + // Required: Username | Password +``` + +如果您尝试将 `case` 添加到与 `Username` 或 `Password` 类型不匹配的 `match` 表达式中,也会出现错误: + +```scala +case 1.0 => ??? // ERROR: this line won’t compile +``` + +### 联合类型的替代方案 + +如图所示,联合类型可用于替代几种不同的类型,而不要求这些类型是定制类层次结构的一部分,也不需要显式包装。 + +#### 预先规划类层次结构 + +其他语言需要预先规划类层次结构,如下例所示: + +```scala +trait UsernameOrPassword +case class Username(name: String) extends UsernameOrPassword +case class Password(hash: Hash) extends UsernameOrPassword +def help(id: UsernameOrPassword) = ... +``` + +预先计划不能很好地扩展,例如,API 用户的需求可能无法预见。 +此外,使用诸如 `UsernameOrPassword` 之类的标记 trait 使类型层次结构混乱也会使代码更难阅读。 + +#### 标记联合 + +另一种选择是定义一个单独的枚举类型,如: + +```scala +enum UsernameOrPassword: + case IsUsername(u: Username) + case IsPassword(p: Password) +``` + +枚举 `UsernameOrPassword` 表示 `Username` 和 `Password` 的 _标记_联合。 +但是,这种联合建模方式需要_显式包装和展开_,例如,`Username` **不是** `UsernameOrPassword` 的子类型。 + +### 联合类型推断 + +_仅当_明确给出这种类型时,编译器才会将联合类型分配给表达式。 +例如,给定这些值: + +```scala +val name = Username("Eve") // name: Username = Username(Eve) +val password = Password(123) // password: Password = Password(123) +``` + +这个 REPL 示例展示了在将变量绑定到 `if`/`else` 表达式的结果时如何使用联合类型: + +```` +scala> val a = if (true) name else password +val a: Object = Username(Eve) + +scala> val b: Password | Username = if (true) name else password +val b: Password | Username = Username(Eve) +```` + +`a` 的类型是 `Object`,它是 `Username` 和 `Password` 的超类型,但不是二者*最小*的超类型 `Password | Username`。 +如果你想要最小的超类型,你必须明确地给出它,就像对 `b` 所做的那样。 + +> 联合类型是交集类型的对偶。 +> 和具有交集类型的 `&` 一样,`|` 也是可交换的:`A | B` 与 `B | A` 是同一类型。 + diff --git a/_zh-cn/overviews/scala3-book/types-variance.md b/_zh-cn/overviews/scala3-book/types-variance.md new file mode 100644 index 0000000000..6f00f6879d --- /dev/null +++ b/_zh-cn/overviews/scala3-book/types-variance.md @@ -0,0 +1,164 @@ +--- +title: 变型 +type: section +description: This section introduces and demonstrates variance in Scala 3. +num: 53 +previous-page: types-adts-gadts +next-page: types-opaque-types +--- + + +类型参数_变型_控制参数化类型(如类或 traits)的子类型。 + +为了解释变型,让我们假设以下类型定义: + +```scala +trait Item { def productNumber: String } +trait Buyable extends Item { def price: Int } +trait Book extends Buyable { def isbn: String } +``` + +我们还假设以下参数化类型: + +```scala +// an example of an invariant type +trait Pipeline[T]: + def process(t: T): T + +// an example of a covariant type +trait Producer[+T]: + def make: T + +// an example of a contravariant type +trait Consumer[-T]: + def take(t: T): Unit +``` + +一般来说,方差有三种模式: + +- **不变的**---默认值,写成 `Pipeline[T]` +- **协变**---用`+`注释,例如 `Producer[+T]` +- **逆变**---用`-`注释,如 `Consumer[-T]` + +我们现在将详细介绍此注释的含义以及我们使用它的原因。 + +### 不变类型 + +默认情况下,像 `Pipeline` 这样的类型在它们的类型参数中是不变的(本例中是 `T`)。 +这意味着像 `Pipeline[Item]`、`Pipeline[Buyable]` 和 `Pipeline[Book]` 这样的类型彼此之间_没有子类型关系_。 + +理所当然地!假设以下方法使用两个类型为`Pipeline[Buyable]` 的值,并根据价格将其参数 `b` 传递给其中一个: + +```scala +def oneOf( + p1: Pipeline[Buyable], + p2: Pipeline[Buyable], + b: Buyable +): Buyable = + val b1 = p1.process(b) + val b2 = p2.process(b) + if b1.price < b2.price then b1 else b2 +``` + +现在,回想一下,我们的类型之间存在以下_子类型关系_: + +```scala +Book <: Buyable <: Item +``` + +我们不能将 `Pipeline[Book]` 传递给 `oneOf` 方法,因为在其实现中,我们调用的 `p1` 和 `p2` 是 `Buyable` 类型的值。 +`Pipeline[Book]` 需要的是 `Book`,这可能会导致运行时错误。 + +我们不能传递一个 `Pipeline[Item]` 因为在它上面调用 `process` 只会保证返回一个 `Item`;但是,我们应该返回一个 `Buyable` 。 + +#### 为什么是不变的? + +事实上,`Pipeline` 类型需要是不变的,因为它使用它的类型参数 `T` _既_作为参数类型,_又_作为返回类型。 +出于同样的原因,Scala 集合库中的某些类型——例如 `Array` 或 `Set` —— 也是_不变的_。 + +### 协变类型 + +与不变的 `Pipeline` 相比,`Producer` 类型通过在类型参数前面加上 `+` 前缀被标记为 **协变**。 +这是有效的,因为类型参数仅用于_返回的位置_。 + +将其标记为协变意味着当需要 `Producer[Buyable]` 时,我们可以传递(或返回)一个 `Producer[Book]`。 +事实上,这是合理的。 `Producer[Buyable].make` 的类型只承诺_返回_ `Buyable`。 +作为 `make` 的调用者,我们乐意接受作为 `Buyable` 的子类型的 `Book` 类型,---也就是说,它_至少_是一个 `Buyable`。 + +以下示例说明了这一点,其中函数 `makeTwo` 需要一个 `Producer[Buyable]`: + +```scala +def makeTwo(p: Producer[Buyable]): Int = + p.make.price + p.make.price +``` + +通过书籍制作人是完全可以的: + +``` +val bookProducer: Producer[Book] = ??? +makeTwo(bookProducer) +``` + +在 `makeTwo` 中调用 `price` 对书籍仍然有效。 + +#### 不可变容器的协变类型 + +在处理不可变容器时,您会经常遇到协变类型,例如可以在标准库中找到的那些(例如 `List`、`Seq`、`Vector` 等)。 + +例如,`List` 和 `Vector` 大致定义为: + +```scala +class List[+A] ... +class Vector[+A] ... +``` + +这样,您可以在需要 `List[Buyable]` 的地方使用 `List[Book]`。 +这在直觉上也是有道理的:如果您期望收藏可以购买的东西,那么给您收藏书籍应该没问题。 +在我们的示例中,它们有一个额外的 ISBN 方法,但您可以随意忽略这些额外的功能。 + +### 逆变类型 + +与标记为协变的类型 `Producer` 相比,类型 `Consumer` 通过在类型参数前加上 `-` 来标记为**逆变**。 +这是有效的,因为类型参数仅用于_参数位置_。 + +将其标记为逆变意味着如果我们想要 `Consumer[Buyable]` 时,可以传递(或返回) `Consumer[Item]`。 +也就是说,我们有子类型关系`Consumer[Item] <: Consumer[Buyable]`。 +请记住,对于类型 `Producer`,情况正好相反,我们有 `Producer[Buyable] <: Producer[Item]`。 + +事实上,这是合理的。 `Consumer[Item].take` 方法接受一个 `Item`。 +作为 `take` 的调用者,我们还可以提供 `Buyable`,它会被 `Consumer[Item]` 愉快地接受,因为 `Buyable` 是 `Item` 的一个子类型——也就是说,它_至少_是 `Item` 。 + +#### 消费者的逆变类型 + +逆变类型比协变类型少得多。 +在我们的示例中,您可以将它们视为“消费者”。你可能来的最重要的类型标记为逆变的 cross 是函数之一: + +```scala +trait Function[-A, +B]: + def apply(a: A): B +``` + +它的参数类型 `A` 被标记为逆变的 `A` ——它消费 `A` 类型的值。 +相反,它的结果类型 `B` 被标记为协变——它产生 `B` 类型的值。 + +以下是一些示例,这些示例说明了由函数上可变注释引起的子类型关系: + +```scala +val f: Function[Buyable, Buyable] = b => b + +// OK to return a Buyable where a Item is expected +val g: Function[Buyable, Item] = f + +// OK to provide a Book where a Buyable is expected +val h: Function[Book, Buyable] = f +``` + +## 概括 + +在本节中,我们遇到了三种不同的方差: + +- **生产者**通常是协变的,并用 `+` 标记它们的类型参数。 + 这也适用于不可变集合。 +- **消费者**通常是逆变的,并用 `-` 标记他们的类型参数。 +- **既是**生产者**又**是消费者的类型必须是不变的,并且不需要在其类型参数上进行任何标记。 + 像 `Array` 这样的可变集合就属于这一类。 From a98a6872db819e7b67eb3596a3238df2c5ace355 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Wed, 8 Jun 2022 08:41:40 +0800 Subject: [PATCH 22/53] add all ca-* files --- .../scala3-book/ca-context-bounds.md | 48 +++++ .../ca-contextual-abstractions-intro.md | 87 ++++++++ .../scala3-book/ca-extension-methods.md | 83 ++++++++ .../overviews/scala3-book/ca-given-imports.md | 48 +++++ .../scala3-book/ca-given-using-clauses.md | 97 +++++++++ .../scala3-book/ca-implicit-conversions.md | 47 +++++ .../scala3-book/ca-multiversal-equality.md | 198 ++++++++++++++++++ _zh-cn/overviews/scala3-book/ca-summary.md | 31 +++ .../overviews/scala3-book/ca-type-classes.md | 108 ++++++++++ 9 files changed, 747 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/ca-context-bounds.md create mode 100644 _zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md create mode 100644 _zh-cn/overviews/scala3-book/ca-extension-methods.md create mode 100644 _zh-cn/overviews/scala3-book/ca-given-imports.md create mode 100644 _zh-cn/overviews/scala3-book/ca-given-using-clauses.md create mode 100644 _zh-cn/overviews/scala3-book/ca-implicit-conversions.md create mode 100644 _zh-cn/overviews/scala3-book/ca-multiversal-equality.md create mode 100644 _zh-cn/overviews/scala3-book/ca-summary.md create mode 100644 _zh-cn/overviews/scala3-book/ca-type-classes.md diff --git a/_zh-cn/overviews/scala3-book/ca-context-bounds.md b/_zh-cn/overviews/scala3-book/ca-context-bounds.md new file mode 100644 index 0000000000..35cad7d6db --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-context-bounds.md @@ -0,0 +1,48 @@ +--- +title: 上下文绑定 +type: section +description: This page demonstrates Context Bounds in Scala 3. +num: 61 +previous-page: types-type-classes +next-page: ca-given-imports +--- + + +{% comment %} +- TODO: define "context parameter" +- TODO: define "synthesized" and "synthesized arguments" +{% endcomment %} + + +在许多情况下,_上下文参数_的名称不必明确提及,因为它仅由编译器在其他上下文参数的合成参数中使用。 +在这种情况下,您不必定义参数名称,只需提供参数类型即可。 + +## 背景 + +例如,这个 `maximum` 方法接受 `Ord` 类型的_上下文参数_,只是将它作为参数传递给 `max`: + +```scala +def maximum[A](xs: List[A])(using ord: Ord[A]): A = + xs.reduceLeft(max(ord)) +``` + +在该代码中,参数名称 `ord` 实际上不是必需的;它可以作为推断参数传递给 `max`,因此您只需声明 `maximum` 使用的类型 `Ord[A]` 而不必给它命名: + +```scala +def maximum[A](xs: List[A])(using Ord[A]): A = + xs.reduceLeft(max) +``` + +## 上下文绑定 + +鉴于此背景,_上下文绑定_是一种简写语法,用于表达“依赖于类型参数的上下文参数”模式。 + +使用上下文绑定,`maximum` 方法可以这样写: + +```scala +def maximum[A: Ord](xs: List[A]): A = xs.reduceLeft(max) +``` + +方法或类的类型参数 `A`,有类似 `:Ord` 的绑定,它表示有 `Ord[A]` 的上下文参数。 + +有关上下文绑定的更多信息,请参阅 Scala 常见问题解答的 [“什么是上下文绑定?”](https://docs.scala-lang.org/tutorials/FAQ/context-bounds.html) 部分。 diff --git a/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md new file mode 100644 index 0000000000..dc14d90c62 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md @@ -0,0 +1,87 @@ +--- +title: Contextual Abstractions +type: chapter +description: This chapter provides an introduction to the Scala 3 concept of Contextual Abstractions. +num: 58 +previous-page: types-others +next-page: ca-given-using-clauses +--- + + +## Background + +Implicits in Scala 2 were a major distinguishing design feature. +They are *the* fundamental way to abstract over context. +They represent a unified paradigm with a great variety of use cases, among them: + +- Implementing type classes +- Establishing context +- Dependency injection +- Expressing capabilities +- Computing new types, and proving relationships between them + +Since then, other languages have followed suit, e.g., Rust’s traits or Swift’s protocol extensions. +Design proposals are also on the table for Kotlin as compile time dependency resolution, for C# as Shapes and Extensions or for F# as Traits. +Implicits are also a common feature of theorem provers such as Coq or Agda. + +Even though these designs use different terminology, they’re all variants of the core idea of *term inference*: +Given a type, the compiler synthesizes a “canonical” term that has that type. + + +## Redesign + +Scala 3 includes a redesign of contextual abstractions in Scala. +While these concepts were gradually “discovered” in Scala 2, they’re now well known and understood, and the redesign takes advantage of that knowledge. + +The design of Scala 3 focuses on **intent** rather than **mechanism**. +Instead of offering one very powerful feature of implicits, Scala 3 offers several use-case oriented features: + +- **Abstracting over contextual information**. + [Using clauses][givens] allow programmers to abstract over information that is available in the calling context and should be passed implicitly. + As an improvement over Scala 2 implicits, using clauses can be specified by type, freeing function signatures from term variable names that are never explicitly referred to. + +- **Providing Type-class instances**. + [Given instances][type-classes] allow programmers to define the _canonical value_ of a certain type. + This makes programming with type-classes more straightforward without leaking implementation details. + +- **Retroactively extending classes**. + In Scala 2, extension methods had to be encoded using implicit conversions or implicit classes. + In contrast, in Scala 3 [extension methods][extension-methods] are now directly built into the language, leading to better error messages and improved type inference. + +- **Viewing one type as another**. + Implicit conversion have been [redesigned][implicit-conversions] from the ground up as instances of a type-class `Conversion`. + +- **Higher-order contextual abstractions**. + The _all-new_ feature of [context functions][contextual-functions] makes contextual abstractions a first-class citizen. + They are an important tool for library authors and allow to express concise domain specific languages. + +- **Actionable feedback from the compiler**. + In case an implicit parameter can not be resolved by the compiler, it now provides you [import suggestions](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html) that may fix the problem. + + +## Benefits + +These changes in Scala 3 achieve a better separation of term inference from the rest of the language: + +- There’s a single way to define givens +- There’s a single way to introduce implicit parameters and arguments +- There’s a separate way to [import givens][given-imports] that does not allow them to hide in a sea of normal imports +- There’s a single way to define an [implicit conversion][implicit-conversions], which is clearly marked as such, and does not require special syntax + +Benefits of these changes include: + +- The new design thus avoids feature interactions and makes the language more consistent +- It makes implicits easier to learn and harder to abuse +- It greatly improves the clarity of the 95% of Scala programs that use implicits +- It has the potential to enable term inference in a principled way that is also accessible and friendly + +This chapter introduces many of these new features in the following sections. + +[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} +[given-imports]: {% link _overviews/scala3-book/ca-given-imports.md %} +[implicit-conversions]: {% link _overviews/scala3-book/ca-implicit-conversions.md %} +[extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[context-bounds]: {% link _overviews/scala3-book/ca-context-bounds.md %} +[type-classes]: {% link _overviews/scala3-book/ca-type-classes.md %} +[equality]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} +[contextual-functions]: {% link _overviews/scala3-book/types-dependent-function.md %} diff --git a/_zh-cn/overviews/scala3-book/ca-extension-methods.md b/_zh-cn/overviews/scala3-book/ca-extension-methods.md new file mode 100644 index 0000000000..b3e8e8b091 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-extension-methods.md @@ -0,0 +1,83 @@ +--- +title: 扩展方法 +type: section +description: This page demonstrates how Extension Methods work in Scala 3. +num: 63 +previous-page: ca-given-imports +next-page: ca-type-classes +--- + + +扩展方法允许您在定义类型后向类型添加方法,即它们允许您向封闭类添加新方法。 +例如,假设其他人创建了一个 `Circle` 类: + +```scala +case class Circle(x: Double, y: Double, radius: Double) +``` + +现在想象一下,你需要一个 `circumference` 方法,但是你不能修改它们的源代码。 +在将术语推理的概念引入编程语言之前,您唯一能做的就是在单独的类或对象中编写一个方法,如下所示: + +```scala +object CircleHelpers: + def circumference(c: Circle): Double = c.radius * math.Pi * 2 +``` + +你可以像这样用该方法: + +```scala +val aCircle = Circle(2, 3, 5) + +// without extension methods +CircleHelpers.circumference(aCircle) +``` + +但是使用扩展方法,您可以创建一个 `circumference` 方法来处理 `Circle` 实例: + +```scala +extension (c: Circle) + def circumference: Double = c.radius * math.Pi * 2 +``` + +在这段代码中: + +- 扩展方法 `circumference` 将添加到 `Circle` 类型里 +- `c: Circle` 语法允许您在扩展方法中引用变量 `c` + +然后在您的代码中使用 `circumference`,就像它最初是在 `Circle` 类中定义的一样: + +```scala +aCircle.circumference +``` + +### 导入扩展方法 + +想象一下,`circumference` 定义在`lib` 包中,你可以通过以下方式导入它 + +```scala +import lib.circumference + +aCircle.circumference +``` + +如果缺少导入,编译器还会通过显示详细的编译错误消息来支持您,如下所示: + +```text +value circumference is not a member of Circle, but could be made available as an extension method. + +The following import might fix the problem: + + import lib.circumference +``` + +## 讨论 + +`extension` 关键字声明您将要在括号中的类型上定义一个或多个扩展方法。 +要在一个类型上定义多个扩展方法,请使用以下语法: + +```scala +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 +``` diff --git a/_zh-cn/overviews/scala3-book/ca-given-imports.md b/_zh-cn/overviews/scala3-book/ca-given-imports.md new file mode 100644 index 0000000000..f8df481645 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-given-imports.md @@ -0,0 +1,48 @@ +--- +title: 给定导入 +type: section +description: This page demonstrates how 'given' import statements work in Scala 3. +num: 62 +previous-page: ca-context-bounds +next-page: ca-extension-methods +--- + + +为了更清楚地说明当前作用域中的给定来自何处,我们使用一种特殊形式的 `import` 语句来导入 `given` 实例。 +此示例中显示了基本形式: + +```scala +object A: + class TC + given tc: TC = ??? + def f(using TC) = ??? + +object B: + import A.* // import all non-given members + import A.given // import the given instance +``` + +在此代码中,对象 `B` 的 `import A.*` 子句导入 `A` 的所有成员*除了* `given` 实例 `tc`。 +相反,第二个导入 `import A.given` *仅*导入 `given` 实例。 +两个 `import` 子句也可以合并为一个: + +```scala +object B: + import A.{given, *} +``` + +## 讨论 + +通配符选择器 `*` 将除给定或扩展之外的所有定义都导入作用域,而 `given` 选择器将所有*给定*定义---包括由扩展而来的定义---导入作用域。 + +这些规则有两个主要优点: + +- 更清楚当前作用域内给定的来源。 + 特别是,在一长串其他通配符导入中无法隐藏导入的给定。 +- 它可以导入所有给定,而无需导入任何其他内容。 + 这很重要,因为给定是可以匿名的,因此通常使用命名导入是不切实际的。 + +“导入给定”语法的更多示例见[打包和导入章节][imports]。 + + +[imports]: {% link _overviews/scala3-book/packaging-imports.md %} diff --git a/_zh-cn/overviews/scala3-book/ca-given-using-clauses.md b/_zh-cn/overviews/scala3-book/ca-given-using-clauses.md new file mode 100644 index 0000000000..704f83bd82 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-given-using-clauses.md @@ -0,0 +1,97 @@ +--- +title: Given Instances and Using Clauses +type: section +description: This page demonstrates how to use 'given' instances and 'using' clauses in Scala 3. +num: 59 +previous-page: ca-contextual-abstractions-intro +next-page: types-type-classes +--- + +Scala 3 offers two important feature for contextual abstraction: + +- **Using Clauses** allow you to specify parameters that, at the call site, can be omitted by the programmer and should be automatically provided by the context. +- **Given Instances** let you define terms that can be used by the Scala compiler to fill in the missing arguments. + +## Using Clauses +When designing a system, often context information like _configuration_ or settings need to be provided to the different components of your system. +One common way to achieve this is by passing the configuration as additional argument to your methods. + +In the following example, we define a case class `Config` to model some website configuration and pass it around in the different methods. +```scala +case class Config(port: Int, baseUrl: String) + +def renderWebsite(path: String, c: Config): String = + "" + renderWidget(List("cart"), c) + "" + +def renderWidget(items: List[String], c: Config): String = ??? + +val config = Config(8080, "docs.scala-lang.org") +renderWebsite("/home", config) +``` +Let us assume that the configuration does not change throughout most of our code base. +Passing `c` to each and every method call (like `renderWidget`) becomes very tedious and makes our program more difficult to read, since we need to ignore the `c` argument. + +#### Using `using` to mark parameters as contextual +In Scala 3, we can mark some of the parameters of our methods as _contextual_. +```scala +def renderWebsite(path: String)(using c: Config): String = + "" + renderWidget(List("cart")) + "" + // ^^^ + // no argument c required anymore + +def renderWidget(items: List[String])(using c: Config): String = ??? +``` +By starting a parameter section with the keyword `using`, we tell the Scala compiler that at the callsite it should automatically find an argument with the correct type. +The Scala compiler thus performs **term inference**. + +In our call to `renderWidget(List("cart"))` the Scala compiler will see that there is a term of type `Config` in scope (the `c`) and automatically provide it to `renderWidget`. +So the program is equivalent to the one above. + +In fact, since we do not need to refer to `c` in our implementation of `renderWebsite` anymore, we can even omit its name in the signature: + +```scala +// no need to come up with a parameter name +// vvvvvvvvvvvvv +def renderWebsite(path: String)(using Config): String = + "" + renderWidget(List("cart")) + "" +``` + +#### Explicitly providing contextual arguments +We have seen how to _abstract_ over contextual parameters and that the Scala compiler can provide arguments automatically for us. +But how can we specify which configuration to use for our call to `renderWebsite`? + +Like we specified our parameter section with `using`, we can also explicitly provide contextual arguments with `using:` + +```scala +renderWebsite("/home")(using config) +``` +Explicitly providing contextual parameters can be useful if we have multiple different values in scope that would make sense and we want to make sure that the correct one is passed to the function. + +For all other cases, as we will see in the next Section, there is also another way to bring contextual values into scope. + +## Given Instances +We have seen that we can explicitly pass arguments as contextual parameters by marking the argument section of the _call_ with `using`. +However, if there is _a single canonical value_ for a particular type, there is another preferred way to make it available to the Scala compiler: by marking it as `given`. + +```scala +val config = Config(8080, "docs.scala-lang.org") +// this is the type that we want to provide the +// canonical value for +// vvvvvv +given Config = config +// ^^^^^^ +// this is the value the Scala compiler will infer +// as argument to contextual parameters of type Config +``` +In the above example we specify that whenever a contextual parameter of type `Config` is omitted in the current scope, the compiler should infer `config` as an argument. + +Having defined a given for `Config`, we can simply call `renderWebsite`: + +```scala +renderWebsite("/home") +// ^^^^^ +// again no argument +``` + +[reference]: {{ site.scala3ref }}/overview.html +[blog-post]: /2020/11/06/explicit-term-inference-in-scala-3.html diff --git a/_zh-cn/overviews/scala3-book/ca-implicit-conversions.md b/_zh-cn/overviews/scala3-book/ca-implicit-conversions.md new file mode 100644 index 0000000000..df51bc2bf3 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-implicit-conversions.md @@ -0,0 +1,47 @@ +--- +title: 隐式转换 +type: section +description: This page demonstrates how to implement Implicit Conversions in Scala 3. +num: 66 +previous-page: ca-multiversal-equality +next-page: ca-summary +--- + +隐式转换由 `scala.Conversion` 类的 `given` 实例定义。 +例如,不考虑可能的转换错误,这段代码定义了从 `String` 到 `Int` 的隐式转换: + +```scala +given Conversion[String, Int] with + def apply(s: String): Int = Integer.parseInt(s) +``` + +使用别名可以更简洁地表示为: + +```scala +given Conversion[String, Int] = Integer.parseInt(_) +``` + +使用这些转换中的任何一种,您现在可以在需要 `Int` 的地方使用 `String`: + +```scala +import scala.language.implicitConversions + +// a method that expects an Int +def plus1(i: Int) = i + 1 + +// pass it a String that converts to an Int +plus1("1") +``` + +> 注意开头的子句 `import scala.language.implicitConversions`, +> 在文件中启用隐式转换。 + +## 讨论 + +Predef 包包含“自动装箱”转换,将原始数字类型映射到 `java.lang.Number` 的子类。 +例如,从 `Int` 到 `java.lang.Integer` 的转换可以定义如下: + +```scala +given int2Integer: Conversion[Int, java.lang.Integer] = + java.lang.Integer.valueOf(_) +``` diff --git a/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md b/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md new file mode 100644 index 0000000000..42c2914042 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md @@ -0,0 +1,198 @@ +--- +title: 多元相等性 +type: section +description: This page demonstrates how to implement Multiversal Equality in Scala 3. +num: 65 +previous-page: ca-type-classes +next-page: ca-implicit-conversions +--- + + +以前,Scala 具有*普遍相等性*:任何类型的两个值都可以使用 `==` 和 `!=` 相互比较。 +这是因为 `==` 和 `!=` 是根据 Java 的 `equals` 方法实现的,该方法还可以比较任何两种引用类型的值。 + +普遍平等很方便,但也很危险,因为它破坏了类型安全。 +例如,假设在一些重构之后,你会得到一个错误的程序,其中值 `y` 的类型为 `S` 而不是正确的类型 `T`: + +```scala +val x = ... // of type T +val y = ... // of type S, but should be T +x == y // typechecks, will always yield false + +``` + +如果 `y` 与其他类型为 `T` 的值进行比较,程序仍然会进行类型检查,因为所有类型的值都可以相互比较。 +但它可能会产生意想不到的结果并在运行时失败。 + +类型安全的编程语言可以做得更好,多重平等是使普遍平等更安全的一种选择方式。 +它使用二进制类型类“CanEqual”来指示两个给定类型的值可以相互比较。 + +## 允许比较类实例 + +默认情况下,在 Scala 3 中,您仍然可以像这样创建相等比较: + +```scala +case class Cat(name: String) +case class Dog(name: String) +val d = Dog("Fido") +val c = Cat("Morris") + +d == c // false, but it compiles +``` + +但是使用 Scala 3,您可以禁用此类比较。 +通过 (a) 导入 `scala.language.strictEquality` 或 (b) 使用 `-language:strictEquality` 编译器标志,此比较不再编译: + +```scala +import scala.language.strictEquality + +val rover = Dog("Rover") +val fido = Dog("Fido") +println(rover == fido) // compiler error + +// compiler error message: +// Values of types Dog and Dog cannot be compared with == or != +``` + +## 启用比较 + +有两种方法可以使用 Scala 3 `CanEqual` 类型类来启用这种比较。 +对于像这样的简单情况,您的类可以*派生* `CanEqual` 类: + +```scala +// Option 1 +case class Dog(name: String) derives CanEqual +``` + +稍后您将看到,当您需要更多的灵活性时,您还可以使用以下语法: + +```scala +// Option 2 +case class Dog(name: String) +given CanEqual[Dog, Dog] = CanEqual.derived +``` + +现在,这两种方法中的任何一种都可以让 `Dog` 实例相互比较。 + +## 一个更真实的例子 + +在一个更真实的示例中,假设您有一家在线书店,并且想要允许或禁止比较实体书、打印的书和有声读物。 +在 Scala 3 中,您首先启用多元平等性,如前面的示例所示: + +```scala +// [1] add this import, or this command line flag: -language:strictEquality +import scala.language.strictEquality +``` + +然后像往常一样创建你的域对象: + +```scala +// [2] create your class hierarchy +trait Book: + def author: String + def title: String + def year: Int + +case class PrintedBook( + author: String, + title: String, + year: Int, + pages: Int +) extends Book + +case class AudioBook( + author: String, + title: String, + year: Int, + lengthInMinutes: Int +) extends Book +``` + +最后,使用 `CanEqual` 定义您想要允许的比较: + +```scala +// [3] create type class instances to define the allowed comparisons. +// allow `PrintedBook == PrintedBook` +// allow `AudioBook == AudioBook` +given CanEqual[PrintedBook, PrintedBook] = CanEqual.derived +given CanEqual[AudioBook, AudioBook] = CanEqual.derived + +// [4a] comparing two printed books works as desired +val p1 = PrintedBook("1984", "George Orwell", 1961, 328) +val p2 = PrintedBook("1984", "George Orwell", 1961, 328) +println(p1 == p2) // true + +// [4b] you can’t compare a printed book and an audiobook +val pBook = PrintedBook("1984", "George Orwell", 1961, 328) +val aBook = AudioBook("1984", "George Orwell", 2006, 682) +println(pBook == aBook) // compiler error +``` + +最后一行代码导致此编译器错误消息: + +```` +Values of types PrintedBook and AudioBook cannot be compared with == or != +```` + +这就是多重​元平等性在编译时捕获非法类型比较的方式。 + +### 启用“PrintedBook == AudioBook” + +这可以按需要工作,但在某些情况下,您可能希望允许将实体书与有声读物进行比较。 +如果需要,请创建以下两个额外的相等比较: + +```scala +// allow `PrintedBook == AudioBook`, and `AudioBook == PrintedBook` +given CanEqual[PrintedBook, AudioBook] = CanEqual.derived +given CanEqual[AudioBook, PrintedBook] = CanEqual.derived +``` + +现在,您可以将实体书与有声书进行比较,而不会出现编译器错误: + +```scala +println(pBook == aBook) // false +println(aBook == pBook) // false +``` + +#### 实施“等于”以使它们真正起作用 + +虽然现在允许进行这些比较,但它们将始终为 `false`,因为它们的 `equals` 方法不知道如何进行这些比较。 +因此,解决方案是重载每个类的 `equals` 方法。 +例如,当您重载 `AudioBook` 的 `equals` 方法时: + +```scala +case class AudioBook( + author: String, + title: String, + year: Int, + lengthInMinutes: Int +) extends Book: + // override to allow AudioBook to be compared to PrintedBook + override def equals(that: Any): Boolean = that match + case a: AudioBook => + if this.author == a.author + && this.title == a.title + && this.year == a.year + && this.lengthInMinutes == a.lengthInMinutes + then true else false + case p: PrintedBook => + if this.author == p.author && this.title == p.title + then true else false + case _ => + false +``` + +您现在可以将 `AudioBook` 与 `PrintedBook` 进行比较: + +```scala +println(aBook == pBook) // true (works because of `equals` in `AudioBook`) +println(pBook == aBook) // false +``` + +目前 `PrintedBook` 书没有 `equals` 方法,所以第二个比较返回 `false`。 +要启用该比较,只需重载 `PrintedBook` 中的 `equals` 方法。 + +您可以在参考文档中找到有关[多元相等性][ref-equal] 的更多信息。 + + +[ref-equal]: {{ site.scala3ref }}/contextual/multiversal-equality.html diff --git a/_zh-cn/overviews/scala3-book/ca-summary.md b/_zh-cn/overviews/scala3-book/ca-summary.md new file mode 100644 index 0000000000..f387d8ed05 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-summary.md @@ -0,0 +1,31 @@ +--- +title: 总结 +type: section +description: This page provides a summary of the Contextual Abstractions lessons. +num: 67 +previous-page: ca-implicit-conversions +next-page: concurrency +--- + + +本章介绍了大多数上下文抽象主题,包括: + +- 给定实例和使用子句 +- 上下文绑定 +- 给定导入 +- 扩展方法 +- 实现类型类 +- 多元相等性 +- 隐式转换 + +这里没有涉及一些更高级的主题,包括: + +- 类型类派生 +- 上下文函数 +- 按名称上下文参数 +- 与 Scala 2 隐式转换 的关系 + +这些主题在 [参考文档][ref] 中有详细讨论。 + + +[ref]: {{ site.scala3ref }}/contextual diff --git a/_zh-cn/overviews/scala3-book/ca-type-classes.md b/_zh-cn/overviews/scala3-book/ca-type-classes.md new file mode 100644 index 0000000000..173361e33f --- /dev/null +++ b/_zh-cn/overviews/scala3-book/ca-type-classes.md @@ -0,0 +1,108 @@ +--- +title: 实现类型类 +type: section +description: This page demonstrates how to create and use type classes in Scala 3. +num: 64 +previous-page: ca-extension-methods +next-page: ca-multiversal-equality +--- + + +_类型类_是一种抽象的参数化类型,它允许您在不使用子类型的情况下向任何封闭数据类型添加新行为。 +这在多用例中很有用,例如: + +- 表达你不拥有的类型——来自标准库或第三方库——如何符合这种行为 +- 为多种类型表达这种行为,而不涉及这些类型之间的子类型关系 + +在 Scala 3 中,类型类只是具有一个或多个参数的 traits,其实现由 `given` 实例提供。 + +## 例子 + +例如,`Show` 是 Haskell 中众所周知的类型类,下面的代码显示了在 Scala 3 中实现它的一种方法。 +如果您认为 Scala 类没有 `toString` 方法,您可以定义一个 `Show` 类型类,然后把此行为添加到任意的类,这个类是能够转换为自定义字符串。 + +### 类型类 + +创建类型类的第一步是声明具有一个或多个抽象方法的参数化 trait。 +因为 `Showable` 只有一个名为 `show` 的方法,所以写成这样: + +```scala +// a type class +trait Showable[A]: + extension(a: A) def show: String +``` + +这是 Scala 3 的说法,任何实现此 trait 的类型都必须定义 `show` 方法的工作方式。 +请注意,语法非常接近普通的 trait: + +```scala +// a trait +trait Show: + def show: String +``` + +有几件重要的事情需要指出: + +1. 像 `Showable` 这样的类型类有一个类型参数 `A` 来说明我们为哪种类型提供了 `show` 的实现;相反,像 `Show` 这样的正常特征不会。 +2. 要将 show 功能添加到特定类型 `A`,正常 trait 需要 `A extends Show`,而对于类型类,我们需要实现 `Showable[A]`。 +3. 为了在两个 `Showable` 中允许相同的方法调用语法来模仿 `Show`,我们将 `Showable.show` 定义为扩展方法。 + +### 实现具体实例 + +下一步是确定在应用程序中,`Showable` 适用于哪些类,然后为它们实现该行为。 +例如,为这个 `Person` 类实现 `Showable`: + +```scala +case class Person(firstName: String, lastName: String) +``` + +你将为 `Showable[Person]` 定义一个 `given` 值。 +这段代码为 `Person` 类提供了一个 `Showable` 的具体实例: + +```scala +given Showable[Person] with + extension(p: Person) def show: String = + s"${p.firstName} ${p.lastName}" +``` + +如图所示,这被定义为 `Person` 类的扩展方法,它使用 `show` 方法主体内的引用 `p`。 + +### 使用类型类 + +现在你可以像这样使用这个类型类: + +```scala +val person = Person("John", "Doe") +println(person.show) +``` + +同样,如果 Scala 没有可用于每个类的 `toString` 方法,您可以使用此技术将 `Showable` 行为添加到您希望能够转换为 `String` 的任何类。 + +### 编写使用类型类的方法 + +与继承一样,您可以定义使用 `Showable` 作为类型参数的方法: + +```scala +def showAll[S: Showable](xs: List[S]): Unit = + xs.foreach(x => println(x.show)) + +showAll(List(Person("Jane"), Person("Mary"))) +``` + +### 具有多种方法的类型类 + +请注意,如果要创建具有多个方法的类型类,则初始语法如下所示: + +```scala +trait HasLegs[A]: + extension (a: A) + def walk(): Unit + def run(): Unit +``` + +### 一个真实的例子 + +有关如何在 Scala 3 中使用类型类的真实示例,请参阅[多元相等性部分][multiversal]中的 `CanEqual` 讨论。 + + +[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} From aadcb0ede8c6a25861249441386fa26f711c24fe Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Fri, 10 Jun 2022 08:58:18 +0800 Subject: [PATCH 23/53] add concurrency.md --- _zh-cn/overviews/scala3-book/concurrency.md | 312 ++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/concurrency.md diff --git a/_zh-cn/overviews/scala3-book/concurrency.md b/_zh-cn/overviews/scala3-book/concurrency.md new file mode 100644 index 0000000000..02b3234842 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/concurrency.md @@ -0,0 +1,312 @@ +--- +title: 并发 +type: chapter +description: This page discusses how Scala concurrency works, with an emphasis on Scala Futures. +num: 68 +previous-page: ca-summary +next-page: scala-tools +--- + + +当您想在 Scala 中编写并行和并发应用程序时,您_可以_使用本机 Java `Thread` --- 但 Scala [Future](https://www.scala-lang.org/api/current/scala/concurrent /Future$.html) 提供了一种更高级和惯用的方法,因此它是首选,本章将对此进行介绍。 + +## 介绍 + +以下是 Scaladoc 中对 Scala `Future` 的描述: + +> “ `Future` 代表一个值,它可能_当前_可用或不可用,但在某个时候可用,或者如果该值不能可用,则表示为异常。” + +为了演示这意味着什么,让我们首先看一下单线程编程。 +在单线程世界中,您将方法调用的结果绑定到如下变量: + +```scala +def aShortRunningTask(): Int = 42 +val x = aShortRunningTask() +``` + +在此代码中,值 `42` 立即绑定到 `x`。 + +当您使用 `Future` 时,分配过程看起来很相似: + +```scala +def aLongRunningTask(): Future[Int] = ??? +val x = aLongRunningTask() +``` + +但在这种情况下的主要区别在于,因为 `aLongRunningTask` 需要不确定的时间才能返回,所以 `x` 中的值可能_当前_可用也可能不可用,但它会在某个时候可用——在未来. + +另一种看待这个问题的方法是阻塞。 +在这个单线程示例中,在 `aShortRunningTask` 完成之前不会打印 `println` 语句: + +```scala +def aShortRunningTask(): Int = + Thread.sleep(500) + 42 +val x = aShortRunningTask() +println("Here") +``` + +相反,如果 `aShortRunningTask` 被创建为 `Future`,`println` 语句几乎立即被打印,因为 `aShortRunningTask` 是在其他线程上产生的——它不会阻塞。 + +在本章中,您将看到如何使用 futures,包括如何并行运行多个 future 并将它们的结果组合到一个 `for` 表达式中。 +您还将看到一些例子,在这些例子中,有些方法用于处理在返回的 future 中的值。 + +> 当你考虑 future 时,重要的是要知道它们是一次性的,“在其他线程上处理这个相对较慢的计算,完成后给把结果通知我”的结构。 +> 作为对比,[Akka](https://akka.io) Actor 旨在运行很长时间,并在其生命周期内响应许多请求。 +> 虽然 actor可能永远活着,但 future 最终会包含只运行一次的计算结果。 + +## REPL 中的一个例子 + +future 用于创建一个临时的并发包。 +例如,当您需要调用运行不确定时间的算法时---例如调用远程微服务---您使用 future---因此您希望在主线程之外运行它。 + +为了演示它是如何工作的,让我们从 REPL 中的 `Future` 示例开始。 +首先,粘贴这些必需的 `import` 语句: + +```scala +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} +``` + +现在您已准备好创造 future 。 +对于这个例子,首先定义一个长时间运行的单线程算法: + +```scala +def longRunningAlgorithm() = + Thread.sleep(10_000) + 42 +``` + +这种奇特的算法在十秒延迟后返回整数值`42`。 +现在通过将其包装到 `Future` 构造函数中来调用该算法,并将结果分配给一个变量: + +```scala +scala> val eventualInt = Future(longRunningAlgorithm()) +eventualInt: scala.concurrent.Future[Int] = Future() +``` + +马上,您的计算——对 `longRunningAlgorithm()` 的调用——开始运行。 +如果你立即检查变量 `eventualInt` 的值,你会看到 future 还没有完成: + +```scala +scala> eventualInt +val res1: scala.concurrent.Future[Int] = Future() +``` + +但是如果你在十秒后再次检查,你会看到它已经成功完成了: + +```scala +scala> eventualInt +val res2: scala.concurrent.Future[Int] = Future(Success(42)) +``` + +虽然这是一个相对简单的示例,但它显示了基本方法:只需使用您的长时间运行的算法构建一个新的 `Future`。 + +需要注意的一点是,您期望的 `42` 被包裹在 `Success` 中,而后者又被包裹在 `Future` 中。 +这是一个需要理解的关键概念:`Future` 中的值始终是`scala.util.Try` 类型之一的实例:`Success` 或 `Failure`。 +因此,当您处理 future 的结果时,您使用通常的 `Try` 处理技术。 + +### 将 `map` 与 future 一起使用 + +`Future` 有一个 `map` 方法,你可以像使用集合中的 `map` 方法一样使用它。 +这是在创建变量 `f` 后立即调用 `map` 时的结果: + +```scala +scala> val a = eventualInt.map(_ * 2) +a: scala.concurrent.Future[Int] = Future() +``` + +如图所示,对于使用 `longRunningAlgorithm` 创建的 future ,初始输出显示 `Future()`。 +但是当你在十秒后检查 `a` 的值时,你会看到它包含 `84` 的预期结果: + +```scala +scala> a +res1: scala.concurrent.Future[Int] = Future(Success(84)) +``` + +再一次,成功的结果被包裹在 `Success` 和 `Future` 中。 + +### 在 future 中使用回调方法 + +除了像`map`这样的高阶函数,你还可以使用回调方法和futures。 +一种常用的回调方法是 `onComplete`,它采用*部分函数*,您可以在其中处理 `Success` 和 `Failure` 情况: + +```scala +eventualInt.onComplete { + case Success(value) => println(s"得到回调,value = $value") + 案例失败(e)=> e.printStackTrace +} +``` + +当您将该代码粘贴到 REPL 中时,您最终会看到结果: + +```scala +收到回调,value = 42 +``` + +## 其他 future 方法 + +`Future` 类还有其他可以使用的方法。 +它具有您在 Scala 集合类中找到的一些方法,包括: + +- `过滤器` +-`平面地图` +- `地图` + +它的回调方法有: + +- `onComplete` +- `然后` +- `foreach` + +其他转换方法包括: + +-`fallbackTo` +- `恢复` +- `recoverWith` + +请参阅 [Futures and Promises][futures] 页面,了解有关 future 可用的其他方法的讨论。 + + + +## 运行多个 future 并加入他们的结果 + +要并行运行多个计算并在所有 future 完成后加入它们的结果,请使用“for”表达式。 + +正确的做法是: + +1. 开始计算返回 `Future` 结果 +2. 将他们的结果合并到一个 `for` 表达式中 +3. 使用 `onComplete` 或类似技术提取合并结果 + + +### 一个例子 + +以下示例显示了正确方法的三个步骤。 +一个关键是你首先开始计算返回 future ,然后将它们加入到 `for` 表达式中: + +```scala +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} + +val startTime = System.currentTimeMillis() +def delta() = System.currentTimeMillis() - startTime +def sleep(millis: Long) = Thread.sleep(millis) + +@main def multipleFutures1 = + + println(s"creating the futures: ${delta()}") + + // (1) start the computations that return futures + val f1 = Future { sleep(800); 1 } // eventually returns 1 + val f2 = Future { sleep(200); 2 } // eventually returns 2 + val f3 = Future { sleep(400); 3 } // eventually returns 3 + + // (2) join the futures in a `for` expression + val result = + for + r1 <- f1 + r2 <- f2 + r3 <- f3 + yield + println(s"in the 'yield': ${delta()}") + (r1 + r2 + r3) + + // (3) process the result + result.onComplete { + case Success(x) => + println(s"in the Success case: ${delta()}") + println(s"result = $x") + case Failure(e) => + e.printStackTrace + } + + println(s"before the 'sleep(3000)': ${delta()}") + + // important for a little parallel demo: keep the jvm alive + sleep(3000) +``` + +当您运行该应用程序时,您会看到如下所示的输出: + +```` +creating the futures: 1 +before the 'sleep(3000)': 2 +in the 'yield': 806 +in the Success case: 806 +result = 6 +```` + +如该输出所示, future 的创建速度非常快,仅在两毫秒内就到达了方法末尾的 `sleep(3000)` 语句之前的打印语句。 +所有这些代码都在 JVM 的主线程上运行。 +然后,在 806 毫秒,三个 future 完成并运行 `yield` 块中的代码。 +然后代码立即转到 `onComplete` 方法中的 `Success` 案例。 + +806 毫秒的输出是看到三个计算并行运行的关键。 +如果它们按顺序运行,总时间约为 1,400 毫秒——三个计算的睡眠时间之和。 +但是因为它们是并行运行的,所以总时间只比运行时间最长的计算:`f1`,即 800 毫秒,稍长。 + +> 请注意,如果计算是在 `for` 表达式中运行的,它们 +> 将按顺序执行,而不是并行执行: +> ~~~ +> // Sequential execution (no parallelism!) +> for +> r1 <- Future { sleep(800); 1 } +> r2 <- Future { sleep(200); 2 } +> r3 <- Future { sleep(400); 3 } +> yield +> r1 + r2 + r3 +> ~~~ +> 因此,如果您希望计算可能并行运行,请记住 +> 在 `for` 表达式之外运行它们。 + +### 一个返回 future 的方法 + +到目前为止,您已经了解了如何将单线程算法传递给 `Future` 构造函数。 +您可以使用相同的技术来创建一个返回 `Future` 的方法: + +```scala +// simulate a slow-running method +def slowlyDouble(x: Int, delay: Long): Future[Int] = Future { + sleep(delay) + x * 2 +} +``` + +与前面的示例一样,只需将方法调用的结果分配给一个新变量。 +然后当你立刻检查结果时,你会看到它没有完成,但是在延迟时间之后,future 会有一个结果: + +```` +scala> val f = slowlyDouble(2, 5_000L) +val f: concurrent.Future[Int] = Future() + +scala> f +val res0: concurrent.Future[Int] = Future() + +scala> f +val res1: concurrent.Future[Int] = Future(Success(4)) +```` + +## 关于 future 的要点 + +希望这些示例能让您了解 Scala future 是如何工作的。 +总而言之,关于 future 的几个关键点是: + +- 您构建 future 以在主线程之外运行任务 +- Futures 用于一次性的、可能长时间运行的并发任务,这些任务*最终*返回一个值;他们创造了一个临时的并发包 +- 一旦你构建了 future,它就会开始运行 +- future 相对于线程的一个好处是它们可以使用 `for` 表达式,并带有各种回调方法,可以简化使用并发线程的过程 +- 当您使用 future 时,您不必关心线程管理的低级细节 +- 您可以使用 `onComplete` 和 `andThen` 之类的回调方法或 `filter`、`map` 等转换方法来处理 future 的结果。 +- `Future` 中的值始终是 `Try` 类型之一的实例:`Success` 或 `Failure` +- 如果您使用多个 future 来产生一个结果,请将它们组合在一个 `for` 表达式中 + +此外,正如您在这些示例中看到的 `import` 语句,Scala `Future` 依赖于 `ExecutionContext`。 + +有关 future 的更多详细信息,请参阅[Future 和 Promises][future],这是一篇讨论 future 、promises 和执行上下文的文章。 +它还讨论了如何将 `for` 表达式转换为 `flatMap` 操作。 + + +[futures]: {% link _overviews/core/futures.md %} From bb47995b07118109b949ebdd3a89db564844ab0b Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sat, 11 Jun 2022 11:44:34 +0800 Subject: [PATCH 24/53] add all tools files --- _zh-cn/overviews/scala3-book/scala-tools.md | 14 + _zh-cn/overviews/scala3-book/tools-sbt.md | 491 ++++++++++++++++++ .../overviews/scala3-book/tools-worksheets.md | 59 +++ 3 files changed, 564 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/scala-tools.md create mode 100644 _zh-cn/overviews/scala3-book/tools-sbt.md create mode 100644 _zh-cn/overviews/scala3-book/tools-worksheets.md diff --git a/_zh-cn/overviews/scala3-book/scala-tools.md b/_zh-cn/overviews/scala3-book/scala-tools.md new file mode 100644 index 0000000000..1449ac690e --- /dev/null +++ b/_zh-cn/overviews/scala3-book/scala-tools.md @@ -0,0 +1,14 @@ +--- +title: Scala 工具 +type: chapter +description: This chapter looks at two commonly-used Scala tools, sbt and ScalaTest. +num: 69 +previous-page: concurrency +next-page: tools-sbt +--- + + +本章介绍编写和运行 Scala 程序的两种方法: + +- 通过创建Scala项目,可能包含多个文件,并定义一个程序入口点, +- 通过与工作表交互,工作表是在单个文件中定义的程序,逐行执行。 diff --git a/_zh-cn/overviews/scala3-book/tools-sbt.md b/_zh-cn/overviews/scala3-book/tools-sbt.md new file mode 100644 index 0000000000..23edcbfb0f --- /dev/null +++ b/_zh-cn/overviews/scala3-book/tools-sbt.md @@ -0,0 +1,491 @@ +--- +title: 使用 sbt 构建和测试 Scala 项目 +type: section +description: This section looks at a commonly-used build tool, sbt, and a testing library, ScalaTest. +num: 70 +previous-page: scala-tools +next-page: tools-worksheets +--- + + +在本节中,您将看到 Scala 项目中常用的两个工具: + +- [sbt](https://www.scala-sbt.org) 构建工具 +- [ScalaTest](https://www.scalatest.org),一个源代码测试框架 + +我们将首先展示如何使用 sbt 构建您的 Scala 项目,然后我们将展示如何一起使用 sbt 和 ScalaTest 来测试您的 Scala 项目。 + +> 如果您想了解帮助您将 Scala 2 代码迁移到 Scala 3 的工具,请参阅我们的 [Scala 3 迁移指南](/scala3/guides/migration/compatibility-intro.html)。 + +## 使用 sbt 构建 Scala 项目 + +您可以使用多种不同的工具来构建您的 Scala 项目,包括 Ant、Maven、Gradle、Mill 等。 +但是名为 _sbt_ 的工具是第一个专门为 Scala 创建的构建工具。 + +> 要安装 sbt,请参阅 [其下载页面](https://www.scala-sbt.org/download.html) 或我们的 [Getting Started][getting_started] 页面。 + +### 创建一个 “Hello, world” 项目 + +只需几个步骤,您就可以创建一个 sbt “Hello, world” 项目。 +首先,创建一个工作目录,然后进入该目录: + +```bash +$ mkdir hello +$ cd hello +``` + +在 `hello` 目录下,创建一个子目录 `project`: + +```bash +$ mkdir project +``` + +在 `project` 目录中创建一个名为 _build.properties_ 的文件,其中 +以下内容: + +```text +sbt.version=1.6.1 +``` + +然后在包含此行的项目根目录中创建一个名为 _build.sbt_ 的文件: + +```scala +scalaVersion := "{{ site.scala-3-version }}" +``` + +现在创建一个名为 _Hello.scala_ 的文件——名称的第一部分无关紧要——使用这一行: + +```scala +@main def helloWorld = println("Hello, world") +``` + +这就是你所要做的。 + +您应该具有如下的项目结构: + +~~~ bash +$ tree +. +├── build.sbt +├── Hello.scala +└── project + └── build.properties +~~~ + +现在使用 `sbt` 命令运行项目: + +```bash +$ sbt run +``` + +您应该会看到如下所示的输出,包括程序中的 “Hello, world”: + +```bash +$ sbt run +[info] welcome to sbt 1.5.4 (AdoptOpenJDK Java 11.x) +[info] loading project definition from project ... +[info] loading settings for project from build.sbt ... +[info] compiling 1 Scala source to target/scala-3.0.0/classes ... +[info] running helloWorld +Hello, world +[success] Total time: 2 s +``` + +sbt 启动器——`sbt` 命令行工具——加载文件 _project/build.properties_ 中设置的 sbt 版本,它加载文件 _build.sbt_ 中设置的 Scala 编译器版本,编译 _Hello.scala_ 文件中的代码,并运行生成的字节码。 + +当你查看你的目录时,你会看到 sbt 有一个名为 _target_ 的目录。 +这些是 sbt 使用的工作目录。 + +如您所见,使用 sbt 创建和运行一个小的 Scala 项目只需要几个简单的步骤。 + +### 在大型项目中使用 sbt + +对于一个小项目,这就是 sbt 运行所需的全部内容。 +对于需要许多源代码文件、依赖项或 sbt 插件的大型项目,您需要创建一个有组织的目录结构。 +本节的其余部分演示了 sbt 使用的结构。 + +### sbt 目录结构 + +与 Maven 一样,sbt 使用标准的项目目录结构。 +这样做的一个很好的好处是,一旦你对它的结构感到满意,它就可以很容易地处理其他 Scala/sbt 项目。 + +首先要知道的是,在项目的根目录下,sbt 需要一个如下所示的目录结构: + +```text +. +├── build.sbt +├── project/ +│ └── build.properties +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ ├── resources/ +│ │ └── scala/ +│ └── test/ +│ ├── java/ +│ ├── resources/ +│ └── scala/ +└── target/ +``` + +如果您想将非托管依赖项---JAR 文件---添加到您的项目中,您还可以在根目录下添加一个 _lib_ 目录。 + +如果您要创建一个包含 Scala 源代码文件和测试的项目,但不会使用任何 Java 源代码文件,并且不需要任何“资源”——例如嵌入式图像、配置文件、等等---这就是你在_src_目录下真正需要的: + +```text +. +└── src/ + ├── main/ + │ └── scala/ + └── test/ + └── scala/ +``` + +### 带有 sbt 目录结构的 “Hello, world” + +{% comment %} +LATER: using something like `sbt new scala/scala3.g8` may eventually + be preferable, but that seems to have a few bugs atm (creates + a 'target' directory above the root; renames the root dir; + uses 'dottyVersion'; 'name' doesn’t match the supplied name; + config syntax is a little hard for beginners.) +{% endcomment %} + +创建这个目录结构很简单。 +有一些工具可以为你做到这一点,但假设你使用的是 Unix/Linux 系统,你可以使用这些命令来创建你的第一个 sbt 项目目录结构: + +```bash +$ mkdir HelloWorld +$ cd HelloWorld +$ mkdir -p src/{main,test}/scala +$ mkdir project target +``` + +在运行这些命令后运行 `find .` 命令时,您应该会看到以下结果: + +```bash +$ find . +. +./project +./src +./src/main +./src/main/scala +./src/test +./src/test/scala +./target +``` + +如果你看到上面那样,那么没有问题,可以进行下一步了。 + +> 还有其他方法可以为 sbt 项目创建文件和目录。 +> 一种方法是使用 `sbt new` 命令,[在 scala-sbt.org 上有文档](https://www.scala-sbt.org/1.x/docs/Hello.html)。 +> 该方法未在此处显示,因为它创建的某些文件比像这样的介绍所必需的要复杂。 + +### 创建第一个 build.sbt 文件 + +此时,您只需要另外两件事来运行 “Hello, world” 项目: + +- 一个 _build.sbt_ 文件 +- 一个 _Hello.scala_ 文件 + +对于像这样的小项目,_build.sbt_ 文件只需要一个 `scalaVersion` 条目,但我们将添加您通常看到的三行: + +```scala +name := "HelloWorld" +version := "0.1" +scalaVersion := "{{ site.scala-3-version }}" +``` + +因为 sbt 项目使用标准的目录结构,所以 sbt 可以找到它需要的所有其他内容。 + +现在你只需要添加一个小小的“Hello, world”程序。 + +### “Hello, world” 程序 + +在大型项目中,您所有的 Scala 源代码文件都将放在 _src/main/scala_ 和 _src/test/scala_ 目录下,但是对于像这样的小示例项目,您可以将源代码文件放在您项目的根目录下。 +因此,在根目录中创建一个名为 _HelloWorld.scala_ 的文件,其中包含以下内容: + +```scala +@main def helloWorld = println("Hello, world") +``` + +该代码定义了一个 Scala 3 “main” 方法,该方法在运行时打印 “Hello, world”。 + +现在,使用 `sbt run` 命令编译并运行您的项目: + +```bash +$ sbt run + +[info] welcome to sbt +[info] loading settings for project ... +[info] loading project definition +[info] loading settings for project root from build.sbt ... +[info] Compiling 1 Scala source ... +[info] running helloWorld +Hello, world +[success] Total time: 4 s +``` + +第一次运行 `sbt` 时,它会下载所需的所有内容,这可能需要一些时间才能运行,但之后它会变得更快。 + +此外,一旦你完成了这第一步,你会发现以交互方式运行 sbt 会快得多。 +为此,首先单独运行 `sbt` 命令: + +```bash +$ sbt + +[info] welcome to sbt +[info] loading settings for project ... +[info] loading project definition ... +[info] loading settings for project root from build.sbt ... +[info] sbt server started at + local:///${HOME}/.sbt/1.0/server/7d26bae822c36a31071c/sock +sbt:hello-world> _ +``` + +然后在这个 sbt shell 中,执行它的 `run` 命令: + +```` +sbt:hello-world> run + +[info] running helloWorld +Hello, world +[success] Total time: 0 s +```` + +这要快得多。 + +如果您在 sbt 命令提示符下键入 `help`,您将看到可以运行的其他命令的列表。 +但现在,只需键入 `exit`(或按 `CTRL-D`)离开 sbt shell。 + +### 使用项目模板 + +手动创建项目结构可能很乏味。谢天谢地,sbt 可以基于模板为你创建项目。 + +要从模板创建 Scala 3 项目,请在 shell 中运行以下命令: + +~~~ +$ sbt new scala/scala3.g8 +~~~ + +Sbt 将加载模板,提出一些问题,并在子目录中创建项目文件: + +~~~ +$ tree scala-3-project-template +scala-3-project-template +├── build.sbt +├── project +│ └── build.properties +├── README.md +└── src + ├── main + │ └── scala + │ └── Main.scala + └── test + └── scala + └── Test1.scala +~~~ + +> 如果要创建与 Scala 2 交叉编译的 Scala 3 项目,请使用模板 `scala/scala3-cross.g8`: +> +> ~~~ +> $ sbt new scala/scala3-cross.g8 +> ~~~ + +在 [sbt 文档](https://www.scala-sbt.org/1.x/docs/sbt-new-and-Templates.html#sbt+new+) 中了解有关 `sbt new` 和项目模板的更多信息和+模板。 + +### Scala 的其他构建工具 + +虽然 sbt 被广泛使用,但您还可以使用其他工具来构建 Scala 项目: + +- [ant](https://ant.apache.org/) +- [Gradle](https://gradle.org/) +- [Maven](https://maven.apache.org/) +- [mill](https://com-lihaoyi.github.io/mill/) + +#### Coursier + +在相关说明中,[Coursier](https://get-coursier.io/docs/overview) 是一个“依赖解析器”,在功能上类似于 Maven 和 Ivy。 +它是用 Scala 从头开始​​编写的,“包含函数式编程原则”,并且可以并行下载工件以实现快速下载。 +sbt 使用它来处理大多数依赖关系解析,并且作为一个命令行工具,它可以用于在您的系统上轻松安装 sbt、Java 和 Scala 等工具,如我们的 [Getting Started][getting_started] 页面所示。 + +来自 `launch` 网页的这个示例显示了 `cs launch` 命令可用于从依赖项启动应用程序: + +```scala +$ cs launch org.scalameta::scalafmt-cli:2.4.2 -- --help +scalafmt 2.4.2 +Usage: scalafmt [options] [...] + + -h, --help prints this usage text + -v, --version print version + more ... +``` + +有关详细信息,请参阅 Coursier 的 [启动页面](https://get-coursier.io/docs/cli-launch)。 + +## 使用 sbt 和 ScalaTest + +[ScalaTest](https://www.scalatest.org) 是 Scala 项目的主要测试库之一。 +在本节中,您将看到创建使用 ScalaTest 的 Scala/sbt 项目所需的步骤。 + +### 1) 创建项目目录结构 + +与上一课一样,使用以下命令为名为 _HelloScalaTest_ 的项目创建一个 sbt 项目目录结构: + +```bash +$ mkdir HelloScalaTest +$ cd HelloScalaTest +$ mkdir -p src/{main,test}/scala +$ mkdir project +``` + +### 2) 创建 build.properties 和 build.sbt 文件 + +接下来,在项目的 _project/_ 子目录中创建一个 _build.properties_ 文件 +用这条线: + +```text +sbt.version=1.5.4 +``` + +接下来,在项目的根目录中创建一个 _build.sbt_ 文件,其中包含以下内容: + +```scala +name := "HelloScalaTest" +version := "0.1" +scalaVersion := "{{site.scala-3-version}}" + +libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % "3.2.9" % Test +) +``` + +该文件的前三行与第一个示例基本相同。 +`libraryDependencies` 行告诉 sbt 包含包含 ScalaTest 所需的依赖项(JAR 文件)。 + +> ScalaTest 文档一直很优秀,您始终可以在 [安装 ScalaTest](https://www.scalatest.org/install) 页面上找到有关这些行应该是什么样子的最新信息。 + +### 3) 创建一个 Scala 源代码文件 + +接下来,创建一个可用于演示 ScalaTest 的 Scala 程序。 +首先,在 _src/main/scala_ 下创建一个名为 _math_ 的目录: + +```bash +$ mkdir src/main/scala/math + ---- +``` + +然后,在该目录中,使用以下内容创建一个名为 _MathUtils.scala_ 的文件: + +```scala +package math + +object MathUtils: + def double(i: Int) = i * 2 +``` + +该方法提供了一种演示 ScalaTest 的简单方法。 + +{% comment %} +Because this project doesn’t have a `main` method, we don’t try to run it with `sbt run`; we just compile it with `sbt compile`: + +```` +$ sbt compile + +[info] welcome to sbt +[info] loading settings for project ... +[info] loading project definition ... +[info] loading settings for project ... +[info] Executing in batch mode. For better performance use sbt's shell +[success] Total time: 1 s +```` + +With that compiled, let’s create a ScalaTest file to test the `double` method. +{% endcomment %} + +### 4) 创建你的第一个 ScalaTest 测试 + +ScalaTest 非常灵活,并提供了几种不同的方式来编写测试。 +一个简单的入门方法是使用 ScalaTest `AnyFunSuite` 编写测试。 +首先,在 _src/test/scala_ 目录下创建一个名为 _math_ 的目录: + +```bash +$ mkdir src/test/scala/math + ---- +``` + +接下来,在该目录中创建一个名为 _MathUtilsTests.scala_ 的文件,其内容如下: + +```scala +package math + +import org.scalatest.funsuite.AnyFunSuite + +class MathUtilsTests extends AnyFunSuite: + + // test 1 + test("'double' should handle 0") { + val result = MathUtils.double(0) + assert(result == 0) + } + + // test 2 + test("'double' should handle 1") { + val result = MathUtils.double(1) + assert(result == 2) + } + + test("test with Int.MaxValue") (pending) + +end MathUtilsTests +``` + +此代码演示了 ScalaTest `AnyFunSuite` 方法。 +几个重要的点: + +- 你的测试类应该扩展 `AnyFunSuite` +- 如图所示,您可以通过为每个 `test` 指定一个唯一的名称来创建测试 +- 在每个测试结束时,您应该调用 `assert` 来测试条件是否已满足 +- 当你知道你想写一个测试,但你现在不想写它时,将测试创建为“待定”,语法如上例所示 + +像这样使用 ScalaTest 类似于 JUnit,所以如果你是从 Java 转到 Scala 的,希望这看起来相似。 + +现在您可以使用 `sbt test` 命令运行这些测试。 +跳过前几行输出,结果如下所示: + +```` +sbt:HelloScalaTest> test + +[info] Compiling 1 Scala source ... +[info] MathUtilsTests: +[info] - 'double' should handle 0 +[info] - 'double' should handle 1 +[info] - test with Int.MaxValue (pending) +[info] Total number of tests run: 2 +[info] Suites: completed 1, aborted 0 +[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 1 +[info] All tests passed. +[success] Total time: 1 s +```` + +如果一切正常,您将看到类似的输出。 +欢迎来到使用 sbt 和 ScalaTest 测试 Scala 应用程序的世界。 + +### 支持多种类型的测试 + +此示例演示了一种类似于 xUnit _测试驱动开发_(TDD) 样式测试的测试样式,并具有_行为驱动开发_(BDD) 样式的一些优点。 + +如前所述,ScalaTest 很灵活,您还可以用其它风格来编写测试,例如类似于 Ruby 的 RSpec 的风格。 +您还可以使用伪对象、基于属性的测试,并使用 ScalaTest 来测试 Scala.js 代码。 + +有关可用的不同测试风格的更多详细信息,请参阅 [ScalaTest 网站](https://www.scalatest.org) 上的用户指南。 + +## 从这往哪儿走 + +有关 sbt 和 ScalaTest 的更多信息,请参阅以下资源: + +- [sbt 文档](https://www.scala-sbt.org/1.x/docs/) +- [ScalaTest 网站](https://www.scalatest.org/) + + +[getting_started]: {{ site.baseurl }}/scala3/getting-started.html diff --git a/_zh-cn/overviews/scala3-book/tools-worksheets.md b/_zh-cn/overviews/scala3-book/tools-worksheets.md new file mode 100644 index 0000000000..1c743ef250 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/tools-worksheets.md @@ -0,0 +1,59 @@ +--- +title: 工作表 +type: section +description: This section looks at worksheets, an alternative to Scala projects. +num: 71 +previous-page: tools-sbt +next-page: interacting-with-java +--- + + +工作表是在保存时评估的 Scala 文件,并把每个表达式的结果 +显示在程序右侧的列中。工作表就像是加了激素的[REPL 会话][REPL session],并且 +享受一流的编辑器支持:自动补全、超链接、交互式错误输入等。 +工作表使用扩展名 `.worksheet.sc` 。 + +下面,我们将展示如何在 IntelliJ 和 VS Code(带有 Metals 扩展)中使用工作表。 + +1. 打开一个 Scala 项目,或者创建一个。 + - 要在 IntelliJ 中创建项目,选择“File”->“New”->“Project...”, 在左侧栏中选择“Scala”, + 单击“下一步”设置项目名称和位置。 + - 要在 VS Code 中创建项目,请运行命令“Metals: New Scala project”,选择 + 种子 `scala/scala3.g8`,设置项目位置,在新的 VS Code 窗口中打开它,然后 + 导入其构建。 +1. 在 `src/main/scala/` 目录下创建一个名为 `hello.worksheet.sc` 的文件。 + - 在 IntelliJ 中,右键单击目录 `src/main/scala/`,然后选择“New”,然后 + 是“文件”。 + - 在 VS Code 中,右键单击目录`src/main/scala/`,然后选择“New File”。 +1. 在编辑器中粘贴以下内容: + ~~~ + println("Hello, world!") + + val x = 1 + x + x + ~~~ + +1. 评估工作表。 + - 在 IntelliJ 中,单击编辑器顶部的绿色箭头以评估工作表。 + - 在 VS Code 中,保存文件。 + + 您应该在右侧面板 (IntelliJ) 上看到每一行的评估结果,或者 + 作为注释(VS Code)。 + +![]({{ site.baseurl }}/resources/images/scala3-book/intellij-worksheet.png) + +在 IntelliJ 中评估的工作表。 + +![]({{ site.baseurl }}/resources/images/scala3-book/metals-worksheet.png) + +在 VS Code 中评估的工作表(带有 Metals 扩展)。 + +请注意,工作表将使用项目定义的 Scala 版本(通常在文件`build.sbt`中, +设置 `scalaVersion` 键)。 + +另请注意,工作表没有 [程序入口点][program entry point]。作为替代,顶级语句和表达式 +从上到下进行评估。 + + +[REPL session]: {% link _overviews/scala3-book/taste-repl.md %} +[program entry point]: {% link _overviews/scala3-book/methods-main-methods.md %} From c59f3badf08e1882d3c2b5977a8f34c6c60ccfbe Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sat, 11 Jun 2022 22:15:33 +0800 Subject: [PATCH 25/53] add interacting-with-java.md --- .../scala3-book/interacting-with-java.md | 337 ++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/interacting-with-java.md diff --git a/_zh-cn/overviews/scala3-book/interacting-with-java.md b/_zh-cn/overviews/scala3-book/interacting-with-java.md new file mode 100644 index 0000000000..d13ac7635f --- /dev/null +++ b/_zh-cn/overviews/scala3-book/interacting-with-java.md @@ -0,0 +1,337 @@ +--- +title: 与 Java 交互 +type: chapter +description: This page demonstrates how Scala code can interact with Java, and how Java code can interact with Scala code. +num: 72 +previous-page: tools-worksheets +next-page: scala-for-java-devs +--- + +## 介绍 + +本节介绍如何在 Scala 中使用 Java 代码,以及如何在 Java 中使用 Scala 代码。 + +一般来说,在 Scala 中使用 Java 代码是非常无缝的。 +只有几个地方需要使用 Scala 实用程序将 Java 概念转换为 Scala,包括: + +- Java 集合类 +- Java `Optional` 类 + +同样,如果您正在编写 Java 代码并想使用 Scala 概念,则需要转换 Scala 集合和 Scala `Option` 类。 + +以下部分演示了您需要的最常见的转换: + +- 如何在 Scala 中使用 Java 集合 +- 如何在 Scala 中使用 Java `Optional` +- 在 Scala 中扩展 Java 接口 +- 如何在 Java 中使用 Scala 集合 +- 如何在 Java 中使用 Scala `Option` +- 如何在 Java 中使用 Scala traits +- 如何在 Java 代码中处理 Scala 方法引发的异常 +- 如何在 Java 中使用 Scala 可变参数 +- 创建备用名称以在 Java 中使用 Scala 方法 + +请注意,本节中的 Java 示例假设您使用的是 Java 11 或更高版本。 + +## 如何在 Scala 中使用 Java 集合 + +当您编写 Scala 代码并需要使用 Java 集合类时,您_可以_按原样使用该类。 +但是,如果您想在 Scala `for` 循环中使用该类,或者想利用 Scala 集合类中的高阶函数,则需要将 Java 集合转换为 Scala 集合。 + +这是一个如何工作的例子。 +假定这个例子是 Java `ArrayList`: + +```java +// java +public class JavaClass { + public static List getStrings() { + return new ArrayList(List.of("a", "b", "c")); + } +} +``` + +您可以使用 Scala _scala.jdk.CollectionConverters_ 包中的转换实用程序将该 Java 列表转换为 Scala `Seq`: + +```scala +// scala +import scala.jdk.CollectionConverters.* + +def testList() = + println("Using a Java List in Scala") + val javaList: java.util.List[String] = JavaClass.getStrings() + val scalaSeq: Seq[String] = javaList.asScala.toSeq + scalaSeq.foreach(println) + for s <- scalaSeq do println(s) +``` + +当然,该代码可以缩短,但此处显示的各个步骤是为了准确演示转换过程是如何工作的。 + +## 如何在 Scala 中使用 Java `Optional` + +当您需要在 Scala 代码中使用 Java `Optional` 类时,导入 _scala.jdk.OptionConverters_ 对象,然后使用 `toScala` 方法将 `Optional` 值转换为 Scala `Option`。 + +为了证明这一点,这里有一个带有两个 `Optional` 值的 Java 类,一个包含字符串,另一个为空: + +```java +// java +import java.util.Optional; + +public class JavaClass { + static Optional oString = Optional.of("foo"); + static Optional oEmptyString = Optional.empty(); +} +``` + +现在在您的 Scala 代码中,您可以访问这些字段。 +如果您只是直接访问它们,它们都将是 `Optional` 值: + +```scala +// scala +import java.util.Optional + +val optionalString = JavaClass.oString // Optional[foo] +val eOptionalString = JavaClass.oEmptyString // Optional.empty +``` + +但是通过使用 _scala.jdk.OptionConverters_ 方法,您可以将它们转换为 Scala `Option` 值: + +```scala +import java.util.Optional +import scala.jdk.OptionConverters.* + +val optionalString = JavaClass.oString // Optional[foo] +val optionString = optionalString.toScala // Some(foo) + +val eOptionalString = JavaClass.oEmptyString // Optional.empty +val eOptionString = eOptionalString.toScala // None +``` + +## 在 Scala 中扩展 Java 接口 + +如果您需要在 Scala 代码中使用 Java 接口,请将它们扩展为 Scala trait。 +例如,给定这三个 Java 接口: + +```java +// java +interface Animal { + void speak(); +} + +interface Wagging { + void wag(); +} + +interface Running { + // an implemented method + default void run() { + System.out.println("I’m running"); + } +} +``` + +你可以在 Scala 中创建一个 `Dog` 类,就像使用 trait 一样。 +你所要做的就是实现 `speak` 和 `wag` 方法: + +```scala +// scala +class Dog extends Animal, Wagging, Running: + def speak = println("Woof") + def wag = println("Tail is wagging") + +@main def useJavaInterfaceInScala = + val d = new Dog + d.speak + d.wag +``` + +## 如何在 Java 中使用 Scala 集合 + +当您需要在 Java 代码中使用 Scala 集合类时,请在 Java 代码中使用 Scala 的 `scala.jdk.javaapi.CollectionConverters` 对象的方法来进行转换。 +例如,如果您在 Scala 类中有这样的 `List[String]`: + +```scala +// scala +class ScalaClass: + val strings = List("a", "b") +``` + +您可以像这样在 Java 代码中访问该 Scala `List`: + +```java +// java +import scala.jdk.javaapi.CollectionConverters; + +// create an instance of the Scala class +ScalaClass sc = new ScalaClass(); + +// access the `strings` field as `sc.strings()` +scala.collection.immutable.List xs = sc.strings(); + +// convert the Scala `List` a Java `List` +java.util.List listOfStrings = CollectionConverters.asJava(xs); +listOfStrings.forEach(System.out::println); +``` + +该代码可以缩短,但显示了完整的步骤以演示该过程的工作原理。 +以下是该代码中需要注意的一些事项: + +- 在你的 Java 代码中,你创建一个 `ScalaClass` 的实例,就像一个 Java 类的实例一样 +- `ScalaClass` 有一个名为 `strings` 的字段,但在 Java 中,您可以将该字段作为方法访问,即,作为 `sc.strings()` + +## 如何在 Java 中使用 Scala `Option` + +当您需要在 Java 代码中使用 Scala `Option` 时,可以使用 Scala `scala.jdk.javaapi.OptionConverters` 对象的 `toJava` 方法将 `Option` 转换为 Java `Optional` 值。 + +为了证明这一点,创建一个具有两个 `Option[String]` 值的 Scala 类,一个包含字符串,另一个为空: + +```scala +// scala +object ScalaObject: + val someString = Option("foo") + val noneString: Option[String] = None +``` + +然后在您的 Java 代码中,使用来自 `scala.jdk.javaapi.OptionConverters` 对象的 `toJava` 方法将这些 `Option[String]` 值转换为 `java.util.Optional[String]`: + +```java +// java +import java.util.Optional; +import static scala.jdk.javaapi.OptionConverters.toJava; + +public class JUseScalaOptionInJava { + public static void main(String[] args) { + Optional stringSome = toJava(ScalaObject.someString()); // Optional[foo] + Optional stringNone = toJava(ScalaObject.noneString()); // Optional.empty + System.out.printf("stringSome = %s\n", stringSome); + System.out.printf("stringNone = %s\n", stringNone); + } +} +``` + +两个 Scala `Option` 字段现在可用作 Java `Optional` 值。 + +## 如何在 Java 中使用 Scala trait + +在 Java 11 中,您可以像使用 Java 接口一样使用 Scala trait,即使 trait 已经实现了方法。 +例如,给定这两个 Scala trait,一个具有已实现的方法,一个只有一个接口: + +```scala +// scala +trait ScalaAddTrait: + def sum(x: Int, y: Int) = x + y // implemented + +trait ScalaMultiplyTrait: + def multiply(x: Int, y: Int): Int // abstract +``` + +Java 类可以实现这两个接口,并定义 `multiply` 方法: + +```java +// java +class JavaMath implements ScalaAddTrait, ScalaMultiplyTrait { + public int multiply(int a, int b) { + return a * b; + } +} + +JavaMath jm = new JavaMath(); +System.out.println(jm.sum(3,4)); // 7 +System.out.println(jm.multiply(3,4)); // 12 +``` + +## 如何处理在 Java 代码中抛出异常的 Scala 方法 + +当您使用 Scala 编程习惯编写 Scala 代码时,您永远不会编写引发异常的方法。 +但是如果由于某种原因你有一个抛出异常的 Scala 方法,并且你希望 Java 开发人员能够使用该方法,请在你的 Scala 方法中添加`@throws`注解,以便 Java 使用者知道它可以抛出的异常. + +例如,注解这个 Scala `exceptionThrower` 方法抛出一个 `Exception`: + +```scala +// scala +object SExceptionThrower: + @throws(classOf[Exception]) + def exceptionThrower = + throw new Exception("Idiomatic Scala methods don’t throw exceptions") +``` + +因此,您需要处理 Java 代码中的异常。 +例如,这段代码不会编译,因为我不处理异常: + +```java +// java: won’t compile because the exception isn’t handled +public class ScalaExceptionsInJava { + public static void main(String[] args) { + SExceptionThrower.exceptionThrower(); + } +} +``` + +编译器给出了这个错误: + +```` +[error] ScalaExceptionsInJava: unreported exception java.lang.Exception; + must be caught or declared to be thrown +[error] SExceptionThrower.exceptionThrower() +```` + +这很好——这就是你想要的:注解告诉 Java 编译器 `exceptionThrower` 可以抛出异常。 +现在,当您编写 Java 代码时,您必须使用 `try` 块处理异常或声明您的 Java 方法抛出异常。 + +相反,如果你 Scala `exceptionThrower` 方法的注释,Java 代码_将编译_。 +这可能不是您想要的,因为 Java 代码可能无法考虑引发异常的 Scala 方法。 + +## 如何在 Java 中使用 Scala 可变参数 + +当 Scala 方法具有可变参数并且您想在 Java 中使用该方法时,请使用 `@varargs` 注解来标记 Scala 方法。 +例如,这个 Scala 类中的 `printAll` 方法声明了一个 `String*` 可变参数字段: + +```scala +// scala +import scala.annotation.varargs + +object VarargsPrinter: + @varargs def printAll(args: String*): Unit = args.foreach(println) +``` + +因为 `printAll` 是用 `@varargs` 注释声明的,所以可以从具有可变数量参数的 Java 程序中调用它,如下例所示: + +```scala +// java +public class JVarargs { + public static void main(String[] args) { + VarargsPrinter.printAll("Hello", "world"); + } +} +``` + +运行此代码时,结果在以下输出中: + +```` +Hello +world +```` + +## 创建备用名称以在 Java 中使用 Scala 方法 + +在 Scala 中,您可能希望使用符号字符创建方法名称: + +```scala +def +(a: Int, b: Int) = a + b +``` + +该方法名称在 Java 中不能很好地工作,但您可以在 Scala 3 中为该方法提供一个“替代”名称——别名——这将在 Java 中工作: + +```scala +import scala.annotation.targetName + +class Adder: + @targetName("add") def +(a: Int, b: Int) = a + b +``` + +现在在您的 Java 代码中,您可以使用别名方法名称 `add`: + +```scala +var adder = new Adder(); +int x = adder.add(1,1); +System.out.printf("x = %d\n", x); +``` From 76e08bfb94094dcd1ec132f9f845bb43f4fe6248 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Thu, 16 Jun 2022 21:04:46 +0800 Subject: [PATCH 26/53] add all scala-for-* files. Finish all files in Scala3-book. --- .../scala3-book/scala-for-java-devs.md | 1273 ++++++++++++++++ .../scala3-book/scala-for-javascript-devs.md | 1330 ++++++++++++++++ .../scala3-book/scala-for-python-devs.md | 1354 +++++++++++++++++ 3 files changed, 3957 insertions(+) create mode 100644 _zh-cn/overviews/scala3-book/scala-for-java-devs.md create mode 100644 _zh-cn/overviews/scala3-book/scala-for-javascript-devs.md create mode 100644 _zh-cn/overviews/scala3-book/scala-for-python-devs.md diff --git a/_zh-cn/overviews/scala3-book/scala-for-java-devs.md b/_zh-cn/overviews/scala3-book/scala-for-java-devs.md new file mode 100644 index 0000000000..af719abbab --- /dev/null +++ b/_zh-cn/overviews/scala3-book/scala-for-java-devs.md @@ -0,0 +1,1273 @@ +--- +title: 为 Java 开发者介绍Scala +type: chapter +description: This page is for Java developers who are interested in learning about Scala 3. +num: 73 +previous-page: interacting-with-java +next-page: scala-for-javascript-devs +--- + + +{% include_relative scala4x.css %} +
+ +此页面通过共享每种语言的并排示例,对 Java 和 Scala 编程语言进行了比较。 +它适用于了解 Java 并希望了解 Scala 的程序员,特别是通过 Scala 特性与 Java 特性的对比来了解。 + +## 概述 + +在进入示例之前,第一部分提供了以下部分的相对简短的介绍和总结。 +它从高层次上介绍了 Java 和 Scala 之间的异同,然后介绍了您在每天编写代码时会遇到的差异。 + +### 高层次的相似性 + +在高层次上,Scala 与 Java 有以下相似之处: + +- Scala代码编译成_.class_文件,打包成JAR文件,运行在JVM上 +- 这是一种[面向对象编程][modeling-oop] (OOP) 语言 +- 它是静态类型的 +- 两种语言都支持 lambdas 和 [高阶函数][hofs] +- 它们都可以与 IntelliJ IDEA 和 Microsoft VS Code 等 IDE 一起使用 +- 可以使用 Gradle、Ant 和 Maven 等构建工具构建项目 +- 它具有用于构建服务器端、网络密集型应用程序的出色库和框架,包括 Web 服务器应用程序、微服务、机器学习等(参见 [“Awesome Scala” 列表](https://github.com/lauris/awesome-scala)) +- Java 和 Scala 都可以使用 Scala 库: + - 他们可以使用 [Akka actor 库](https://akka.io) 来构建基于 actor 的并发系统,并使用 Apache Spark 来构建数据密集型应用程序 + - 他们可以使用 [Play Framework](https://www.playframework.com) 开发服务器端应用程序 +- 您可以使用 [GraalVM](https://www.graalvm.org) 将您的项目编译为本机可执行文件 +- Scala 可以无缝使用为 Java 开发的大量库 + +### 高层次的差异 + +同样在高层次上,Java 和 Scala 之间的区别是: + +- Scala 语法简洁易读;我们称之为_表现力_ +- 虽然它是静态类型的,但 Scala 经常感觉像是一门动态语言 +- Scala 是一种纯 OOP 语言,因此每个对象都是类的一个实例,而像运算符一样的符号 `+` 和 `+=` 是真正的方法;这意味着您可以创建自己的运算符 +- 除了是纯OOP语言,Scala还是纯FP语言;实际上,它鼓励 OOP 和 FP 的融合,具有用于逻辑的函数和用于模块化的对象 +- Scala 拥有一整套不可变集合,包括 `List`、`Vector` 和不可变的 `Map` 和 `Set` 实现 +- Scala 中的一切都是一个_表达式_:像 `if` 语句、`for` 循环、`match` 表达式,甚至 `try`/`catch` 表达式都有返回值 +- Scala 习惯上倾向缺省使用不可变性:鼓励您使用不可变(`final`)变量和不可变集合 +- 惯用的 Scala 代码不使用 `null`,因此不会遭受 `NullPointerException` +- Scala 生态系统在 sbt、Mill 等中还有其他 [构建工具][tools] +- 除了在 JVM 上运行之外,[Scala.js](https://www.scala-js.org) 项目允许您使用 Scala 作为 JavaScript 替代品 +- [Scala Native](http://www.scala-native.org) 项目添加了低级结构,让您可以编写“系统”级代码,也可以编译为本机可执行文件 + +{% comment %} +These are several notes that came up early in the writing process, and I (Alvin) can’t really address them: +TODO: Need a good, simple way to state that Scala has a sound type system +TODO: Points to make about Scala’s consistency? +TODO: Add a point about how the type system lets you express details as desired +{% endcomment %} + +### 编程层次差异 + +最后,这些是您在编写代码时每天都会看到的一些差异: + +- Scala 的语法极其一致 +- 变量和参数被定义为`val`(不可变,如Java中的`final`)或`var`(可变) +- _类型推导_ 让您的代码感觉是动态类型的,并有助于保持您的代码简洁 +- 除了简单的 `for` 循环之外,Scala 还具有强大的 `for` comprehensions,可以根据您的算法产生结果 +- 模式匹配和 `match` 表达式将改变你编写代码的方式 +- 默认情况下编写不可变代码会导致编写_表达式_而不是_语句_;随着时间的推移,您会发​​现编写表达式可以简化您的代码(和您的测试) +- [顶层定义][toplevel] 让您可以将方法、字段和其他定义放在任何地方,同时也带来简洁、富有表现力的代码 +- 您可以通过将多个 traits “混合”到类和对象中来创建_混搭_(特征类似于 Java 8 和更新版本中的接口) +- 默认情况下类是封闭的,支持 Joshua Bloch 在 _Effective Java_ 的习惯用法,“Design and document for inheritance or else forbid it” +- Scala 的 [上下文抽象][contextual] 和 _术语推导_ 提供了一系列特性: + - [扩展方法][extension-methods] 让您向封闭类添加新功能 + - [_给_实例][givens] 让您定义编译器可以在 _using_ 点合成的术语,从而使您的代码不那么冗长,实质上让编译器为您编写代码 + - [多元等式][multiversal] 允许您在编译时将相等比较限制为仅那些有意义的比较 +- Scala 拥有最先进的第三方开源函数式编程库 +- Scala 样例类就像 Java 14 中的记录;它们可以帮助您在编写 FP 代码时对数据进行建模,并内置对模式匹配和克隆等概念的支持 +- 由于名称参数、中缀符号、可选括号、扩展方法和 [高阶函数][hofs] 等功能,您可以创建自己的“控制结构”和 DSL +- Scala 文件不必根据它们包含的类或 trait 来命名 +- 许多其他好东西:伴生类和对象、宏、[联合][union-types] 和 [交集][intersection-types]、数字字面量、多参数列表、参数的默认值、命名参数等 + +### 用例子来进行特性对比 + +鉴于该介绍,以下部分提供了 Java 和 Scala 编程语言功能的并排比较。 + +## OOP 风格的类和方法 + +本节提供了与 OOP 风格的类和方法相关的特性的比较。 + +### 注释: + + + + + + + + + + +
+ // +
/* ... */ +
/** ... */
+
+ // +
/* ... */ +
/** ... */
+
+ +### OOP 风格类,主构造函数: + +Scala不遵循JavaBeans标准,因此我们在这里展示的Java代码 +与它后面的Scala代码等效,而不是显示以JavaBeans风格编写 +的Java代码。 + + + + + + + + + + +
+ class Person { +
  public String firstName; +
  public String lastName; +
  public int age; +
  public Person( +
    String firstName, +
    String lastName, +
    int age +
  ) { +
    this.firstName = firstName; +
    this.lastName = lastName; +
    this.age = age; +
  } +
  public String toString() { +
    return String.format("%s %s is %d years old.", firstName, lastName, age); +
  } +
}
+
+ class Person ( +
  var firstName: String, +
  var lastName: String, +
  var age: Int +
):   +
  override def toString = s"$firstName $lastName is $age years old." +
+
+ +### 辅助构造函数: + + + + + + + + + + +
+ public class Person { +
  public String firstName; +
  public String lastName; +
  public int age; +
+
  // primary constructor +
  public Person( +
    String firstName, +
    String lastName, +
    int age +
  ) { +
    this.firstName = firstName; +
    this.lastName = lastName; +
    this.age = age; +
  } +
+
  // zero-arg constructor +
  public Person() { +
    this("", "", 0); +
  } +
+
  // one-arg constructor +
  public Person(String firstName) { +
    this(firstName, "", 0); +
  } +
+
  // two-arg constructor +
  public Person( +
    String firstName, +
    String lastName +
  ) { +
    this(firstName, lastName, 0); +
  } +
+
}
+
+ class Person ( +
  var firstName: String, +
  var lastName: String, +
  var age: Int +
): +
    // zero-arg auxiliary constructor +
    def this() = this("", "", 0) +
+
    // one-arg auxiliary constructor +
    def this(firstName: String) = +
      this(firstName, "", 0) +
+
    // two-arg auxiliary constructor +
    def this( +
      firstName: String, +
      lastName: String +
    ) = +
      this(firstName, lastName, 0) +
+
end Person
+
+ +### 类默认是封闭的: +“Plan for inheritance or else forbid it.” + + + + + + + + + + +
+ final class Person +
+ class Person +
+ +### 为扩展开放的类: + + + + + + + + + + +
+ class Person +
+ open class Person +
+ +### 单行方法: + + + + + + + + + + +
+ public int add(int a, int b) { +
  return a + b; +
}
+
+ def add(a: Int, b: Int): Int = a + b +
+ +### 多行方法: + + + + + + + + + + +
+ public void walkThenRun() { +
  System.out.println("walk"); +
  System.out.println("run"); +
}
+
+ def walkThenRun() = +
  println("walk") +
  println("run")
+
+ +### 不可变字段: + + + + + + + + + + +
+ final int i = 1; +
+ val i = 1 +
+ +### 可变字段: + + + + + + + + + + +
+ int i = 1; +
var i = 1;
+
+ var i = 1 +
+ +## 接口、trait 和继承 + +本节将Java接口与Scala trait 进行比较,包括类如何扩展接口和 trait。 + +### 接口/trait: + + + + + + + + + + +
+ public interface Marker; +
+ trait Marker +
+ +### 简单接口: + + + + + + + + + + +
+ public interface Adder { +
  public int add(int a, int b); +
}
+
+ trait Adder: +
  def add(a: Int, b: Int): Int
+
+ +### 有实体方法的接口: + + + + + + + + + + +
+ public interface Adder { +
  int add(int a, int b); +
  default int multiply( +
    int a, int b +
  ) { +
    return a * b; +
  } +
}
+
+ trait Adder: +
  def add(a: Int, b: Int): Int +
  def multiply(a: Int, b: Int): Int = +
    a * b
+
+ +### 继承: + + + + + + + + + + +
+ class Dog extends Animal implements HasLegs, HasTail +
+ class Dog extends Animal, HasLegs, HasTail +
+ +### 扩展多个接口 + +这些接口和特征具有具体的、已实现的方法(默认方法): + + + + + + + + + + +
+ interface Adder { +
  default int add(int a, int b) { +
    return a + b; +
  } +
} +
+
interface Multiplier { +
  default int multiply ( +
    int a, +
    int b) +
  { +
    return a * b; +
  } +
} +
+
public class JavaMath
implements Adder, Multiplier {} +
+
JavaMath jm = new JavaMath(); +
jm.add(1,1); +
jm.multiply(2,2);
+
+ trait Adder: +
  def add(a: Int, b: Int) = a + b +
+
trait Multiplier: +
  def multiply(a: Int, b: Int) = a * b +
+
class ScalaMath extends Adder, Multiplier +
+
val sm = new ScalaMath +
sm.add(1,1) +
sm.multiply(2,2)
+
+ +### 混搭: + + + + + + + + + + +
+ N/A +
+ class DavidBanner +
+
trait Angry: +
  def beAngry() = +
    println("You won’t like me ...") +
+
trait Big: +
  println("I’m big") +
+
trait Green: +
  println("I’m green") +
+
// mix in the traits as DavidBanner +
// is created +
val hulk = new DavidBanner with Big, +
  Angry, Green
+
+ +## 控制结构 + +本节比较在 Java 和 Scala 中的[控制结构][control]。 + +### `if` 语句,单行: + + + + + + + + + + +
+ if (x == 1) { System.out.println(1); } +
+ if x == 1 then println(x) +
+ +### `if` 语句,多行: + + + + + + + + + + +
+ if (x == 1) { +
  System.out.println("x is 1, as you can see:") +
  System.out.println(x) +
}
+
+ if x == 1 then +
  println("x is 1, as you can see:") +
  println(x)
+
+ +### if, else if, else: + + + + + + + + + + +
+ if (x < 0) { +
  System.out.println("negative") +
} else if (x == 0) { +
  System.out.println("zero") +
} else { +
  System.out.println("positive") +
}
+
+ if x < 0 then +
  println("negative") +
else if x == 0 +
  println("zero") +
else +
  println("positive")
+
+ +### `if` 作为方法体: + + + + + + + + + + +
+ public int min(int a, int b) { +
  return (a < b) ? a : b; +
}
+
+ def min(a: Int, b: Int): Int = +
  if a < b then a else b
+
+ +### 从 `if` 返回值: + +在 Java 中调用_三元运算符_: + + + + + + + + + + +
+ int minVal = (a < b) ? a : b; +
+ val minValue = if a < b then a else b +
+ +### `while` 循环: + + + + + + + + + + +
+ while (i < 3) { +
  System.out.println(i); +
  i++; +
}
+
+ while i < 3 do +
  println(i) +
  i += 1
+
+ +### `for` 循环,单行: + + + + + + + + + + +
+ for (int i: ints) { +
  System.out.println(i); +
}
+
+ //preferred +
for i <- ints do println(i) +
+
// also available +
for (i <- ints) println(i)
+
+ +### `for` 循环,多行: + + + + + + + + + + +
+ for (int i: ints) { +
  int x = i * 2; +
  System.out.println(x); +
}
+
+ for +
  i <- ints +
do +
  val x = i * 2 +
  println(s"i = $i, x = $x")
+
+ +### `for` 循环,多生成器: + + + + + + + + + + +
+ for (int i: ints1) { +
  for (int j: chars) { +
    for (int k: ints2) { +
      System.out.printf("i = %d, j = %d, k = %d\n", i,j,k); +
    } +
  } +
}
+
+ for +
  i <- 1 to 2 +
  j <- 'a' to 'b' +
  k <- 1 to 10 by 5 +
do +
  println(s"i = $i, j = $j, k = $k")
+
+ +### 带守卫(`if`)表达式的生成器: + + + + + + + + + + +
+ List ints = +
  ArrayList(1,2,3,4,5,6,7,8,9,10); +
+
for (int i: ints) { +
  if (i % 2 == 0 && i < 5) { +
    System.out.println(x); +
  } +
}
+
+ for +
  i <- 1 to 10 +
  if i % 2 == 0 +
  if i < 5 +
do +
  println(i)
+
+ +### `for` comprehension: + + + + + + + + + + +
+ N/A +
+ val list = +
  for +
    i <- 1 to 3 +
  yield +
    i * 10 +
// list: Vector(10, 20, 30)
+
+ +### switch/match: + + + + + + + + + + +
+ String monthAsString = ""; +
switch(day) { +
  case 1: monthAsString = "January"; +
          break; +
  case 2: monthAsString = "February"; +
          break; +
  default: monthAsString = "Other"; +
          break; +
}
+
+ val monthAsString = day match +
  case 1 => "January" +
  case 2 => "February" +
  _ => "Other" +
+
+ +### switch/match, 每个情况下多个条件: + + + + + + + + + + +
+ String numAsString = ""; +
switch (i) { +
  case 1: case 3: +
  case 5: case 7: case 9: +
    numAsString = "odd"; +
    break; +
  case 2: case 4: +
  case 6: case 8: case 10: +
    numAsString = "even"; +
    break; +
  default: +
    numAsString = "too big"; +
    break; +
}
+
+ val numAsString = i match +
  case 1 | 3 | 5 | 7 | 9 => "odd" +
  case 2 | 4 | 6 | 8 | 10 => "even" +
  case _ => "too big" +
+
+ +### try/catch/finally: + + + + + + + + + + +
+ try { +
  writeTextToFile(text); +
} catch (IOException ioe) { +
  println(ioe.getMessage()) +
} catch (NumberFormatException nfe) { +
  println(nfe.getMessage()) +
} finally { +
  println("Clean up resources here.") +
}
+
+ try +
  writeTextToFile(text) +
catch +
  case ioe: IOException => +
    println(ioe.getMessage) +
  case nfe: NumberFormatException => +
    println(nfe.getMessage) +
finally +
  println("Clean up resources here.")
+
+ +## 集合类 + +本节比较 Java 和 Scala 里的[集合类][collections-classes]。 + +### 不可变集合类 + +如何创建不可变集合实例的例子。 + +### Sequences: + + + + + + + + + + +
+ List strings = List.of("a", "b", "c"); +
+ val strings = List("a", "b", "c") +
val strings = Vector("a", "b", "c")
+
+ +### Sets: + + + + + + + + + + +
+ Set set = Set.of("a", "b", "c"); +
+ val set = Set("a", "b", "c") +
+ +### Maps: + + + + + + + + + + +
+ Map map = Map.of( +
  "a", 1, +
  "b", 2, +
  "c", 3 +
);
+
+ val map = Map( +
  "a" -> 1, +
  "b" -> 2, +
  "c" -> 3 +
)
+
+ +### 可变集合类 + +Scala 在其 _scala.collection.mutable_ 包中有可变集合类,例如 `ArrayBuffer`、`Map` 和 `Set`。 +在 [导入它们][imports] 到当前作用域之后,创建它们就像刚刚显示的不可变 `List`、`Vector`、`Map` 和 `Set` 示例一样。 + +Scala 还有一个 `Array` 类,您可以将其视为 Java `array` 原始类型的包装器。 +一个 Scala `Array[A]` 映射到一个 Java `A[]`,所以你可以认为这个是 Scala `Array[String]`: + +```scala +val a = Array("a", "b") +``` + +这个追溯到 Java 的 `String[]`: + +```scala +String[] a = ["a", "b"]; +``` + +但是,Scala `Array` 还具有您期望在 Scala 集合中使用的所有函数方法,包括 `map` 和 `filter`: + +```scala +val nums = Array(1, 2, 3, 4, 5) +val doubledNums = nums.map(_ * 2) +val 过滤Nums = nums.filter(_ > 2) +``` + +因为 Scala `Array` 的表示方式与 Java `array` 相同,所以您可以轻松地在 Scala 代码中使用返回数组的 Java 方法。 + +> 尽管讨论了 `Array`,但请记住,在 Scala 中通常有可能更适合的 `Array` 替代品。 +> 数组对于与其他语言(Java、JavaScript)的互操作很有用,并且在编写需要从底层平台获得最大性能的低级代码时也很有用。但总的来说,当你需要使用序列时,Scala 的习惯用法是更喜欢像 `Vector` 和 `List` 这样的不可变序列,然后在你真的需要可变序列时使用 `ArrayBuffer`。 + +您还可以使用 Scala `CollectionConverters` 对象在 Java 和 Scala 集合类之间进行转换。 +在不同的包中有两个对象,一个用于从 Java 转换为 Scala,另一个用于从 Scala 转换为 Java。 +下表显示了可能的转换: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JavaScala
java.util.Collectionscala.collection.Iterable
java.util.Listscala.collection.mutable.Buffer
java.util.Setscala.collection.mutable.Set
java.util.Mapscala.collection.mutable.Map
java.util.concurrent.ConcurrentMapscala.collection.mutable.ConcurrentMap
java.util.Dictionaryscala.collection.mutable.Map
+ +## 集合类的方法 + +由于能够将 Java 集合视为流,Java 和 Scala 现在可以使用许多相同的通用函数方法: + +- `map` +- `filter` +- `forEach`/`foreach` +- `findFirst`/`find` +- `reduce` + +如果您习惯在 Java 中将这些方法与 lambda 表达式一起使用,您会发现在 Scala 的 [集合类][collections-classes] 上使用相同的方法很容易。 + +Scala 也有_数十个_其他 [集合方法][collections-methods],包括 `head`、`tail`、`drop`、`take`、`distinct`、`flatten` 等等。 +起初你可能想知道为什么会有这么多方法,但是在使用 Scala 之后你会意识到_因为_有这些方法,你很少需要再编写自定义的 `for` 循环了。 + +(这也意味着你也很少需要_读_自定义的 `for` 循环。 +因为开发人员倾向于在_读_代码上花费的时间是_编写_代码的十倍,这很重要。) + +## 元组 + +Java 元组是这样创建的: + +```scala +Pair pair = + new Pair("Eleven", 11); + +Triplet triplet = + Triplet.with("Eleven", 11, 11.0); +Quartet triplet = + Quartet.with("Eleven", 11, 11.0, new Person("Eleven")); +``` + +其他 Java 元组名称是 Quintet、Sextet、Septet、Octet、Ennead、Decade。 + +Scala 中任何大小的元组都是通过将值放在括号内来创建的,如下所示: + +```scala +val a = ("eleven") +val b = ("eleven", 11) +val c = ("eleven", 11, 11.0) +val d = ("eleven", 11, 11.0, Person("Eleven")) +``` + +## 枚举 + +本节比较 Java 和 Scala 中的枚举。 + +### 基本枚举: + + + + + + + + + + +
+ enum Color { +
  RED, GREEN, BLUE +
}
+
+ enum Color: +
  case Red, Green, Blue
+
+ +### 参数化的枚举: + + + + + + + + + + +
+ enum Color { +
  Red(0xFF0000), +
  Green(0x00FF00), +
  Blue(0x0000FF); +
+
  private int rgb; +
+
  Color(int rgb) { +
    this.rgb = rgb; +
  } +
}
+
+ enum Color(val rgb: Int): +
  case Red   extends Color(0xFF0000) +
  case Green extends Color(0x00FF00) +
  case Blue  extends Color(0x0000FF)
+
+ +### 用户定义的枚举成员: + + + + + + + + + + +
+ enum Planet { +
  MERCURY (3.303e+23, 2.4397e6), +
  VENUS   (4.869e+24, 6.0518e6), +
  EARTH   (5.976e+24, 6.37814e6); +
  // more planets ... +
+
  private final double mass; +
  private final double radius; +
+
  Planet(double mass, double radius) { +
    this.mass = mass; +
    this.radius = radius; +
  } +
+
  public static final double G = +
    6.67300E-11; +
+
  private double mass() { +
    return mass; +
  } +
+
  private double radius() { +
    return radius; +
  } +
+
  double surfaceGravity() { +
    return G * mass / +
      (radius * radius); +
  } +
+
  double surfaceWeight( +
    double otherMass +
  ) { +
    return otherMass * +
      surfaceGravity(); +
  } +
+
}
+
+ enum Planet( +
  mass: Double, +
  radius: Double +
): +
  case Mercury extends
    Planet(3.303e+23, 2.4397e6) +
  case Venus extends
    Planet(4.869e+24, 6.0518e6) +
  case Earth extends
    Planet(5.976e+24, 6.37814e6) +
    // more planets ... +
+
  private final val G = 6.67300E-11 +
+
  def surfaceGravity =
    G * mass / (radius * radius) +
+
  def surfaceWeight(otherMass: Double) +
    = otherMass * surfaceGravity
+
+ +## 异常和错误处理 + +本节介绍 Java 和 Scala 中的异常处理之间的差异。 + +### Java 使用检查异常 + +Java 使用检查的异常,因此在 Java 代码中,您历来编写过 `try`/`catch`/`finally` 块,以及方法上的 `throws` 子句: + +```scala +public int makeInt(String s) +throws NumberFormatException { + // code here to convert a String to an int +} +``` + +### Scala 不使用检查异常 + +Scala 的习惯用法是_不_使用这样的检查异常。 +在处理可能抛出异常的代码时,您可以使用 `try`/`catch`/`finally` 块从抛出异常的代码中捕获异常,但是如何从那里开始的方式是不同的。 + +解释这一点的最好方法是说 Scala 代码是由具有返回值的_表达式_组成的。 +其结果是,最终你写代就像写一系列代数表达式: + +```scala +val a = f(x) +val b = g(a,z) +val c = h(b,y) +``` + +这很好,它只是代数。 +您创建方程来解决小问题,然后组合方程来解决更大的问题。 + +非常重要的是——正如你在代数课程中所记得的那样——代数表达式不会短路——它们不会抛出会破坏一系列方程的异常。 + +因此,在 Scala 中,我们的方法不会抛出异常。 +相反,它们返回像 `Option` 这样的类型。 +例如,这个 `makeInt` 方法捕获一个可能的异常并返回一个 `Option` 值: + +```scala +def makeInt(s: String): Option[Int] = + try + Some(s.toInt) + catch + case e: NumberFormatException => None +``` + +Scala `Option` 类似于 Java `Optional` 类。 +如图所示,如果 string 到 int 的转换成功,则在 `Some` 值中返回 `Int`,如果失败,则返回 `None` 值。 +`Some` 和 `None` 是 `Option` 的子类型,因此该方法被声明为返回 `Option[Int]` 类型。 + +当您有一个 `Option` 值时,例如 `makeInt` 返回的值,有很多方法可以使用它,具体取决于您的需要。 +此代码显示了一种可能的方法: + +```scala +makeInt(aString) match + case Some(i) => println(s"Int i = $i") + case None => println(s"Could not convert $aString to an Int.") +``` + +`Option` 在 Scala 中很常用,它内置在标准库的许多类中。 +其他类似的类的集合,例如 Try/Success/Failure 和 Either/Left/Right,提供了更大的灵活性。 + +有关在 Scala 中处理错误和异常的更多信息,请参阅 [函数式错误处理][error-handling] 部分。 + +## Scala 独有的概念 + +以上就是 Java 和 Scala 语言的比较。 + +Scala 中还有其他一些概念目前在 Java 11 中是没有的。 +这包括: + +- 与 Scala 的 [上下文抽象][contextual] 相关的一切 +- 几个 Scala 方法特性: + - 多参数列表 + - 默认参数值 + - 调用方法时使用命名参数 +- 样例类(如 Java 14 中的“记录”)、样例对象以及伴生类和对象(参见 [领域建模][modeling-intro])一章 +- 创建自己的控制结构和 DSL 的能力 +- [顶级定义][toplevel] +- 模式匹配 +- `match` 表达式的高级特性 +- 类型 lambdas +- trait参数 +- [不透明类型别名][opaque] +- [多元相等性][equality] +- [类型类][type-classes] +- 中缀方法 +- 宏和元编程 + + +[collections-classes]: {% link _overviews/scala3-book/collections-classes.md %} +[collections-methods]: {% link _overviews/scala3-book/collections-methods.md %} +[control]: {% link _overviews/scala3-book/control-structures.md %} +[equality]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} +[error-handling]: {% link _overviews/scala3-book/fp-functional-error-handling.md %} +[extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} +[hofs]: {% link _overviews/scala3-book/fun-hofs.md %} +[imports]: {% link _overviews/scala3-book/packaging-imports.md %} +[modeling-intro]: {% link _overviews/scala3-book/domain-modeling-intro.md %} +[modeling-oop]: {% link _overviews/scala3-book/domain-modeling-oop.md %} +[opaque]: {% link _overviews/scala3-book/types-opaque-types.md %} +[tools]: {% link _overviews/scala3-book/scala-tools.md %} +[toplevel]: {% link _overviews/scala3-book/taste-toplevel-definitions.md %} +[type-classes]: {% link _overviews/scala3-book/ca-type-classes.md %} + +[concurrency]: {% link _overviews/scala3-book/concurrency.md %} +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[control]: {% link _overviews/scala3-book/control-structures.md %} +[fp-intro]: {% link _overviews/scala3-book/fp-intro.md %} +[intersection-types]: {% link _overviews/scala3-book/types-intersection.md %} +[modeling-fp]: {% link _overviews/scala3-book/domain-modeling-fp.md %} +[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} +[union-types]: {% link _overviews/scala3-book/types-union.md %} + +
diff --git a/_zh-cn/overviews/scala3-book/scala-for-javascript-devs.md b/_zh-cn/overviews/scala3-book/scala-for-javascript-devs.md new file mode 100644 index 0000000000..13cb85cec2 --- /dev/null +++ b/_zh-cn/overviews/scala3-book/scala-for-javascript-devs.md @@ -0,0 +1,1330 @@ +--- +title: Scala for JavaScript Developers +type: chapter +description: This chapter provides an introduction to Scala 3 for JavaScript developers +num: 74 +previous-page: scala-for-java-devs +next-page: scala-for-python-devs +--- + + +{% include_relative scala4x.css %} +
+ +此页面提供了 JavaScript 和 Scala 编程语言之间的比较。 +它适用于了解 JavaScript 并希望了解 Scala 的程序员,特别是通过查看 JavaScript 语言功能与 Scala 比较的示例。 + +## 概述 + +本节对以下各节进行了相对简短的介绍和总结。 +它从高层次上介绍了 JavaScript 和 Scala 之间的异同,然后介绍了您在每天编写代码时会遇到的差异。 + +### 高层次的相似性 + +在高层次上,Scala 与 JavaScript 有以下相似之处: + +- 两者都被认为是高级编程语言,您不必关心指针和手动内存管理等低级概念 +- 两者都有一个相对简单、简洁的语法 +- 两者都支持 C/C++/Java 风格的花括号语法,用于编写方法和其他代码块 +- 两者都包括面向对象编程 (OOP) 的特性(如类) +- 两者都包含用于 [函数式编程][fp-intro] (FP) 的(如 lambda) +- JavaScript 在浏览器和 Node.js 等其他环境中运行。 + Scala 的 [Scala.js](https://www.scala-js.org) 风格以 JavaScript 为目标,因此 Scala 程序可以在相同的环境中运行。 +- 开发人员使用 [Node.js](https://nodejs.org) 在 JavaScript 和 Scala 中编写服务器端应用程序; [Play Framework](https://www.playframework.com/) 之类的项目也可以让您在 Scala 中编写服务器端应用程序 +- 两种语言都有相似的 `if` 语句、`while` 循环和 `for` 循环 +- 从 [在这个 Scala.js 页面](https://www.scala-js.org/libraries/index.html) 开始,您会发现许多支持 React、Angular、jQuery 和许多其他 JavaScript 和Scala 库 +- JavaScript 对象是可变的;以命令式风格编写时,Scala 对象_可以_是可变的 +- JavaScript 和 Scala 都支持 _promises_ 作为处理异步计算结果的一种方式([Scala concurrency][concurrency] 使用期货和承诺) + +### 高层次差异 + +同样在高层次上,JavaScript 和 Scala 之间的一些区别是: + +- JavaScript 是动态类型的,Scala 是静态类型的 + - 尽管 Scala 是静态类型的,但类型推断之类的特性让它感觉像是一种动态语言(正如您将在下面的示例中看到的那样) +- Scala 惯用语默认支持不变性:鼓励您使用不可变变量和不可变集合 +- Scala 语法简洁易读;我们称之为_表现力_ +- Scala 是一种纯 OOP 语言,因此每个对象都是类的一个实例,而像运算符一样的符号 `+` 和 `+=` 是真正的方法;这意味着您可以创建自己的方法作为运算符 +- 作为一种纯 OOP 语言和纯 FP 语言,Scala 鼓励 OOP 和 FP 的融合,具有用于逻辑的函数和用于模块化的不可变对象 +- Scala 拥有最先进的第三方开源函数式编程库 +- Scala 中的一切都是一个_表达式_:像 `if` 语句、`for` 循环、`match` 表达式,甚至 `try`/`catch` 表达式都有返回值 +- [Scala Native](https://scala-native.readthedocs.io/en/v0.3.9-docs) 项目让您可以编写“系统”级代码,也可以编译为本机可执行文件 + +### 编程层次差异 + +在较低的层次上,这些是您在编写代码时每天都会看到的一些差异: + +- Scala 变量和参数使用 `val`(不可变,如 JavaScript `const`)或 `var`(可变,如 JavaScript `var` 或 `let`)定义 +- Scala 不在行尾使用分号 +- Scala 是静态类型的,尽管在许多情况下您不需要声明类型 +- Scala 使用 trait 作为接口并创建_混搭_ +- 除了简单的 `for` 循环之外,Scala 还具有强大的 `for` comprehensions,可以根据您的算法产生结果 +- 模式匹配和 `match` 表达式将改变你编写代码的方式 +- Scala 的 Scala 的 [上下文抽象][contextual] 和 _术语推导_ 提供了一系列特性: + - [扩展方法][extension-methods] 允许您在不破坏模块化的情况下向封闭类添加新功能,方法是仅在特定范围内可用(与猴子补丁相反,它会污染代码的其他区域) + - [给实例][givens] 让您定义编译器可以用来为您合成代码的术语 + - 类型安全和[多元等式][multiversal]让您将相等比较——在编译时——仅限于那些有意义的比较 +- 由于名称参数、中缀符号、可选括号、扩展方法和 [高阶函数][hofs] 等功能,您可以创建自己的“控制结构”和 DSL +- 您可以在本书中阅读到许多其他好东西:样例类、伴生类和对象、宏、[联合][union-type]和[交集][intersection-types]类型、多参数列表、命名参数等 + +## 变量和类型 + +### 注释 + + + + + + + + + + +
+ // +
/* ... */ +
/** ... */
+
+ // +
/* ... */ +
/** ... */
+
+ +### 可变变量 + + + + + + + + + + +
+ let   // now preferred for mutable +
var   // old mutable style
+
+ var  // used for mutable variables +
+ +### 不可变变量 + + + + + + + + + + +
+ const +
+ val +
+ +Scala 的经验法则是使用 `val` 声明变量,除非有特定原因需要可变变量。 + +## 命名标准 + +JavaScript 和 Scala 通常使用相同的 _CamelCase_ 命名标准。 +变量命名为 `myVariableName`,方法命名为 `lastIndexOf`,类和对象命名为 `Animal` 和 `PrintedBook` 。 + +## 字符串 + +JavaScript 和 Scala 中字符串的许多用法相似,但 Scala 仅对简单字符串使用双引号,对多行字符串使用三引号。 + +### 字符串基础 + + + + + + + + + + +
+ // use single- or double-quotes +
let msg = 'Hello, world'; +
let msg = "Hello, world";
+
+ // use only double-quotes +
val msg = "Hello, world"
+
+ +### 插入 + + + + + + + + + + +
+ let name = 'Joe'; +
+
// JavaScript uses backticks +
let msg = `Hello, ${name}`;
+
+ val name = "Joe" +
val age = 42 +
val weight = 180.5 +
+
// use `s` before a string for simple interpolation +
println(s"Hi, $name")   // "Hi, Joe" +
println(s"${1 + 1}")    // "2" +
+
// `f` before a string allows printf-style formatting. +
// this example prints: +
// "Joe is 42 years old, and weighs" +
// "180.5 pounds." +
println(f"$name is $age years old, and weighs $weight%.1f pounds.")
+
+ +### 带插入的多行字符串 + + + + + + + + + + +
+ let name = "joe"; +
let str = ` +
Hello, ${name}. +
This is a multiline string. +
`; +
+
+ val name = "Martin Odersky" +
+
val quote = s""" +
|$name says +
|Scala is a fusion of +
|OOP and FP. +
""".stripMargin.replaceAll("\n", " ").trim +
+
// result: +
// "Martin Odersky says Scala is a fusion of OOP and FP." +
+
+ +JavaScript 和 Scala 也有类似的处理字符串的方法,包括 `charAt`、`concat`、`indexOf` 等等。 +`\n`、`\f`、`\t` 等转义字符在两种语言中也是相同的。 + +## 数字和算术 + +JavaScript 和 Scala 之间的数字运算符很相似。 +最大的不同是 Scala 不提供 `++` 和 `--` 运算符。 + +### 数字运算符: + + + + + + + + + + +
+ let x = 1; +
let y = 2.0; +
  +
let a = 1 + 1; +
let b = 2 - 1; +
let c = 2 * 2; +
let d = 4 / 2; +
let e = 5 % 2; +
+
+ val x = 1 +
val y = 2.0 +
  +
val a = 1 + 1 +
val b = 2 - 1 +
val c = 2 * 2 +
val d = 4 / 2 +
val e = 5 % 2 +
+
+ +### 自增和自减: + + + + + + + + + + +
+ i++; +
i += 1; +
+
i--; +
i -= 1;
+
+ i += 1; +
i -= 1;
+
+ +或许最大的区别在于像`+`和`-`这样的“操作符”在Scala中实际上是_方法_,而不是操作符。 +Scala 数字也有这些相关的方法: + +```scala +var a = 2 +a *= 2 // 4 +a /= 2 // 2 +``` + +Scala 的 `Double` 类型最接近于 JavaScript 的默认 `number` 类型, +`Int` 表示有符号的 32 位整数值,而 `BigInt` 对应于 JavaScript 的 `bigint`。 + +这些是 Scala `Int` 和 `Double` 值。 +请注意,类型不必显式声明: + +```scala +val i = 1 // Int +val d = 1.1 // Double +``` + +你可以按需要使用其它数字类型: + +```scala +val a: Byte = 0 // Byte = 0 +val a: Double = 0 // Double = 0.0 +val a: Float = 0 // Float = 0.0 +val a: Int = 0 // Int = 0 +val a: Long = 0 // Long = 0 +val a: Short = 0 // Short = 0 + +val x = BigInt(1_234_456_789) +val y = BigDecimal(1_234_456.890) +``` + +### 布尔值 + +两个语言都在布尔值中用 `true` 和 `false`。 + + + + + + + + + + +
+ let a = true; +
let b = false;
+
+ val a = true +
val b = false
+
+ +## 日期 + +日期是两种语言中另一种常用的类型。 + +### 获取当前日期: + + + + + + + + + + +
+ let d = new Date();
+
// result: +
// Sun Nov 29 2020 18:47:57 GMT-0700 (Mountain Standard Time) +
+
+ // different ways to get the current date and time +
import java.time.* +
+
val a = LocalDate.now +
    // 2020-11-29 +
val b = LocalTime.now +
    // 18:46:38.563737 +
val c = LocalDateTime.now +
    // 2020-11-29T18:46:38.563750 +
val d = Instant.now +
    // 2020-11-30T01:46:38.563759Z
+
+ +### 指定不同的日期: + + + + + + + + + + +
+ let d = Date(2020, 1, 21, 1, 0, 0, 0); +
let d = Date(2020, 1, 21, 1, 0, 0); +
let d = Date(2020, 1, 21, 1, 0); +
let d = Date(2020, 1, 21, 1); +
let d = Date(2020, 1, 21);
+
+ val d = LocalDate.of(2020, 1, 21) +
val d = LocalDate.of(2020, Month.JANUARY, 21) +
val d = LocalDate.of(2020, 1, 1).plusDays(20) +
+
+ +在这种情况下,Scala 使用 Java 附带的日期和时间类。 +JavaScript 和 Scala 之间的许多日期/时间方法是相似的。 +有关详细信息,请参阅 [_java.time_ 包](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/package-summary.html)。 + +## 函数 + +在 JavaScript 和 Scala 中,函数都是对象,因此它们的功能相似,但它们的语法和术语略有不同。 + +### 命名函数,一行: + + + + + + + + + + +
+ function add(a, b) { +
  return a + b; +
} +
add(2, 2);   // 4
+
+ // technically this is a method, not a function +
def add(a: Int, b: Int) = a + b +
add(2, 2)   // 4
+
+ +### 命名函数,多行: + + + + + + + + + + +
+ function addAndDouble(a, b) { +
  // imagine this requires +
  // multiple lines +
  return (a + b) * 2 +
}
+
+ def addAndDouble(a: Int, b: Int): Int = +
  // imagine this requires +
  // multiple lines +
  (a + b) * 2
+
+ +在 Scala 中,显示 `Int` 返回类型是可选的。 +它_不_显示在 `add` 示例中,而_是_显示在 `addAndDouble` 示例中,因此您可以看到这两种方法。 + +## 匿名函数 + +JavaScript 和 Scala 都允许您定义匿名函数,您可以将其传递给其他函数和方法。 + +### 箭头和匿名函数 + + + + + + + + + + +
+ // arrow function +
let log = (s) => console.log(s) +
+
// anonymous function +
let log = function(s) { +
  console.log(s); +
} +
+
// use either of those functions here +
function printA(a, log) { +
  log(a); +
}
+
+ // a function (an anonymous function assigned to a variable) +
val log = (s: String) => console.log(s) +
+
// a scala method. methods tend to be used much more often, +
// probably because they’re easier to read. +
def log(a: Any) = console.log(a) +
+
// a function or a method can be passed into another +
// function or method +
def printA(a: Any, f: log: Any => Unit) = log(a) +
+
+ +在 Scala 中,您很少使用所示的第一种语法来定义函数。 +相反,您经常在使用点定义匿名函数。 +许多集合方法是 [高阶函数][hofs]并接受函数参数,因此您编写如下代码: + +```scala +// map method, long form +List(1,2,3).map(i => i * 10) // List(10,20,30) + +// map, short form (which is more commonly used) +List(1,2,3).map(_ * 10) // List(10,20,30) + +// filter, short form +List(1,2,3).filter(_ < 3) // List(1,2) + +// filter and then map +List(1,2,3,4,5).filter(_ < 3).map(_ * 10) // List(10, 20) +``` + +## 类 + +Scala 既有类也有样例类。 +_类_ 与 JavaScript 类相似,通常用于 [OOP 风格应用程序][modeling-oop](尽管它们也可以在 FP 代码中使用),并且 _样例类_有附加的特性,这让它在 [FP 风格应用][modeling-fp]中很有用。 + +下面的例子展示了如何创建几个类型作为枚举,然后定义一个 OOP 风格的 `Pizza` 类。 +最后,创建并使用了一个 `Pizza` 实例: + +```scala +// create some enumerations that the Pizza class will use +enum CrustSize: + case Small, Medium, Large + +enum CrustType: + case Thin, Thick, Regular + +enum Topping: + case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions + +// import those enumerations and the ArrayBuffer, +// so the Pizza class can use them +import CrustSize.* +import CrustType.* +import Topping.* +import scala.collection.mutable.ArrayBuffer + +// define an OOP style Pizza class +class Pizza( + var crustSize: CrustSize, + var crustType: CrustType +): + + private val toppings = ArrayBuffer[Topping]() + + def addTopping(t: Topping): Unit = + toppings += t + + def removeTopping(t: Topping): Unit = + toppings -= t + + def removeAllToppings(): Unit = + toppings.clear() + + override def toString(): String = + s""" + |Pizza: + | Crust Size: ${crustSize} + | Crust Type: ${crustType} + | Toppings: ${toppings} + """.stripMargin + +end Pizza + +// create a Pizza instance +val p = Pizza(Small, Thin) + +// change the crust +p.crustSize = Large +p.crustType = Thick + +// add and remove toppings +p.addTopping(Cheese) +p.addTopping(Pepperoni) +p.addTopping(BlackOlives) +p.removeTopping(Pepperoni) + +// print the pizza, which uses its `toString` method +println(p) +``` + +## 接口、trait 和继承 + +Scala 使用 trait 作为接口,也可以创建混搭。 +trait 可以有抽象和具体的成员,包括方法和字段。 + +这个例子展示了如何定义两个 traits,创建一个扩展和实现这些 traits 的类,然后创建和使用该类的一个实例: + +```scala +trait HasLegs: + def numLegs: Int + def walk(): Unit + def stop() = println("Stopped walking") + +trait HasTail: + def wagTail(): Unit + def stopTail(): Unit + +class Dog(var name: String) extends HasLegs, HasTail: + val numLegs = 4 + def walk() = println("I’m walking") + def wagTail() = println("⎞⎜⎛ ⎞⎜⎛") + def stopTail() = println("Tail is stopped") + override def toString = s"$name is a Dog" + +// create a Dog instance +val d = Dog("Rover") + +// use the class’s attributes and behaviors +println(d.numLegs) // 4 +d.wagTail() // "⎞⎜⎛ ⎞⎜⎛" +d.walk() // "I’m walking" +``` + +## 控制结构 + +除了在 JavaScript 中使用 `===` 和 `!==` 之外,比较和逻辑运算符在 JavaScript 和 Scala 中几乎相同。 + +{% comment %} +TODO: Sébastien mentioned that `===` is closest to `eql` in Scala. Update this area. +{% endcomment %} + +### 比较运算符 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JavaScriptScala
+ == + ==
+ === + ==
+ != + !=
+ !== + !=
+ > + >
+ < + <
+ >= + >=
+ <= + <=
+ +### 逻辑运算符 + + + + + + + + + + + + +
JavaScriptScala
+ && +
|| +
!
+
+ && +
|| +
!
+
+ +## if/then/else 表达式 + +JavaScript和 Scala if/then/else 语句相似。 +在 Scala 2 中它们几乎相同,但在 Scala 3 中,花括号不再是必需的(尽管它们仍然可以使用)。 + +### `if` 语句,单行: + + + + + + + + + + +
+ if (x == 1) { console.log(1); } +
+ if x == 1 then println(x) +
+ +### `if` 语句,多行: + + + + + + + + + + +
+ if (x == 1) { +
  console.log("x is 1, as you can see:") +
  console.log(x) +
}
+
+ if x == 1 then +
  println("x is 1, as you can see:") +
  println(x)
+
+ +### if, else if, else: + + + + + + + + + + +
+ if (x < 0) { +
  console.log("negative") +
} else if (x == 0) { +
  console.log("zero") +
} else { +
  console.log("positive") +
}
+
+ if x < 0 then +
  println("negative") +
else if x == 0 +
  println("zero") +
else +
  println("positive")
+
+ +### 从 `if` 返回值: + +JavaScript 使用三元运算符,Scala 像往常一样使用它的 `if` 表达式: + + + + + + + + + + +
+ let minVal = a < b ? a : b; +
+ val minValue = if a < b then a else b +
+ +### `if` 作为方法体: + +Scala 方法往往很短,您可以轻松地使用 `if` 作为方法体: + + + + + + + + + + +
+ function min(a, b) { +
  return (a < b) ? a : b; +
}
+
+ def min(a: Int, b: Int): Int = +
  if a < b then a else b
+
+ +在 Scala 3 中,如果您愿意,您仍然可以使用“花括号”样式。 +例如,您可以像这样编写 if/else-if/else 表达式: + +```scala +if (i == 0) { + println(0) +} else if (i == 1) { + println(1) +} else { + println("other") +} +``` + +## 循环 + +JavaScript 和 Scala 都有 `while` 循环和 `for` 循环。 +Scala 曾经有 do/while 循环,但它们已从语言中删除。 + +### `while` 循环: + + + + + + + + + + +
+ let i = 0; +
while (i < 3) { +
  console.log(i); +
  i++; +
}
+
+ var i = 0; +
while i < 3 do +
  println(i) +
  i += 1
+
+ +如果你愿意,Scala 代码也可以写成这样: + +```scala +var i = 0 +while (i < 3) { + println(i) + i += 1 +} +``` + +以下示例展示了 JavaScript 和 Scala 中的“for”循环。 +他们假设您可以使用这些集合: + +```scala +// JavaScript +let nums = [1, 2, 3]; + +// Scala +val nums = List(1, 2, 3) +``` + +### `for` 循环,单行: + + + + + + + + + + +
+ // newer syntax +
for (let i of nums) { +
  console.log(i); +
} +
+
// older +
for (i=0; i<nums.length; i++) { +
  console.log(nums[i]); +
}
+
+ // preferred +
for i <- ints do println(i) +
+
// also available +
for (i <- ints) println(i)
+
+ +### `for` 循环,在循环体内多行 + + + + + + + + + + +
+ // preferred +
for (let i of nums) { +
  let j = i * 2; +
  console.log(j); +
} +
+
// also available +
for (i=0; i<nums.length; i++) { +
  let j = nums[i] * 2; +
  console.log(j); +
}
+
+ // preferred +
for i <- ints do +
  val i = i * 2 +
  println(j) +
+
// also available +
for (i <- nums) { +
  val j = i * 2 +
  println(j) +
}
+
+ +### 在 `for` 循环中有多个生成器 + + + + + + + + + + +
+ let str = "ab"; +
for (let i = 1; i < 3; i++) { +
  for (var j = 0; j < str.length; j++) { +
    for (let k = 1; k < 11; k++) { +
      let c = str.charAt(j); +
      console.log(`i: ${i} j: ${c} k: ${k}`); +
    } +
  } +
}
+
+ for +
  i <- 1 to 2 +
  j <- 'a' to 'b' +
  k <- 1 to 10 by 5 +
do +
  println(s"i: $i, j: $j, k: $k")
+
+ +### 带守卫的生成器 + +_守卫_是 `for` 表达式中的 `if` 表达式的名称。 + + + + + + + + + + +
+ for (let i = 0; i < 10; i++) { +
  if (i % 2 == 0 && i < 5) { +
    console.log(i); +
  } +
}
+
+ for +
  i <- 1 to 10 +
  if i % 2 == 0 +
  if i < 5 +
do +
  println(i)
+
+ +### `for` comprehension + +`for` comprehension 是一个 `for` 循环,它使用 `yield` 返回(产生)一个值。 它们经常在 Scala 中使用。 + + + + + + + + + + +
+ N/A +
+ val list = +
  for +
    i <- 1 to 3 +
  yield +
    i * 10 +
// result: Vector(10, 20, 30)
+
+ +## switch & match + +JavaScript 有 `switch` 语句,Scala 有 `match` 表达式。 +就像 Scala 中的所有其他东西一样,这些确实是_表达式_,这意味着它们返回一个结果: + +```scala +val day = 1 + +// later in the code ... +val monthAsString = day match + case 1 => "January" + case 2 => "February" + case _ => "Other" +``` + +`match` 表达式可以在每个 `case` 语句中处理多个匹配项: + +```scala +val numAsString = i match + case 1 | 3 | 5 | 7 | 9 => "odd" + case 2 | 4 | 6 | 8 | 10 => "even" + case _ => "too big" +``` + +它们也可以用于方法体: + +```scala +def isTruthy(a: Matchable) = a match + case 0 | "" => false + case _ => true + +def isPerson(x: Matchable): Boolean = x match + case p: Person => true + case _ => false +``` + +`match` 表达式有许多其他模式匹配选项。 + +## 集合类 + +Scala 有不同的 [集合类][collections-classes] 来满足不同的需求。 + +常见的_不可变_序列是: + +- `List` +- `Vector` + +常见的_可变_序列是: + +- `Array` +- `ArrayBuffer` + +Scala 还有可变和不可变的 Map 和 Set。 + +这是创建常见 Scala 集合类型的方式: + +```scala +val strings = List("a", "b", "c") +val strings = Vector("a", "b", "c") +val strings = ArrayBuffer("a", "b", "c") + +val set = Set("a", "b", "a") // result: Set("a", "b") +val map = Map( + "a" -> 1, + "b" -> 2, + "c" -> 3 +) +``` + +### 集合上的方法 + +以下示例展示了使用 Scala 集合的许多不同方法。 + +### 填充列表: + +```scala +// to, until +(1 to 5).toList // List(1, 2, 3, 4, 5) +(1 until 5).toList // List(1, 2, 3, 4) + +(1 to 10 by 2).toList // List(1, 3, 5, 7, 9) +(1 until 10 by 2).toList // List(1, 3, 5, 7, 9) +(1 to 10).by(2).toList // List(1, 3, 5, 7, 9) + +('d' to 'h').toList // List(d, e, f, g, h) +('d' until 'h').toList // List(d, e, f, g) +('a' to 'f').by(2).toList // List(a, c, e) + +// range method +List.range(1, 3) // List(1, 2) +List.range(1, 6, 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) +``` + +### 序列上的函数式方法: + +```scala +// these examples use a List, but they’re the same with Vector +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) +a.contains(20) // true +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.last // 10 +a.lastOption // Some(10) +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) + +// map, flatMap +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) + +List(1,2,3).updated(0,10) // List(10, 2, 3) +List(2,4).union(List(1,3)) // List(2, 4, 1, 3) + +// zip +val women = List("Wilma", "Betty") // List(Wilma, Betty) +val men = List("Fred", "Barney") // List(Fred, Barney) +val couples = women.zip(men) // List((Wilma,Fred), (Betty,Barney)) +``` + +Scala 有_很多_更多可供您使用的方法。 +所有这些方法的好处是: + +- 您不必编写自定义的 `for` 循环来解决问题 +- 当你阅读别人的代码时,你不必阅读他们自定义的 `for` 循环; 你只会找到像这样的常用方法,因此更容易阅读来自不同项目的代码 + +### 元组 + +当您想将多个数据类型放在同一个列表中时,JavaScript 允许您这样做: + +```javascript +stuff = ["Joe", 42, 1.0]; +``` + +在 Scala 中你这样做: + +```scala +val a = ("eleven") +val b = ("eleven", 11) +val c = ("eleven", 11, 11.0) +val d = ("eleven", 11, 11.0, Person("Eleven")) +``` + +在 Scala 中,这些类型称为元组,如图所示,它们可以包含一个或多个元素,并且元素可以具有不同的类型。 +访问它们的元素就像访问 `List`、`Vector` 或 `Array` 的元素一样: + +```scala +d(0) // "eleven" +d(1) // 11 +``` + +### 枚举 + +JavaScript 没有枚举,但你可以这样做: + +```javascript +let Color = { + RED: 1, + GREEN: 2, + BLUE: 3 +}; +Object.freeze(Color); +``` + +在 Scala 3 中,您可以使用枚举做很多事情。 +您可以创建该代码的等效代码: + +```scala +enum Color: + case Red, Green, Blue +``` + +你可以创建带参数的枚举: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +你也可以创建用户自定义的枚举成员: + +```scala +enum Planet(mass: Double, radius: Double): + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Venus extends Planet(4.869e+24,6.0518e6) + case Earth extends Planet(5.976e+24,6.37814e6) + // more planets here ... + + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity +``` + +## Scala.js DOM 代码 + +Scala.js 允许您编写 Scala 代码,这些代码编译为 JavaScript 代码,然后可以在浏览器中使用。 +该方法类似于 TypeScript、ReScript 和其他编译为 JavaScript 的语言。 + +包含必要的库并在项目中导入必要的包后,编写 Scala.js 代码看起来与编写 JavaScript 代码非常相似: + +```scala +// show an alert dialog on a button click +jQuery("#hello-button").click{() => + dom.window.alert("Hello, world") +} + +// define a button and what should happen when it’s clicked +val btn = button( + "Click me", + onclick := { () => + dom.window.alert("Hello, world") + }) + +// create two divs with css classes, an h2 element, and the button +val content = + div(cls := "foo", + div(cls := "bar", + h2("Hello"), + btn + ) + ) + +// add the content to the DOM +val root = dom.document.getElementById("root") +root.innerHTML = "" +root.appendChild(content.render) +``` + +请注意,尽管 Scala 是一种类型安全的语言,但在上面的代码中没有声明任何类型。 +Scala 强大的类型推断能力通常使 Scala 代码看起来像是动态类型的。 +但它是类型安全的,因此您可以在开发周期的早期捕获许多类错误。 + +## 其他 Scala.js 资源 + +Scala.js 网站为对使用 Scala.js 感兴趣的 JavaScript 开发人员提供了极好的教程集。 +以下是他们的一些初始教程: + +- [基础教程(创建第一个 Scala.js 项目)](https://www.scala-js.org/doc/tutorial/basic/) +- [适用于 JavaScript 开发人员的 Scala.js](https://www.scala-js.org/doc/sjs-for-js/) +- [从 ES6 到 Scala:基础](https://www.scala-js.org/doc/sjs-for-js/es6-to-scala-part1.html) +- [从 ES6 到 Scala:集合](https://www.scala-js.org/doc/sjs-for-js/es6-to-scala-part2.html) +- [从 ES6 到 Scala:高级](https://www.scala-js.org/doc/sjs-for-js/es6-to-scala-part3.html) + +## Scala 独有的概念 + +Scala 中还有其他一些概念目前在 JavaScript 中没有等效的概念: + +- 几乎所有与[上下文抽象][contextual]相关的东西 +- 方法特性: + - 多个参数列表 + - 调用方法时使用命名参数 +- 使用 trait 作为接口 +- 样例类 +- 伴生类和对象 +- 创建自己的[控制结构][control]和 DSL 的能力 +- `match` 表达式和模式匹配的高级功能 +- `for` comprehension +- 中缀方法 +- 宏和元编程 +- 更多的 ... + + +[collections-classes]: {% link _overviews/scala3-book/collections-classes.md %} +[concurrency]: {% link _overviews/scala3-book/concurrency.md %} +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[control]: {% link _overviews/scala3-book/control-structures.md %} +[extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[fp-intro]: {% link _overviews/scala3-book/fp-intro.md %} +[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} +[hofs]: {% link _overviews/scala3-book/fun-hofs.md %} +[intersection-types]: {% link _overviews/scala3-book/types-intersection.md %} +[modeling-fp]: {% link _overviews/scala3-book/domain-modeling-fp.md %} +[modeling-oop]: {% link _overviews/scala3-book/domain-modeling-oop.md %} +[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} +[union-types]: {% link _overviews/scala3-book/types-union.md %} + +
diff --git a/_zh-cn/overviews/scala3-book/scala-for-python-devs.md b/_zh-cn/overviews/scala3-book/scala-for-python-devs.md new file mode 100644 index 0000000000..30b39a06ef --- /dev/null +++ b/_zh-cn/overviews/scala3-book/scala-for-python-devs.md @@ -0,0 +1,1354 @@ +--- +title: Scala for Python Developers +type: chapter +description: This page is for Python developers who are interested in learning about Scala 3. +num: 75 +previous-page: scala-for-javascript-devs +next-page: +--- + +{% include_relative scala4x.css %} + +
+ +{% comment %} + +NOTE: Hopefully someone with more Python experience can give this a thorough review. + +NOTE: On this page (https://contributors.scala-lang.org/t/feedback-sought-optional-braces/4702/10), Li Haoyi comments: “Python’s success also speaks for itself; beginners certainly don’t pick Python because of performance, ease of installation, packaging, IDE support, or simplicity of the language’s runtime semantics!” I’m not a Python expert, so these points are good to know, though I don’t want to go negative in any comparisons. +It’s more like thinking, “Python developers will appreciate Scala’s performance, ease of installation, packaging, IDE support, etc.” +{% endcomment %} + +{% comment %} +TODO: We should probably go through this document and add links to our other detail pages, when time permits. +{% endcomment %} + +本节提供了 Python 和 Scala 编程语言之间的比较。 +它适用于懂得 Python 并希望了解 Scala 的程序员,特别是通过查看 Python 语言特性与 Scala 比较的示例。 + +## 介绍 + +在进入示例之前,第一部分提供了以下部分的相对简短的介绍和总结。 +这两种语言首先在高层次上进行比较,然后在日常编程层次上进行比较。 + +### 高层次相似性 + +在高层次上,Scala 与 Python 有这些*相似之处*: + +- 两者都是高级编程语言,您不必关心指针和手动内存管理等低级概念 +- 两者都有一个相对简单、简洁的语法 +- 两者都支持[函数式编程][fp-intro] +- 两者都是面向对象的编程 (OOP) 语言 +- 两者都有推导:Python 有列表推导,Scala 有 `for` 推导 +- 两种语言都支持 lambdas 和 [高阶函数][hofs] +- 两者都可以与 [Apache Spark](https://spark.apache.org) 一起用于大数据处理 +- 两者都有很多很棒的库 + +### 高层次差异 + +同样在高层次上,Python 和 Scala 之间的_差异_是: + +- Python 是动态类型的,Scala 是静态类型的 + - 虽然它是静态类型的,但 Scala 的类型推断等特性让它感觉像是一门动态语言 +- Python 被解释,Scala 代码被编译成 _.class_ 文件,并在 Java 虚拟机 (JVM) 上运行 +- 除了在 JVM 上运行之外,[Scala.js](https://www.scala-js.org) 项目允许您使用 Scala 作为 JavaScript 替代品 +- [Scala Native](https://scala-native.readthedocs.io/en/v0.3.9-docs) 项目可让您编写“系统”级代码,并编译为本机可执行文件 +- Scala 中的一切都是一个_表达式_:像 `if` 语句、`for` 循环、`match` 表达式,甚至 `try`/`catch` 表达式都有返回值 +- Scala 习惯默认不变性:鼓励您使用不可变变量和不可变集合 +- Scala 对[并发和并行编程][concurrency]有很好的支持 + +### 编程层次相似性 + +本节介绍您在日常编写代码时会看到 Python 和 Scala 之间的相似之处: + +- Scala 的类型推断常常让人感觉像是一种动态类型语言 +- 两种语言都不使用分号来结束表达式 +- 两种语言都支持使用重要的缩进而不是大括号和圆括号 +- 定义方法的语法类似 +- 两者都有列表、字典(映射)、集合和元组 +- 两者都有映射和过滤的推导 +- 使用 Scala 3 的[顶级定义][toplevel],您可以将方法、字段和其他定义放在任何地方 + - 一个区别是 Python 甚至可以在不声明单个方法的情况下运行,而 Scala 3 不能在顶层做_所有事_;例如,启动 Scala 应用程序需要一个 [main 方法][main-method] (`@main def`) + +### 编程层次差异 + +同样在编程级别,这些是您在编写代码时每天都会看到的一些差异: + +- 在 Scala 中编程感觉非常一致: + - `val` 和 `var` 字段用于定义字段和参数 + - 列表、映射、集合和元组都以类似方式创建和访问;例如,括号用于创建所有类型---`List(1,2,3)`, `Set(1,2,3)`, `Map(1->"one")`---就像创建任何其他 Scala 类 + - [集合类][collections-classes] 通常具有大部分相同的高阶函数 + - 模式匹配在整个语言中一致使用 + - 用于定义传递给方法的函数的语法与用于定义匿名函数的语法相同 +- Scala 变量和参数使用 `val`(不可变)或 `var`(可变)关键字定义 +- Scala 习惯用法更喜欢不可变的数据结构 +- Scala 通过 IntelliJ IDEA 和 Microsoft VS Code 提供了极好的 IDE 支持 +- 注释:Python 使用 `#` 表示注释; Scala 使用 C、C++ 和 Java 样式:`//`、`/*...*/` 和 `/**...*/` +- 命名约定:Python 标准是使用下划线,例如 `my_list`; Scala 使用 `myList` +- Scala 是静态类型的,因此您可以声明方法参数、方法返回值和其他地方的类型 +- 模式匹配和 `match` 表达式在 Scala 中被广泛使用(并且会改变你编写代码的方式) +- Scala 中大量使用 trait;接口和抽象类在 Python 中使用较少 +- Scala 的 [上下文抽象][contextual] 和 _术语推导_提供了一系列不同的特性: + - [扩展方法][extension-method]让您使用清晰的语法轻松地向类添加新功能 + - [多元等式][multiversal] 让您限制相等比较---在编译时——只有那些有意义的比较 +- Scala 拥有最先进的开源函数式编程库(参见 [“Awesome Scala” 列表](https://github.com/lauris/awesome-scala)) +- 借助对象、按名称参数、中缀表示法、可选括号、扩展方法、高阶函数等功能,您可以创建自己的“控制结构”和 DSL +- Scala 代码可以在 JVM 中运行,甚至可以编译为原生代码(使用 [Scala Native](https://github.com/scala-native/scala-native) 和 [GraalVM](https://www.graalvm) .org)) 实现高性能 +- 许多其他好东西:样例类、伴生类和对象、宏、[联合][union-types] 和 [交集][intersection-types] 类型、[顶级定义][toplevel]、数字字面量、多参数列表和更多的 + +### 特性比较示例 + +鉴于该介绍,以下部分提供了 Python 和 Scala 编程语言功能的并排比较。 + +{% comment %} +TODO: Update the Python examples to use four spaces. I started to do this, but then thought it would be better to do that in a separate PR. +{% endcomment %} + +## 注释 + +Python 使用 `#` 表示注释,而 Scala 的注释语法与 C、C++ 和 Java 等语言相同: + + + + + + + + + + +
+ # a comment +
+ // a comment +
/* ... */ +
/** ... */
+
+ +## 变量赋值 + +这些例子演示了如何在 Python 和 Scala 中创建变量。 + +### 创建整数和字符串变量: + + + + + + + + + + +
+ x = 1 +
x = "Hi" +
y = """foo +
       bar +
       baz"""
+
+ val x = 1 +
val x = "Hi" +
val y = """foo +
           bar +
           baz"""
+
+ +### 列表: + + + + + + + + + + +
+ x = [1,2,3] +
+ val x = List(1,2,3) +
+ +### 字典/映射: + + + + + + + + + + +
+ x = { +
  "Toy Story": 8.3, +
  "Forrest Gump": 8.8, +
  "Cloud Atlas": 7.4 +
}
+
+ val x = Map( +
  "Toy Story" -> 8.3, +
  "Forrest Gump" -> 8.8, +
  "Cloud Atlas" -> 7.4 +
)
+
+ +### 集合: + + + + + + + + + + +
+ x = {1,2,3} +
+ val x = Set(1,2,3) +
+ +### 元组: + + + + + + + + + + +
+ x = (11, "Eleven") +
+ val x = (11, "Eleven") +
+ +如果 Scala 字段是可变的,请使用 `var` 而不是 `val` 来定义变量: + +```scala +var x = 1 +x += 1 +``` + +然而,Scala 中的经验法则是始终使用 `val`,除非变量确实需要被改变。 + +## OOP 风格的类和方法 + +本节比较了与 OOP 风格的类和方法相关的特性。 + +### OOP 风格类,主构造函数: + + + + + + + + + + +
+ class Person(object): +
  def __init__(self, name): +
    self.name = name +
+
  def speak(self): +
    print(f'Hello, my name is {self.name}')
+
+ class Person (var name: String): +
  def speak() = println(s"Hello, my name is $name")
+
+ +### 创建和使用实例: + + + + + + + + + + +
+ p = Person("John") +
p.name   # John +
p.name = 'Fred' +
p.name   # Fred +
p.speak()
+
+ val p = Person("John") +
p.name   // John +
p.name = "Fred" +
p.name   // Fred +
p.speak()
+
+ +### 单行方法: + + + + + + + + + + +
+ def add(a, b): return a + b +
+ def add(a: Int, b: Int): Int = a + b +
+ +### 多行方法: + + + + + + + + + + +
+ def walkThenRun(): +
  print('walk') +
  print('run')
+
+ def walkThenRun() = +
  println("walk") +
  println("run")
+
+ +## 接口,traits,和继承 + +如果您熟悉 Java 8 和更新版本,Scala trait 类似于那些 Java 接口。 +Traits 在 Scala 中一直使用,而 Python 接口和抽象类的使用频率要低得多。 +因此,与其试图比较两者,不如用这个例子来展示如何使用 Scala trait来构建一个模拟数学问题的小解决方案: + +```scala +trait Adder: + def add(a: Int, b: Int) = a + b + +trait Multiplier: + def multiply(a: Int, b: Int) = a * b + +// create a class from the traits +class SimpleMath extends Adder, Multiplier +val sm = new SimpleMath +sm.add(1,1) // 2 +sm.multiply(2,2) // 4 +``` + +还有[许多其他方法可以将 trait 与类和对象一起使用][modeling-intro],但这让您了解如何使用它们将概念组织成行为的逻辑组,然后根据需要将它们合并以创建一个完整的解决方案。 + +## 控制结构 + +本节比较 Python 和 Scala 中的[控制结构][control-structures]。 +两种语言都有类似 `if`/`else`、`while`、`for` 循环和 `try` 的结构。 +Scala 也有 `match` 表达式。 + +### `if` 语句,单行: + + + + + + + + + + +
+ if x == 1: print(x) +
+ if x == 1 then println(x) +
+ +### `if` 语句,多行: + + + + + + + + + + +
+ if x == 1: +
  print("x is 1, as you can see:") +
  print(x)
+
+ if x == 1 then +
  println("x is 1, as you can see:") +
  println(x)
+
+ +### if, else if, else: + + + + + + + + + + +
+ if x < 0: +
  print("negative") +
elif x == 0: +
  print("zero") +
else: +
  print("positive")
+
+ if x < 0 then +
  println("negative") +
else if x == 0 then +
  println("zero") +
else +
  println("positive")
+
+ +### 从 `if` 返回值: + + + + + + + + + + +
+ min_val = a if a < b else b +
+ val minValue = if a < b then a else b +
+ +### `if` 作为方法体: + + + + + + + + + + +
+ def min(a, b): +
  return a if a < b else b
+
+ def min(a: Int, b: Int): Int = +
  if a < b then a else b
+
+ +### `while` 循环: + + + + + + + + + + +
+ i = 1 +
while i < 3: +
  print(i) +
  i += 1
+
+ var i = 1 +
while i < 3 do +
  println(i) +
  i += 1
+
+ +### 有范围的 `for` 循环: + + + + + + + + + + +
+ for i in range(0,3): +
  print(i)
+
+ // preferred +
for i <- 0 until 3 do println(i) +
+
// also available +
for (i <- 0 until 3) println(i) +
+
// multiline syntax +
for +
  i <- 0 until 3 +
do +
  println(i)
+
+ +### 列表的 `for` 循环: + + + + + + + + + + +
+ for i in ints: print(i) +
+
for i in ints: +
  print(i)
+
+ for i <- ints do println(i) +
+ +### `for` 循环,多行: + + + + + + + + + + +
+ for i in ints: +
  x = i * 2 +
  print(f"i = {i}, x = {x}")
+
+ for +
  i <- ints +
do +
  val x = i * 2 +
  println(s"i = $i, x = $x")
+
+ +### 多“范围”生成器: + + + + + + + + + + +
+ for i in range(1,3): +
  for j in range(4,6): +
    for k in range(1,10,3): +
      print(f"i = {i}, j = {j}, k = {k}")
+
+ for +
  i <- 1 to 2 +
  j <- 4 to 5 +
  k <- 1 until 10 by 3 +
do +
  println(s"i = $i, j = $j, k = $k")
+
+ +### 带守卫(`if` 表达式)的生成器: + + + + + + + + + + +
+ for i in range(1,11): +
  if i % 2 == 0: +
    if i < 5: +
      print(i)
+
+ for +
  i <- 1 to 10 +
  if i % 2 == 0 +
  if i < 5 +
do +
  println(i)
+
+ +### 每行有多个 `if` 条件: + + + + + + + + + + +
+ for i in range(1,11): +
  if i % 2 == 0 and i < 5: +
    print(i)
+
+ for +
  i <- 1 to 10 +
  if i % 2 == 0 && i < 5 +
do +
  println(i)
+
+ +### 推导: + + + + + + + + + + +
+ xs = [i * 10 for i in range(1, 4)] +
# xs: [10,20,30]
+
+ val xs = for i <- 1 to 3 yield i * 10 +
// xs: Vector(10, 20, 30)
+
+ +### `match` 表达式: + + + + + + + + + + +
+ # From 3.10, Python supports structural pattern matching +
# You can also use dictionaries for basic “switch” functionality +
match month: +
  case 1: +
    monthAsString = "January" +
  case 2: +
    monthAsString = "February" +
  case _: +
    monthAsString = "Other"
+
+ val monthAsString = month match +
  case 1 => "January" +
  case 2 => "February" +
  _ => "Other"
+
+ +### switch/match: + + + + + + + + + + +
+ # Only from Python 3.10 +
match i: +
  case 1 | 3 | 5 | 7 | 9: +
    numAsString = "odd" +
  case 2 | 4 | 6 | 8 | 10: +
    numAsString = "even" +
  case _: +
    numAsString = "too big"
+
+ val numAsString = i match +
  case 1 | 3 | 5 | 7 | 9 => "odd" +
  case 2 | 4 | 6 | 8 | 10 => "even" +
  case _ => "too big"
+
+ +### try, catch, finally: + + + + + + + + + + +
+ try: +
  print(a) +
except NameError: +
  print("NameError") +
except: +
  print("Other") +
finally: +
  print("Finally")
+
+ try +
  writeTextToFile(text) +
catch +
  case ioe: IOException => +
    println(ioe.getMessage) +
  case fnf: FileNotFoundException => +
    println(fnf.getMessage) +
finally +
  println("Finally")
+
+ +匹配表达式和模式匹配是 Scala 编程体验的重要组成部分,但这里只展示了几个 `match` 表达式功能。 有关更多示例,请参见 [控制结构][control-structures] 页面。 + +## 集合类 + +本节比较 Python 和 Scala 中可用的 [集合类][collections-classes],包括列表、字典/映射、集合和元组。 + +### 列表 + +Python 有它的列表,Scala 有几个不同的专门的可变和不可变序列类,具体取决于您的需要。 +因为 Python 列表是可变的,所以它最直接地与 Scala 的 `ArrayBuffer` 进行比较。 + +### Python 列表 & Scala序列: +Match expressions and pattern matching are a big part of the Scala programming experience, but only a few `match` expression features are shown here. See the [Control Structures][control-structures] page for many more examples. + +## Collections classes + +This section compares the [collections classes][collections-classes] that are available in Python and Scala, including lists, dictionaries/maps, sets, and tuples. + +### Lists + +Where Python has its list, Scala has several different specialized mutable and immutable sequence classes, depending on your needs. +Because the Python list is mutable, it most directly compares to Scala’s `ArrayBuffer`. + +### Python list & Scala sequences: + + + + + + + + + + +
+ a = [1,2,3] +
+ // use different sequence classes +
// as needed +
val a = List(1,2,3) +
val a = Vector(1,2,3) +
val a = ArrayBuffer(1,2,3)
+
+ +### 获取列表元素: + + + + + + + + + +
+ a[0]
a[1]
+
+ a(0)
a(1)
// just like all other method calls +
+ +### 更新列表元素: + + + + + + + + + + +
+ a[0] = 10 +
a[1] = 20
+
+ // ArrayBuffer is mutable +
a(0) = 10 +
a(1) = 20
+
+ +### 合并两个列表: + + + + + + + + + + +
+ c = a + b +
+ val c = a ++ b +
+ +### 遍历列表: + + + + + + + + + + +
+ for i in ints: print(i) +
+
for i in ints: +
  print(i)
+
+ // preferred +
for i <- ints do println(i) +
+
// also available +
for (i <- ints) println(i)
+
+ +Scala 的主要序列类是 `List`、`Vector` 和 `ArrayBuffer`。 +`List` 和 `Vector` 是当你想要一个不可变序列时使用的主要类,而 `ArrayBuffer` 是当你想要一个可变序列时使用的主要类。 +(Scala 中的“缓冲区”是一个可以增长和缩小的序列。) + +### 字典/映射 + +Python 字典就像_可变的_ Scala `Map` 类。 +但是,默认的 Scala 映射是_不可变的_,并且有许多转换方法可以让您轻松创建新映射。 + +#### 字典/映射 创建: + + + + + + + + + + +
+ my_dict = { +
  'a': 1, +
  'b': 2, +
  'c': 3 +
}
+
+ val myMap = Map( +
  "a" -> 1, +
  "b" -> 2, +
  "c" -> 3 +
)
+
+ +#### 获取字典/映射元素: + + + + + + + + + + +
+ my_dict['a']   # 1 +
+ myMap("a")   // 1 +
+ +#### 带 `for` 循环的字典/映射: + + + + + + + + + + +
+ for key, value in my_dict.items(): +
  print(key) +
  print(value)
+
+ for (key,value) <- myMap do +
  println(key) +
  println(value)
+
+ +Scala 有其他专门的 `Map` 类来满足不同的需求。 + +### 集合 + +Python 集合类似于_可变的_ Scala `Set` 类。 + +#### 集合创建: + + + + + + + + + + +
+ set = {"a", "b", "c"} +
+ val set = Set(1,2,3) +
+ +#### 重复元素: + + + + + + + + + + +
+ set = {1,2,1} +
# set: {1,2}
+
+ val set = Set(1,2,1) +
// set: Set(1,2)
+
+ +Scala 有其他专门的 `Set` 类来满足不同的需求。 + +### 元组 + +Python 和 Scala 元组也很相似。 + +#### 元组创建: + + + + + + + + + + +
+ t = (11, 11.0, "Eleven") +
+ val t = (11, 11.0, "Eleven") +
+ +#### 获取元组元素: + + + + + + + + + + +
+ t[0]   # 11 +
t[1]   # 11.0
+
+ t(0)   // 11 +
t(1)   // 11.0
+
+ +## 集合类上的方法 + +Python 和 Scala 有几个相同的常用函数方法可供它们使用: + +- `map` +- `filter` +- `reduce` + +如果您习惯于在 Python 中将这些方法与 lambda 表达式一起使用,您会发现 Scala 在其集合类中使用了类似的方法。 +为了演示此功能,这里有两个示例列表: + +```scala +numbers = (1,2,3) // python +val numbers = List(1,2,3) // scala +``` + +下表中使用了这些列表,显示了如何对其应用映射和过滤算法。 + +### 映射与推导: + + + + + + + + + + +
+ x = [i * 10 for i in numbers] +
+ val x = for i <- numbers yield i * 10 +
+ +### 有推导的过滤: + + + + + + + + + + +
+ evens = [i for i in numbers if i % 2 == 0] +
+ val evens = numbers.filter(_ % 2 == 0) +
// or +
val evens = for i <- numbers if i % 2 == 0 yield i
+
+ +### 映射 & 有推导的过滤: + + + + + + + + + + +
+ x = [i * 10 for i in numbers if i % 2 == 0] +
+ val x = numbers.filter(_ % 2 == 0).map(_ * 10) +
// or +
val x = for i <- numbers if i % 2 == 0 yield i * 10
+
+ +### 映射: + + + + + + + + + + +
+ x = map(lambda x: x * 10, numbers) +
+ val x = numbers.map(_ * 10) +
+ +### 过滤: + + + + + + + + + + +
+ f = lambda x: x > 1 +
x = filter(f, numbers)
+
+ val x = numbers.filter(_ > 1) +
+ + +### Scala 集合方法: + +Scala 集合类有超过 100 种功能方法来简化您的代码。 +除了 `map`、`filter` 和 `reduce`,下面列出了其他常用的方法。 +在这些方法示例中: + +- `c` 指的是一个集合 +- `p` 是谓词 +- `f` 是一个函数、匿名函数或方法 +- `n` 指的是一个整数值 + +以下是一些可用的过滤方法: + +| 方法 | 说明 | +| -------------- | ------------- | +| `c1.diff(c2) ` | 返回 `c1` 和 `c2` 中元素的差异。 | +| `c.distinct` | 返回 `c` 中的唯一元素。 | +| `c.drop(n)` | 返回集合中除前 `n` 个元素之外的所有元素。 | +| `c.filter(p) ` | 返回集合中谓词为 `true` 的所有元素。 | +| `c.head` | 返回集合的第一个元素。 (如果集合为空,则抛出 `NoSuchElementException`。) | +| `c.tail` | 返回集合中除第一个元素之外的所有元素。 (如果集合为空,则抛出 `UnsupportedOperationException`。) | +| `c.take(n)` | 返回集合 `c` 的前 `n` 个元素。 | + +以下是一些转换方法: + +| 方法 | 说明 | +| --------------- | ------------- | +| `c.flatten` | 将集合的集合(例如列表列表)转换为单个集合(单个列表)。 | +| `c.flatMap(f)` | 通过将 `f` 应用于集合 `c` 的所有元素(如 `map`)返回一个新集合,然后将结果集合的元素展平。 | +| `c.map(f)` | 通过将 `f` 应用于集合 `c` 的所有元素来创建一个新集合。 | +| `c.reduce(f)` | 将 `reduction` 函数 `f` 应用于 `c` 中的连续元素以产生单个值。 | +| `c.sortWith(f)` | 返回由比较函数 `f` 排序的 `c` 版本。 | + +一些常见的分组方法: + +| 方法 | 说明 | +| ---------------- | ------------- | +| `c.groupBy(f)` | 根据 `f` 将集合划分为集合的 `Map`。 | +| `c.partition(p)` | 根据谓词 `p` 返回两个集合。 | +| `c.span(p)` | 返回两个集合的集合,第一个由 `c.takeWhile(p)` 创建,第二个由 `c.dropWhile(p)` 创建。 | +| `c.splitAt(n)` | 通过在元素 `n` 处拆分集合 `c` 来返回两个集合的集合。 | + +一些信息和数学方法: + +| 方法 | 说明 | +| -------------- | ------------- | +| `c1.containsSlice(c2)` | 如果 `c1` 包含序列 `c2`,则返回 `true`。 | +| `c.count(p)` | 计算 `c` 中元素的数量,其中 `p` 为 `true`。 | +| `c.distinct` | 返回 `c` 中的不重复的元素。 | +| `c.exists(p)` | 如果集合中任何元素的 `p` 为 `true` ,则返回 `true` 。 | +| `c.find(p)` | 返回匹配 `p` 的第一个元素。该元素以 `Option[A]` 的形式返回。 | +| `c.min` | 返回集合中的最小元素。 (可以抛出_java.lang.UnsupportedOperationException_。)| +| `c.max` | 返回集合中的最大元素。 (可以抛出_java.lang.UnsupportedOperationException_。)| +| `c slice(from, to)` | 返回从元素 `from` 开始到元素 `to` 结束的元素间隔。 | +| `c.sum` | 返回集合中所有元素的总和。 (需要为集合中的元素定义 `Ordering`。) | + +以下是一些示例,展示了这些方法如何在列表上工作: + +```scala +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.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) +``` + +这些方法展示了 Scala 中的一个常见模式:对象上可用的函数式方法。 +这些方法都不会改变初始列表“a”; 相反,它们都返回的数据在注释后显示。 + +还有更多可用的方法,但希望这些描述和示例能让您体验到预建集合方法的强大功能。 + +## 枚举 + +本节比较 Python 和 Scala 3 中的枚举。 + +### 创建枚举: + + + + + + + + + + +
+ from enum import Enum, auto +
class Color(Enum): +
    RED = auto() +
    GREEN = auto() +
    BLUE = auto()
+
+ enum Color: +
  case Red, Green, Blue
+
+ +### 值和比较: + + + + + + + + + + +
+ Color.RED == Color.BLUE  # False +
+ Color.Red == Color.Blue  // false +
+ +### 参数化枚举: + + + + + + + + + + +
+ N/A +
+ enum Color(val rgb: Int): +
  case Red   extends Color(0xFF0000) +
  case Green extends Color(0x00FF00) +
  case Blue  extends Color(0x0000FF)
+
+ +### 用户定义枚举成员: + + + + + + + + + + +
+ N/A +
+ enum Planet( +
    mass: Double, +
    radius: Double +
  ): +
  case Mercury extends +
    Planet(3.303e+23, 2.4397e6) +
  case Venus extends +
    Planet(4.869e+24, 6.0518e6) +
  case Earth extends +
    Planet(5.976e+24, 6.37814e6) +
  // more planets ... +
+
  // fields and methods +
  private final val G = 6.67300E-11 +
  def surfaceGravity = G * mass / +
    (radius * radius) +
  def surfaceWeight(otherMass: Double) +
    = otherMass * surfaceGravity
+
+ +## Scala 独有的概念 + +Scala 中的有些概念目前在 Python 中没有等效的功能。 +请点击以下链接了解更多详情: + +- 大多数与[上下文抽象][contextual]相关的概念,如[扩展方法][extension-methods]、[类型类][type-classes]、隐式值 +- Scala 允许多参数列表,从而实现部分应用函数等特性,以及创建自己的 DSL 的能力 +- 样例类,对于函数式编程和模式匹配非常有用 +- 创建自己的控制结构和 DSL 的能力 +- 模式匹配和 `match` 表达式 +- [多重等式][multiversal]:在编译时控制哪些等式比较有意义的能力 +- 中缀方法 +- 宏和元编程 + +## Scala 和虚拟环境 + +在 Scala 中,无需显式设置 Python 虚拟环境的等价物。默认情况下,Scala 构建工具管理项目依赖项,因此用户不必考虑手动安装包。例如,使用 `sbt` 构建工具,我们在 `libraryDependencies` 设置下的 `build.sbt` 文件中指定依赖关系,然后执行 + +``` +cd myapp +sbt compile +``` + +自动解析该特定项目的所有依赖项。 下载依赖的位置很大程度上是构建工具的一个实现细节,用户不必直接与这些下载的依赖交互。 例如,如果我们删除整个 sbt 依赖项缓存,则在项目的下一次编译时,sbt 会自动重新解析并再次下载所有必需的依赖项。 + +这与 Python 不同,默认情况下,依赖项安装在系统范围或用户范围的目录中,因此要在每个项目的基础上获得隔离环境,必须创建相应的虚拟环境。 例如,使用 `venv` 模块,我们可以像这样为特定项目创建一个 + +``` +cd myapp +python3 -m venv myapp-env +source myapp-env/bin/activate +pip install -r requirements.txt +``` + +这会在项目的 `myapp/myapp-env` 目录下安装所有依赖项,并更改 shell 环境变量 `PATH` 以从 `myapp-env` 查找依赖项。 +在 Scala 中,这些手动过程都不是必需的。 + + +[collections-classes]: {% link _overviews/scala3-book/collections-classes.md %} +[concurrency]: {% link _overviews/scala3-book/concurrency.md %} +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %} +[control-structures]: {% link _overviews/scala3-book/control-structures.md %} +[extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[fp-intro]: {% link _overviews/scala3-book/fp-intro.md %} +[hofs]: {% link _overviews/scala3-book/fun-hofs.md %} +[intersection-types]: {% link _overviews/scala3-book/types-intersection.md %} +[main-method]: {% link _overviews/scala3-book/methods-main-methods.md %} +[modeling-intro]: {% link _overviews/scala3-book/domain-modeling-intro.md %} +[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} +[toplevel]: {% link _overviews/scala3-book/taste-toplevel-definitions.md %} +[type-classes]: {% link _overviews/scala3-book/types-type-classes.md %} +[union-types]: {% link _overviews/scala3-book/types-union.md %} +
From d3637fbdcb1d2b500defcd9a4d3888b1ea566bbb Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Thu, 16 Jun 2022 21:25:13 +0800 Subject: [PATCH 27/53] corrected broken link in concurrency.md --- _zh-cn/overviews/scala3-book/concurrency.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/concurrency.md b/_zh-cn/overviews/scala3-book/concurrency.md index 02b3234842..5eafd6face 100644 --- a/_zh-cn/overviews/scala3-book/concurrency.md +++ b/_zh-cn/overviews/scala3-book/concurrency.md @@ -8,7 +8,7 @@ next-page: scala-tools --- -当您想在 Scala 中编写并行和并发应用程序时,您_可以_使用本机 Java `Thread` --- 但 Scala [Future](https://www.scala-lang.org/api/current/scala/concurrent /Future$.html) 提供了一种更高级和惯用的方法,因此它是首选,本章将对此进行介绍。 +当您想在 Scala 中编写并行和并发应用程序时,您_可以_使用本机 Java `Thread` --- 但 Scala [Future](https://www.scala-lang.org/api/current/scala/concurrent/Future$.html) 提供了一种更高级和惯用的方法,因此它是首选,本章将对此进行介绍。 ## 介绍 From 47aab084f29e85ebf77931acb9df90fbc2c14694 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Mon, 4 Jul 2022 13:12:28 +0800 Subject: [PATCH 28/53] correct ca.* files --- _zh-cn/overviews/scala3-book/ca-given-imports.md | 16 ++++++++-------- .../scala3-book/ca-implicit-conversions.md | 2 +- .../scala3-book/ca-multiversal-equality.md | 14 +++++++------- _zh-cn/overviews/scala3-book/ca-summary.md | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/_zh-cn/overviews/scala3-book/ca-given-imports.md b/_zh-cn/overviews/scala3-book/ca-given-imports.md index f8df481645..d1b69f57e0 100644 --- a/_zh-cn/overviews/scala3-book/ca-given-imports.md +++ b/_zh-cn/overviews/scala3-book/ca-given-imports.md @@ -1,5 +1,5 @@ --- -title: 给定导入 +title: Given 导入 type: section description: This page demonstrates how 'given' import statements work in Scala 3. num: 62 @@ -8,7 +8,7 @@ next-page: ca-extension-methods --- -为了更清楚地说明当前作用域中的给定来自何处,我们使用一种特殊形式的 `import` 语句来导入 `given` 实例。 +为了更清楚地说明当前作用域中的 given 来自何处,我们使用一种特殊形式的 `import` 语句来导入 `given` 实例。 此示例中显示了基本形式: ```scala @@ -33,16 +33,16 @@ object B: ## 讨论 -通配符选择器 `*` 将除给定或扩展之外的所有定义都导入作用域,而 `given` 选择器将所有*给定*定义---包括由扩展而来的定义---导入作用域。 +通配符选择器 `*` 将除 given 或扩展之外的所有定义都导入作用域,而 `given` 选择器将所有 *given* 定义---包括由扩展而来的定义---导入作用域。 这些规则有两个主要优点: -- 更清楚当前作用域内给定的来源。 - 特别是,在一长串其他通配符导入中无法隐藏导入的给定。 -- 它可以导入所有给定,而无需导入任何其他内容。 - 这很重要,因为给定是可以匿名的,因此通常使用命名导入是不切实际的。 +- 更清楚当前作用域内 given 的来源。 + 特别是,在一长串其他通配符导入中无法隐藏导入的 given 。 +- 它可以导入所有 given ,而无需导入任何其他内容。 + 这很重要,因为 given 是可以匿名的,因此通常使用命名导入是不切实际的。 -“导入给定”语法的更多示例见[打包和导入章节][imports]。 +“导入 given”语法的更多示例见 [package 和 import 章节][imports]。 [imports]: {% link _overviews/scala3-book/packaging-imports.md %} diff --git a/_zh-cn/overviews/scala3-book/ca-implicit-conversions.md b/_zh-cn/overviews/scala3-book/ca-implicit-conversions.md index df51bc2bf3..670940e4f6 100644 --- a/_zh-cn/overviews/scala3-book/ca-implicit-conversions.md +++ b/_zh-cn/overviews/scala3-book/ca-implicit-conversions.md @@ -38,7 +38,7 @@ plus1("1") ## 讨论 -Predef 包包含“自动装箱”转换,将原始数字类型映射到 `java.lang.Number` 的子类。 +Predef 包包含“自动装箱”转换,将基本数字类型映射到 `java.lang.Number` 的子类。 例如,从 `Int` 到 `java.lang.Integer` 的转换可以定义如下: ```scala diff --git a/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md b/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md index 42c2914042..df8ac7d98a 100644 --- a/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md +++ b/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md @@ -8,7 +8,7 @@ next-page: ca-implicit-conversions --- -以前,Scala 具有*普遍相等性*:任何类型的两个值都可以使用 `==` 和 `!=` 相互比较。 +以前,Scala 具有*通用相等性*:任何类型的两个值都可以使用 `==` 和 `!=` 相互比较。 这是因为 `==` 和 `!=` 是根据 Java 的 `equals` 方法实现的,该方法还可以比较任何两种引用类型的值。 普遍平等很方便,但也很危险,因为它破坏了类型安全。 @@ -25,7 +25,7 @@ x == y // typechecks, will always yield false 但它可能会产生意想不到的结果并在运行时失败。 类型安全的编程语言可以做得更好,多重平等是使普遍平等更安全的一种选择方式。 -它使用二进制类型类“CanEqual”来指示两个给定类型的值可以相互比较。 +它使用二元类型类“CanEqual”来指示两个给定类型的值可以相互比较。 ## 允许比较类实例 @@ -84,7 +84,7 @@ given CanEqual[Dog, Dog] = CanEqual.derived import scala.language.strictEquality ``` -然后像往常一样创建你的域对象: +然后像往常一样创建你的领域对象: ```scala // [2] create your class hierarchy @@ -134,7 +134,7 @@ println(pBook == aBook) // compiler error Values of types PrintedBook and AudioBook cannot be compared with == or != ```` -这就是多重​元平等性在编译时捕获非法类型比较的方式。 +这就是多元相等性在编译时捕获非法类型比较的方式。 ### 启用“PrintedBook == AudioBook” @@ -157,8 +157,8 @@ println(aBook == pBook) // false #### 实施“等于”以使它们真正起作用 虽然现在允许进行这些比较,但它们将始终为 `false`,因为它们的 `equals` 方法不知道如何进行这些比较。 -因此,解决方案是重载每个类的 `equals` 方法。 -例如,当您重载 `AudioBook` 的 `equals` 方法时: +因此,解决方案是覆盖每个类的 `equals` 方法。 +例如,当您覆盖 `AudioBook` 的 `equals` 方法时: ```scala case class AudioBook( @@ -190,7 +190,7 @@ println(pBook == aBook) // false ``` 目前 `PrintedBook` 书没有 `equals` 方法,所以第二个比较返回 `false`。 -要启用该比较,只需重载 `PrintedBook` 中的 `equals` 方法。 +要启用该比较,只需覆盖 `PrintedBook` 中的 `equals` 方法。 您可以在参考文档中找到有关[多元相等性][ref-equal] 的更多信息。 diff --git a/_zh-cn/overviews/scala3-book/ca-summary.md b/_zh-cn/overviews/scala3-book/ca-summary.md index f387d8ed05..5b9b1c147d 100644 --- a/_zh-cn/overviews/scala3-book/ca-summary.md +++ b/_zh-cn/overviews/scala3-book/ca-summary.md @@ -22,7 +22,7 @@ next-page: concurrency - 类型类派生 - 上下文函数 -- 按名称上下文参数 +- 传名上下文参数 - 与 Scala 2 隐式转换 的关系 这些主题在 [参考文档][ref] 中有详细讨论。 From ddcf921542f01b7ecda74d9d9a47379a573aa833 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 17 Jul 2022 20:48:01 +0800 Subject: [PATCH 29/53] modified translation according to review. * _zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md: --- .../ca-contextual-abstractions-intro.md | 97 +++++++++---------- .../scala3-book/ca-multiversal-equality.md | 6 +- .../scala3-book/collections-classes.md | 9 +- .../scala3-book/collections-methods.md | 4 +- _zh-cn/overviews/scala3-book/concurrency.md | 25 +++-- 5 files changed, 68 insertions(+), 73 deletions(-) diff --git a/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md index dc14d90c62..542ed86f18 100644 --- a/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md +++ b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md @@ -1,5 +1,5 @@ --- -title: Contextual Abstractions +title: 上下文抽象 type: chapter description: This chapter provides an introduction to the Scala 3 concept of Contextual Abstractions. num: 58 @@ -8,74 +8,73 @@ next-page: ca-given-using-clauses --- -## Background +## 背景 -Implicits in Scala 2 were a major distinguishing design feature. -They are *the* fundamental way to abstract over context. -They represent a unified paradigm with a great variety of use cases, among them: +Scala 2 中的隐式是一个主要的显着设计特征。 +它们是*那个*抽象上下文的基本方法。 +它们代表了具有多种用例的统一范式,其中包括: -- Implementing type classes -- Establishing context -- Dependency injection -- Expressing capabilities -- Computing new types, and proving relationships between them +- 实现类型类 +- 建立上下文 +- 依赖注入 +- 表达能力 +- 计算新类型,并证明它们之间的关系 -Since then, other languages have followed suit, e.g., Rust’s traits or Swift’s protocol extensions. -Design proposals are also on the table for Kotlin as compile time dependency resolution, for C# as Shapes and Extensions or for F# as Traits. -Implicits are also a common feature of theorem provers such as Coq or Agda. +从那时起,其他语言也纷纷效仿,例如 Rust 的特征或 Swift 的协议扩展。 +这样的设计提案也已出现。对于 Kotlin 是作为编译时依赖解决方案,对于 C# 是作为 Shapes 和 Extensions,对于 F# 是作为 Traits。 +隐式也是 Coq 或 Agda 等定理证明器的共同特征。 -Even though these designs use different terminology, they’re all variants of the core idea of *term inference*: -Given a type, the compiler synthesizes a “canonical” term that has that type. +尽管这些设计使用不同的术语,但它们都是*术语推理*核心思想的变体: +给定一个类型,编译器会合成一个具有该类型的“规范”术语。 +## 重新设计 -## Redesign +Scala 3 重新设计了 Scala 中的上下文抽象。 +虽然这些概念是在 Scala 2 中逐渐“发现”的,但它们现在已广为人知和理解,并且重新设计利用了这些知识。 -Scala 3 includes a redesign of contextual abstractions in Scala. -While these concepts were gradually “discovered” in Scala 2, they’re now well known and understood, and the redesign takes advantage of that knowledge. +Scala 3 的设计侧重于 **意图** 而不是 **机制**。 +Scala 3 没有提供一个非常强大的隐式特性,而是提供了几个面向用例的特性: -The design of Scala 3 focuses on **intent** rather than **mechanism**. -Instead of offering one very powerful feature of implicits, Scala 3 offers several use-case oriented features: +- **抽象上下文信息**。 + [使用子句][givens] 允许程序员对调用上下文中可用的信息进行抽象,并且应该隐式传递。 + 作为对 Scala 2 隐式的改进,可以按类型指定 using 子句,从而将函数签名从从未显式引用的术语变量名称中释放出来。 -- **Abstracting over contextual information**. - [Using clauses][givens] allow programmers to abstract over information that is available in the calling context and should be passed implicitly. - As an improvement over Scala 2 implicits, using clauses can be specified by type, freeing function signatures from term variable names that are never explicitly referred to. +- **提供类型类实例**。 + [给定实例][type-classes]允许程序员定义某种类型的_规范值_。 + 这使得使用类型类的编程更加简单,而不会泄露实现细节。 -- **Providing Type-class instances**. - [Given instances][type-classes] allow programmers to define the _canonical value_ of a certain type. - This makes programming with type-classes more straightforward without leaking implementation details. +- **追溯扩展类**。 + 在 Scala 2 中,扩展方法必须使用隐式转换或隐式类进行编码。 + 相比之下,在 Scala 3 [扩展方法][extension-methods] 现在直接内置到语言中,产生了更好的错误消息和改进的类型推断。 -- **Retroactively extending classes**. - In Scala 2, extension methods had to be encoded using implicit conversions or implicit classes. - In contrast, in Scala 3 [extension methods][extension-methods] are now directly built into the language, leading to better error messages and improved type inference. +- **将一种类型视为另一种类型**。 + 隐式转换已从头开始[重新设计][implicit-conversions],作为类型类 `Conversion` 的实例。 -- **Viewing one type as another**. - Implicit conversion have been [redesigned][implicit-conversions] from the ground up as instances of a type-class `Conversion`. +- **高阶上下文抽象**。 + [上下文函数][contextual-functions] 的_全新_特性使上下文抽象成为一等公民。 + 它们是库作者的重要工具,可以表达简洁的领域特定语言。 -- **Higher-order contextual abstractions**. - The _all-new_ feature of [context functions][contextual-functions] makes contextual abstractions a first-class citizen. - They are an important tool for library authors and allow to express concise domain specific languages. +- **来自编译器的可操作反馈**。 + 如果编译器无法解析隐式参数,它现在会为您提供 [导入建议](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions. html) 来解决问题。 -- **Actionable feedback from the compiler**. - In case an implicit parameter can not be resolved by the compiler, it now provides you [import suggestions](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html) that may fix the problem. +## 好处 +Scala 3 中的这些更改实现了术语推理与语言其余部分的更好分离: -## Benefits +- 仅有一种定义 given 的方法 +- 仅有一种方法可以引入隐式参数和自变量 +- [导入 givens][given-imports] 仅有一种单独的方法,不允许它们隐藏在正常导入的海洋中 +- 定义 [隐式转换][implicit-conversions] 的方法仅有一种,它被清楚地标记为这样,并且不需要特殊的语法 -These changes in Scala 3 achieve a better separation of term inference from the rest of the language: +这些变化的好处包括: -- There’s a single way to define givens -- There’s a single way to introduce implicit parameters and arguments -- There’s a separate way to [import givens][given-imports] that does not allow them to hide in a sea of normal imports -- There’s a single way to define an [implicit conversion][implicit-conversions], which is clearly marked as such, and does not require special syntax +- 新设计避免了特性交织,从而使语言更加一致 +- 它使隐式更容易学习和更难滥用 +- 它极大地提高了 95% 使用隐式的 Scala 程序的清晰度 +- 它有可能以一种易于理解和友好的原则方式进行术语推理 -Benefits of these changes include: +本章将在以下各节中介绍其中的许多新功能。 -- The new design thus avoids feature interactions and makes the language more consistent -- It makes implicits easier to learn and harder to abuse -- It greatly improves the clarity of the 95% of Scala programs that use implicits -- It has the potential to enable term inference in a principled way that is also accessible and friendly - -This chapter introduces many of these new features in the following sections. [givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %} [given-imports]: {% link _overviews/scala3-book/ca-given-imports.md %} diff --git a/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md b/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md index df8ac7d98a..72bd30b61e 100644 --- a/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md +++ b/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md @@ -11,7 +11,7 @@ next-page: ca-implicit-conversions 以前,Scala 具有*通用相等性*:任何类型的两个值都可以使用 `==` 和 `!=` 相互比较。 这是因为 `==` 和 `!=` 是根据 Java 的 `equals` 方法实现的,该方法还可以比较任何两种引用类型的值。 -普遍平等很方便,但也很危险,因为它破坏了类型安全。 +通用相等性很方便,但也很危险,因为它破坏了类型安全。 例如,假设在一些重构之后,你会得到一个错误的程序,其中值 `y` 的类型为 `S` 而不是正确的类型 `T`: ```scala @@ -24,7 +24,7 @@ x == y // typechecks, will always yield false 如果 `y` 与其他类型为 `T` 的值进行比较,程序仍然会进行类型检查,因为所有类型的值都可以相互比较。 但它可能会产生意想不到的结果并在运行时失败。 -类型安全的编程语言可以做得更好,多重平等是使普遍平等更安全的一种选择方式。 +类型安全的编程语言可以做得更好,多元等价是使普遍平等更安全的一种选择方式。 它使用二元类型类“CanEqual”来指示两个给定类型的值可以相互比较。 ## 允许比较类实例 @@ -154,7 +154,7 @@ println(pBook == aBook) // false println(aBook == pBook) // false ``` -#### 实施“等于”以使它们真正起作用 +#### 实现 “equals” 以使它们真正起作用 虽然现在允许进行这些比较,但它们将始终为 `false`,因为它们的 `equals` 方法不知道如何进行这些比较。 因此,解决方案是覆盖每个类的 `equals` 方法。 diff --git a/_zh-cn/overviews/scala3-book/collections-classes.md b/_zh-cn/overviews/scala3-book/collections-classes.md index 7617c934a3..dcf1626c74 100644 --- a/_zh-cn/overviews/scala3-book/collections-classes.md +++ b/_zh-cn/overviews/scala3-book/collections-classes.md @@ -26,7 +26,7 @@ Scala 提供了丰富的集合类型,但您可以从其中的几个开始, 从高层次看 Scala 集合,有三个主要类别可供选择: - **序列**是元素的顺序集合,可以是_有索引的_(如数组)或_线性的_(如链表) -- **影射** 包含键/值对的集合,例如 Java `Map`、Python 字典或 Ruby `Hash` +- **映射** 包含键/值对的集合,例如 Java `Map`、Python 字典或 Ruby `Hash` - **集合** 是无重复元素的无序集合 所有这些都是基本类型,并且具有用于特定目的的子类型,例如并发、缓存和流式传输。 @@ -37,8 +37,7 @@ Scala 提供了丰富的集合类型,但您可以从其中的几个开始, 作为简要概述,接下来的三个图显示了 Scala 集合中类和 trait 的层次结构。 第一张图显示了_scala.collection_包中的集合类型。 -这些都是高级抽象类或 traits,它们 -通常有_不可变_和_可变_的实现。 +这些都是高级抽象类或 traits,它们通常有_不可变_和_可变_的实现。 ![一般集合层次结构][collections1] @@ -79,11 +78,11 @@ NOTE: those images come from this page: https://docs.scala-lang.org/overviews/co ### 关于不可变集合的说明 在接下来的部分中,无论何时使用_不可变_这个词,都可以安全地假设该类型旨在用于_函数性编程_(FP) 风格。 -使用这些类型,您无需修改​​集合;您将功能方法应用于该集合以创建新的结果。 +使用这些类型,您无需修改​​集合;您将函数式方法应用于该集合以创建新的结果。 ## 选择序列 -选择_序列_---一个顺序集合元素时---您有两个主要决定: +选择_序列_ -- 一个顺序集合元素时 -- 您有两个主要决定: - 是否应该对序列进行索引(如数组),允许快速访问任何元素,还是应该将其实现为线性链表? - 你想要一个可变的还是不可变的集合? diff --git a/_zh-cn/overviews/scala3-book/collections-methods.md b/_zh-cn/overviews/scala3-book/collections-methods.md index 7d0d19f368..9a47151843 100644 --- a/_zh-cn/overviews/scala3-book/collections-methods.md +++ b/_zh-cn/overviews/scala3-book/collections-methods.md @@ -338,7 +338,7 @@ names.dropWhile(_ != "chris") // List(chris, david) ## `reduce` -当您听到“映射归约”这个术语时,“归约”部分指的是诸如 `reduce` 之类的方法。 +当您听到 “map reduce” 术语时,“reduce” 部分指的是诸如 `reduce` 之类的方法。 它接受一个函数(或匿名函数)并将该函数应用于列表中的连续元素。 解释 `reduce` 的最好方法是创建一个可以传递给它的小辅助方法。 @@ -387,7 +387,7 @@ res1: Int = 24 ## 更多 -在 Scala 集合类型上确实有几十个额外的方法,可以让你不再需要编写另一个 `for` 循环。有关 Scala 集合的更多详细信息,请参阅[可变和不可变集合][mut-immut-colls]和[Scala集合的构架][architecture]。 +在 Scala 集合类型上确实有几十个额外的方法,可以让你不再需要编写另一个 `for` 循环。有关 Scala 集合的更多详细信息,请参阅[可变和不可变集合][mut-immut-colls]和[Scala集合的架构][architecture]。 > 最后一点,如果您在 Scala 项目中使用 Java 代码,您可以将 Java 集合转换为 Scala 集合。 > 通过这样做,您可以在 `for` 表达式中使用这些集合,还可以利用 Scala 的函数式集合方法。 diff --git a/_zh-cn/overviews/scala3-book/concurrency.md b/_zh-cn/overviews/scala3-book/concurrency.md index 5eafd6face..68027763f7 100644 --- a/_zh-cn/overviews/scala3-book/concurrency.md +++ b/_zh-cn/overviews/scala3-book/concurrency.md @@ -8,7 +8,7 @@ next-page: scala-tools --- -当您想在 Scala 中编写并行和并发应用程序时,您_可以_使用本机 Java `Thread` --- 但 Scala [Future](https://www.scala-lang.org/api/current/scala/concurrent/Future$.html) 提供了一种更高级和惯用的方法,因此它是首选,本章将对此进行介绍。 +当您想在 Scala 中编写并行和并发应用程序时,您_可以_使用原生 Java `Thread` --- 但 Scala [Future](https://www.scala-lang.org/api/current/scala/concurrent/Future$.html) 提供了一种更高级和惯用的方法,因此它是首选,本章将对此进行介绍。 ## 介绍 @@ -150,29 +150,27 @@ eventualInt.onComplete { `Future` 类还有其他可以使用的方法。 它具有您在 Scala 集合类中找到的一些方法,包括: -- `过滤器` --`平面地图` -- `地图` +- `filter` +- `flatMap` +- `map` 它的回调方法有: - `onComplete` -- `然后` +- `andThen` - `foreach` 其他转换方法包括: --`fallbackTo` -- `恢复` +- `fallbackTo` +- `recover` - `recoverWith` 请参阅 [Futures and Promises][futures] 页面,了解有关 future 可用的其他方法的讨论。 - - ## 运行多个 future 并加入他们的结果 -要并行运行多个计算并在所有 future 完成后加入它们的结果,请使用“for”表达式。 +要并行运行多个计算并在所有 future 完成后加入它们的结果,请使用 “for” 表达式。 正确的做法是: @@ -180,7 +178,6 @@ eventualInt.onComplete { 2. 将他们的结果合并到一个 `for` 表达式中 3. 使用 `onComplete` 或类似技术提取合并结果 - ### 一个例子 以下示例显示了正确方法的三个步骤。 @@ -242,7 +239,7 @@ result = 6 如该输出所示, future 的创建速度非常快,仅在两毫秒内就到达了方法末尾的 `sleep(3000)` 语句之前的打印语句。 所有这些代码都在 JVM 的主线程上运行。 然后,在 806 毫秒,三个 future 完成并运行 `yield` 块中的代码。 -然后代码立即转到 `onComplete` 方法中的 `Success` 案例。 +然后代码立即转到 `onComplete` 方法中的 `Success` 分支。 806 毫秒的输出是看到三个计算并行运行的关键。 如果它们按顺序运行,总时间约为 1,400 毫秒——三个计算的睡眠时间之和。 @@ -295,7 +292,7 @@ val res1: concurrent.Future[Int] = Future(Success(4)) 总而言之,关于 future 的几个关键点是: - 您构建 future 以在主线程之外运行任务 -- Futures 用于一次性的、可能长时间运行的并发任务,这些任务*最终*返回一个值;他们创造了一个临时的并发包 +- Futures 用于一次性的、可能长时间运行的并发任务,这些任务*最终*返回一个值;他们创造了一个临时的并发的容器 - 一旦你构建了 future,它就会开始运行 - future 相对于线程的一个好处是它们可以使用 `for` 表达式,并带有各种回调方法,可以简化使用并发线程的过程 - 当您使用 future 时,您不必关心线程管理的低级细节 @@ -305,7 +302,7 @@ val res1: concurrent.Future[Int] = Future(Success(4)) 此外,正如您在这些示例中看到的 `import` 语句,Scala `Future` 依赖于 `ExecutionContext`。 -有关 future 的更多详细信息,请参阅[Future 和 Promises][future],这是一篇讨论 future 、promises 和执行上下文的文章。 +有关 future 的更多详细信息,请参阅[Future 和 Promises][future],这是一篇讨论 future 、promises 和 ExecutionContext 的文章。 它还讨论了如何将 `for` 表达式转换为 `flatMap` 操作。 From 7e54512071bbd6932174fb2e1d76bfdd069da4b9 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Tue, 19 Jul 2022 16:25:43 +0800 Subject: [PATCH 30/53] correct collections-classes.md --- _zh-cn/overviews/scala3-book/collections-classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/collections-classes.md b/_zh-cn/overviews/scala3-book/collections-classes.md index dcf1626c74..dd88184c28 100644 --- a/_zh-cn/overviews/scala3-book/collections-classes.md +++ b/_zh-cn/overviews/scala3-book/collections-classes.md @@ -77,7 +77,7 @@ NOTE: those images come from this page: https://docs.scala-lang.org/overviews/co ### 关于不可变集合的说明 -在接下来的部分中,无论何时使用_不可变_这个词,都可以安全地假设该类型旨在用于_函数性编程_(FP) 风格。 +在接下来的部分中,无论何时使用_不可变_这个词,都可以安全地假设该类型旨在用于_函数式编程_(FP) 风格。 使用这些类型,您无需修改​​集合;您将函数式方法应用于该集合以创建新的结果。 ## 选择序列 From 86fdf8b73156b376cb27edaccba1903da743c0a1 Mon Sep 17 00:00:00 2001 From: "Luo Xiangxin (Ben) [ CN | Shanghai ]" Date: Thu, 21 Jul 2022 09:29:19 +0800 Subject: [PATCH 31/53] change translation according to review on Jul 19 --- .../scala3-book/ca-contextual-abstractions-intro.md | 12 ++++-------- .../overviews/scala3-book/ca-multiversal-equality.md | 2 +- _zh-cn/overviews/scala3-book/concurrency.md | 8 ++++---- _zh-cn/overviews/scala3-book/control-structures.md | 6 +++--- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md index 542ed86f18..1c1560013e 100644 --- a/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md +++ b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md @@ -10,9 +10,7 @@ next-page: ca-given-using-clauses ## 背景 -Scala 2 中的隐式是一个主要的显着设计特征。 -它们是*那个*抽象上下文的基本方法。 -它们代表了具有多种用例的统一范式,其中包括: +隐式是Scala 2中的一个主要的设计特色。隐式是进行上下文抽象的基本方式。它们代表了具有多种用例的统一范式,其中包括: - 实现类型类 - 建立上下文 @@ -20,12 +18,10 @@ Scala 2 中的隐式是一个主要的显着设计特征。 - 表达能力 - 计算新类型,并证明它们之间的关系 -从那时起,其他语言也纷纷效仿,例如 Rust 的特征或 Swift 的协议扩展。 -这样的设计提案也已出现。对于 Kotlin 是作为编译时依赖解决方案,对于 C# 是作为 Shapes 和 Extensions,对于 F# 是作为 Traits。 -隐式也是 Coq 或 Agda 等定理证明器的共同特征。 +此后,其他语言也纷纷效仿,例如 Rust 的 traits 或 Swift 的协议扩展。对于编译期的依赖解析方案,Kotlin 的设计方案也被提上日程,而对 C# 而言是 Shapes 和 Extensions 或对 F# 来说是 Traits。Implicits 也是 Coq 或 Agda 等定理证明器的共同特色。 尽管这些设计使用不同的术语,但它们都是*术语推理*核心思想的变体: -给定一个类型,编译器会合成一个具有该类型的“规范”术语。 +给定一个类型,编译器会合成一个具有该类型的“canonical”术语。 ## 重新设计 @@ -40,7 +36,7 @@ Scala 3 没有提供一个非常强大的隐式特性,而是提供了几个面 作为对 Scala 2 隐式的改进,可以按类型指定 using 子句,从而将函数签名从从未显式引用的术语变量名称中释放出来。 - **提供类型类实例**。 - [给定实例][type-classes]允许程序员定义某种类型的_规范值_。 + [给定实例][type-classes]允许程序员定义某种类型的_规范值(canonical value)_。 这使得使用类型类的编程更加简单,而不会泄露实现细节。 - **追溯扩展类**。 diff --git a/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md b/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md index 72bd30b61e..cc42901a49 100644 --- a/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md +++ b/_zh-cn/overviews/scala3-book/ca-multiversal-equality.md @@ -147,7 +147,7 @@ given CanEqual[PrintedBook, AudioBook] = CanEqual.derived given CanEqual[AudioBook, PrintedBook] = CanEqual.derived ``` -现在,您可以将实体书与有声书进行比较,而不会出现编译器错误: +现在,您可以将实体书与有声书进行比较,而不会出现编译错误: ```scala println(pBook == aBook) // false diff --git a/_zh-cn/overviews/scala3-book/concurrency.md b/_zh-cn/overviews/scala3-book/concurrency.md index 68027763f7..fd9dbbbbcd 100644 --- a/_zh-cn/overviews/scala3-book/concurrency.md +++ b/_zh-cn/overviews/scala3-book/concurrency.md @@ -130,19 +130,19 @@ res1: scala.concurrent.Future[Int] = Future(Success(84)) ### 在 future 中使用回调方法 除了像`map`这样的高阶函数,你还可以使用回调方法和futures。 -一种常用的回调方法是 `onComplete`,它采用*部分函数*,您可以在其中处理 `Success` 和 `Failure` 情况: +一种常用的回调方法是 `onComplete`,它采用*偏函数*,您可以在其中处理 `Success` 和 `Failure` 情况: ```scala eventualInt.onComplete { - case Success(value) => println(s"得到回调,value = $value") - 案例失败(e)=> e.printStackTrace + case Success(value) => println(s"Got the callback,value = $value") + case Failure(e) => e.printStackTrace } ``` 当您将该代码粘贴到 REPL 中时,您最终会看到结果: ```scala -收到回调,value = 42 +Got the callback, value = 42 ``` ## 其他 future 方法 diff --git a/_zh-cn/overviews/scala3-book/control-structures.md b/_zh-cn/overviews/scala3-book/control-structures.md index d52501eb45..cc233795db 100644 --- a/_zh-cn/overviews/scala3-book/control-structures.md +++ b/_zh-cn/overviews/scala3-book/control-structures.md @@ -17,7 +17,7 @@ Scala具有您希望在编程语言中找到的控制结构,包括: 它还具有另外两个您可能以前从未见过的强大结构,具体取决于您的编程背景: -- `for` 表达式(也被称作 _`for` 理解_) +- `for` 表达式(也被称作 _`for` comprehensions_) - `match` 表达式 这些都将在以下各节中进行演示。 @@ -175,7 +175,7 @@ i = 2, j = b, k = 1 i = 2, j = b, k = 6 ```` -### 防护 +### 守卫 `for` 循环也可以包含 `if` 语句,这些语句称为 _防护_: @@ -194,7 +194,7 @@ do 4 ```` -`for` 循环可以根据需要有任意数量的防护装置。 +`for` 循环可以根据需要有任意数量的守卫。 此示例显示了打印数字`4`的一种方法: ```scala From 6a507eac28cc814a37c15c08cae02c32681af55c Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Wed, 27 Jul 2022 10:52:09 +0800 Subject: [PATCH 32/53] modified according to review on Jul 25, 2022. --- .../ca-contextual-abstractions-intro.md | 2 +- .../overviews/scala3-book/control-structures.md | 4 ++-- .../overviews/scala3-book/domain-modeling-fp.md | 15 +++++++-------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md index 1c1560013e..e58fedc4aa 100644 --- a/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md +++ b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md @@ -51,7 +51,7 @@ Scala 3 没有提供一个非常强大的隐式特性,而是提供了几个面 它们是库作者的重要工具,可以表达简洁的领域特定语言。 - **来自编译器的可操作反馈**。 - 如果编译器无法解析隐式参数,它现在会为您提供 [导入建议](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions. html) 来解决问题。 + 如果编译器无法解析隐式参数,它现在会为您提供 [导入建议](https://www.scala-lang.org/blog/2020/05/05/scala-3-import-suggestions.html) 来解决问题。 ## 好处 diff --git a/_zh-cn/overviews/scala3-book/control-structures.md b/_zh-cn/overviews/scala3-book/control-structures.md index cc233795db..49686cea84 100644 --- a/_zh-cn/overviews/scala3-book/control-structures.md +++ b/_zh-cn/overviews/scala3-book/control-structures.md @@ -177,7 +177,7 @@ i = 2, j = b, k = 6 ### 守卫 -`for` 循环也可以包含 `if` 语句,这些语句称为 _防护_: +`for` 循环也可以包含 `if` 语句,这些语句称为 _守卫_: ```scala for @@ -416,7 +416,7 @@ i match #### 样例类和 match 表达式 -您还可以从 `case` 类中提取字段---以及正确编写了 `apply`/`unapply` 方法的类---并在防护条件下使用这些字段。 +您还可以从 `case` 类中提取字段 —— 以及正确编写了 `apply`/`unapply` 方法的类 —— 并在防护条件下使用这些字段。 下面是一个使用简单 `Person` 案例类的示例: ```scala diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-fp.md b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md index 7c43c0a1cb..c047eb479f 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-fp.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md @@ -126,17 +126,17 @@ case class Order( ) ``` -#### “瘦域对象” +#### “瘦领域对象(贫血模型)” -Debasish Ghosh 在他的《*功能和反应式域建模*》一书中指出,OOP 从业者将他们的类描述为封装数据和行为的“富域模型”,而 FP 数据模型可以被认为是“瘦域对象”。 -这是因为——正如本课所示——数据模型被定义为具有属性但没有行为的 `case` 类,从而产生了简短而简洁的数据结构。 +Debasish Ghosh 在他的《*函数式和反应式领域建模*》一书中指出,OOP 从业者将他们的类描述为封装数据和行为的“富领域模型(充血模型)”,而 FP 数据模型可以被认为是“瘦领域对象”。 +这是因为——正如本课所示——数据模型被定义为具有属性但没有行为的样例类,从而产生了简短而简洁的数据结构。 ## 操作建模 这就引出了一个有趣的问题:因为 FP 将数据与对该数据的操作分开,那么如何在 Scala 中实现这些操作? 答案实际上很简单:您只需编写对我们刚刚建模的数据值进行操作的函数(或方法)。 -例如,我们可以定义一个计算比萨价格的函数。 +例如,我们可以定义一个计算披萨价格的函数。 ```scala def pizzaPrice(p: Pizza): Double = p match @@ -262,7 +262,7 @@ Pizza.price(pizza1) 但是,还应权衡: - 它将功能与您的数据模型紧密结合。 - 特别是,伴生对象需要在与您的 `case` 类相同的文件中定义。 + 特别是,伴生对象需要在与您的样例类相同的文件中定义。 - 可能不清楚在哪里定义像 `crustPrice` 这样同样可以放置在 `CrustSize` 或 `CrustType` 的伴生对象中的函数。 ## 模块 @@ -372,7 +372,7 @@ println(price(p4)) // prints 8.75 #### 例子 -使用这种方法,您可以在案例案例中直接实现比萨上的功能: +使用这种方法,您可以在样例类中直接实现披萨上的功能: ```scala case class Pizza( crustSize: CrustSize, @@ -449,7 +449,7 @@ extension (p: Pizza) 在上面的代码中,我们将比萨上的不同方法定义为_扩展方法_。 对于 `extension (p: Pizza)`,我们想在 `Pizza` 的实例上让方法可用,并在下文中把扩展的实例称为 `p`。 -这样我们就可以获得和之前一样的API +这样我们就可以获得和之前一样的 API,同时能够在任何其他模块中定义扩展。 ```scala Pizza(Small, Thin, Seq(Cheese)) @@ -458,7 +458,6 @@ Pizza(Small, Thin, Seq(Cheese)) .price ``` -同时能够在任何其他模块中定义扩展。 通常,如果您是数据模型的设计者,您将在伴生对象中定义您的扩展方法。 这样,它们已经可供所有用户使用。 否则,扩展方法需要显式导入才能使用。 From f524cce56e6fe8e14160b21f9af7542a69fdb004 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Fri, 29 Jul 2022 08:48:40 +0800 Subject: [PATCH 33/53] Update _zh-cn/overviews/scala3-book/control-structures.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/control-structures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/control-structures.md b/_zh-cn/overviews/scala3-book/control-structures.md index 49686cea84..df732d6974 100644 --- a/_zh-cn/overviews/scala3-book/control-structures.md +++ b/_zh-cn/overviews/scala3-book/control-structures.md @@ -22,7 +22,7 @@ Scala具有您希望在编程语言中找到的控制结构,包括: 这些都将在以下各节中进行演示。 -## if/then/else 结构nstruct +## if/then/else 结构 单行 Scala `if` 语句像这样: From 774fa7d32164b68a3fd711858fec71366f26eee7 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Fri, 29 Jul 2022 08:49:15 +0800 Subject: [PATCH 34/53] Update _zh-cn/overviews/scala3-book/domain-modeling-tools.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/domain-modeling-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-tools.md b/_zh-cn/overviews/scala3-book/domain-modeling-tools.md index af76e6ca47..402b3104de 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-tools.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-tools.md @@ -326,7 +326,7 @@ val fred = Person("Fred", 29) ## Traits -如果你熟悉Java,Scala trait 类似于Java 8+中的接口。性状可以包含: +如果你熟悉Java,Scala trait 类似于Java 8+中的接口。特质可以包含: - 抽象方法和成员 - 具体方法和成员 From d64054ff00330a00a054f359b218f18eb35f1887 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Fri, 29 Jul 2022 08:49:28 +0800 Subject: [PATCH 35/53] Update _zh-cn/overviews/scala3-book/domain-modeling-tools.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/domain-modeling-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-tools.md b/_zh-cn/overviews/scala3-book/domain-modeling-tools.md index 402b3104de..d4e1f8979c 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-tools.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-tools.md @@ -437,7 +437,7 @@ trait 的组成更加灵活---您可以混合多个 trait,但只能扩展一 枚举可用于定义由一组有限的命名值组成的类型(在[FP建模][fp-modeling]一节中,我们将看到枚举比这更灵活)。 基本枚举用于定义常量集,如一年中的月份、一周中的天数、北/南/东/西方向等。 -例如,这些枚举定义了与比萨饼相关的属性集: +例如,这些枚举定义了与披萨饼相关的属性集: ```scala enum CrustSize: From e2163756a10e7b9c133ef460c93eae61c2b3bb61 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Fri, 29 Jul 2022 09:01:31 +0800 Subject: [PATCH 36/53] changed according to review on Jul 28, 2022. --- .../scala3-book/control-structures.md | 8 ++++---- .../scala3-book/domain-modeling-fp.md | 20 +++++++++---------- .../scala3-book/domain-modeling-oop.md | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/_zh-cn/overviews/scala3-book/control-structures.md b/_zh-cn/overviews/scala3-book/control-structures.md index df732d6974..0dfa1b845a 100644 --- a/_zh-cn/overviews/scala3-book/control-structures.md +++ b/_zh-cn/overviews/scala3-book/control-structures.md @@ -391,10 +391,10 @@ val evenOrOdd = i match case _ => println("some other number") ``` -### 在 `case` 子句中使用 `if` 防护 +### 在 `case` 子句中使用 `if` 守卫 -您还可以在匹配表达式的 `case` 中使用防护装置。 -在此示例中,第二个和第三个 `case` 都使用防护来匹配多个整数值: +您还可以在匹配表达式的 `case` 中使用守卫装置。 +在此示例中,第二个和第三个 `case` 都使用守卫来匹配多个整数值: ```scala i match @@ -416,7 +416,7 @@ i match #### 样例类和 match 表达式 -您还可以从 `case` 类中提取字段 —— 以及正确编写了 `apply`/`unapply` 方法的类 —— 并在防护条件下使用这些字段。 +您还可以从 `case` 类中提取字段 —— 以及正确编写了 `apply`/`unapply` 方法的类 —— 并在守卫条件下使用这些字段。 下面是一个使用简单 `Person` 案例类的示例: ```scala diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-fp.md b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md index c047eb479f..51f9f89487 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-fp.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md @@ -43,7 +43,7 @@ FP设计以类似的方式实现: > FP 中的数据只**是**: > 将功能与数据分离,让您无需担心行为即可检查数据。 -在本章中,我们将为比萨店中的“比萨”建模数据和操作。 +在本章中,我们将为披萨店中的“披萨”建模数据和操作。 您将看到如何实现 Scala/FP 模型的“数据”部分,然后您将看到几种不同的方式来组织对该数据的操作。 ## 数据建模 @@ -68,11 +68,11 @@ enum Topping: case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions ``` -> 描述不同选择的数据类型(如 `CrustSize`)有时也称为_聚合类型_。 +> 描述不同选择的数据类型(如 `CrustSize`)有时也称为_归纳类型_。 ### 描述复合数据 -可以将比萨饼视为上述不同属性的_组件_容器。 +可以将披萨饼视为上述不同属性的_组件_容器。 我们可以使用 `case` 类来描述 `Pizza` 由 `crustSize`、`crustType` 和可能的多个 `Topping` 组成: ```scala @@ -87,11 +87,11 @@ case class Pizza( ) ``` -> 聚合多个组件的数据类型(如`Pizza`)有时也称为_产品类型_。 +> 聚合多个组件的数据类型(如`Pizza`)有时也称为_乘积类型_。 就是这样。 -这就是 FP 式比萨系统的数据模型。 -该解决方案非常简洁,因为它不需要将比萨饼上的操作与数据模型相结合。 +这就是 FP 式披萨系统的数据模型。 +该解决方案非常简洁,因为它不需要将披萨饼上的操作与数据模型相结合。 数据模型易于阅读,就像声明关系数据库的设计一样。 创建数据模型的值并检查它们也很容易: @@ -102,7 +102,7 @@ println(myFavPizza.crustType) // prints Regular #### 更多数据模型 -我们可能会以同样的方式对整个比萨订购系统进行建模。 +我们可能会以同样的方式对整个披萨订购系统进行建模。 下面是一些用于对此类系统建模的其他 `case` 类: ```scala @@ -305,7 +305,7 @@ val p3 = updateCrustType(p2, Thick) val p4 = updateCrustSize(p3, Large) ``` -如果该代码看起来没问题,您通常会开始草拟另一个 API ——例如用于订单的 API ——但由于我们现在只关注比萨饼,我们将停止考虑接口,然后创建这个接口的具体实现。 +如果该代码看起来没问题,您通常会开始草拟另一个 API ——例如用于订单的 API ——但由于我们现在只关注披萨饼,我们将停止考虑接口,然后创建这个接口的具体实现。 > 请注意,这通常是一个两步过程。 > 在第一步中,您将 API 的合同草拟为*接口*。 @@ -398,7 +398,7 @@ case class Pizza( ``` 请注意,与之前的方法不同,因为这些是 `Pizza` 类上的方法,它们不会将 `Pizza` 引用作为输入参数。 -相反,他们用 `this` 作为当前比萨实例的引用。 +相反,他们用 `this` 作为当前披萨实例的引用。 现在你可以像这样使用这个新设计: @@ -446,7 +446,7 @@ extension (p: Pizza) p.copy(crustType = ct) ``` -在上面的代码中,我们将比萨上的不同方法定义为_扩展方法_。 +在上面的代码中,我们将披萨上的不同方法定义为_扩展方法_。 对于 `extension (p: Pizza)`,我们想在 `Pizza` 的实例上让方法可用,并在下文中把扩展的实例称为 `p`。 这样我们就可以获得和之前一样的 API,同时能够在任何其他模块中定义扩展。 diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md index 1510152aef..c0ac0601f9 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md @@ -149,7 +149,7 @@ open class Person(name: String) ``` 用 [`open`][open] 标记类是 Scala 3 的一个新特性。必须将类显式标记为开放可以避免面向对象设计中的许多常见缺陷。 -特别是,它要求库设计者明确计划扩展,例如记录标记为开放的类以及附加的扩展合同。 +特别是,它要求库设计者明确计划扩展,例如用额外的扩展契约来记录那些被标记为开放的类。 {% comment %} NOTE/FWIW: In his book, “Effective Java,” Joshua Bloch describes this as “Item 19: Design and document for inheritance or else prohibit it.” From 2f5c1db9f61abcd752e663db4b89f9e8c32426e7 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Wed, 10 Aug 2022 10:42:41 +0800 Subject: [PATCH 37/53] add scala4x.css in fold. Add next in scala-features.md --- .../overviews/scala3-book/scala-features.md | 2 +- _zh-cn/overviews/scala3-book/scala4x.css | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 _zh-cn/overviews/scala3-book/scala4x.css diff --git a/_zh-cn/overviews/scala3-book/scala-features.md b/_zh-cn/overviews/scala3-book/scala-features.md index d5e1ccef3e..9882b67911 100644 --- a/_zh-cn/overviews/scala3-book/scala-features.md +++ b/_zh-cn/overviews/scala3-book/scala-features.md @@ -4,7 +4,7 @@ type: chapter description: This page discusses the main features of the Scala 3 programming language. num: 2 previous-page: introduction -next-page: +next-page: why-scala-3 scala3: true partof: scala3-book diff --git a/_zh-cn/overviews/scala3-book/scala4x.css b/_zh-cn/overviews/scala3-book/scala4x.css new file mode 100644 index 0000000000..c7d34705bb --- /dev/null +++ b/_zh-cn/overviews/scala3-book/scala4x.css @@ -0,0 +1,59 @@ + + + From 2deccccfa7860db3beacaa8ad95bda7a05acc0f6 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 21 Aug 2022 19:20:07 +0800 Subject: [PATCH 38/53] Update _zh-cn/overviews/scala3-book/domain-modeling-fp.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/domain-modeling-fp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-fp.md b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md index 51f9f89487..4fbcc32226 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-fp.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md @@ -8,7 +8,7 @@ next-page: methods-intro --- -本章介绍了在 Scala 3 中使用函数式编程 (FP) 进行域建模。 +本章介绍了在 Scala 3 中使用函数式编程 (FP) 进行领域建模。 当使用 FP 对我们周围的世界进行建模时,您通常会使用以下 Scala 构造: - 枚举 From 0051e8b533dc7f0ba643aa41cef69964f750d6ff Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 21 Aug 2022 19:20:36 +0800 Subject: [PATCH 39/53] Update _zh-cn/overviews/scala3-book/domain-modeling-oop.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/domain-modeling-oop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md index c0ac0601f9..789ca35ea2 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md @@ -1,5 +1,5 @@ --- -title: OOP 建模 +title: OOP 领域建模 type: section description: This chapter provides an introduction to OOP domain modeling with Scala 3. num: 21 From 6c267bf4bca2c7e5e8dabbb3f7cb6a47fb5462dd Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 21 Aug 2022 19:20:51 +0800 Subject: [PATCH 40/53] Update _zh-cn/overviews/scala3-book/fun-write-map-function.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/fun-write-map-function.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/fun-write-map-function.md b/_zh-cn/overviews/scala3-book/fun-write-map-function.md index 5183a1dd0b..cbec37c7e7 100644 --- a/_zh-cn/overviews/scala3-book/fun-write-map-function.md +++ b/_zh-cn/overviews/scala3-book/fun-write-map-function.md @@ -1,5 +1,5 @@ --- -title: 写你自己的 map 方法 +title: 自定义 map 函数 type: section description: This page demonstrates how to create and use higher-order functions in Scala. num: 32 From a523765c8b1a835dac05aca336613ebc325cbccd Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 21 Aug 2022 19:21:05 +0800 Subject: [PATCH 41/53] Update _zh-cn/overviews/scala3-book/fun-write-map-function.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/fun-write-map-function.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/fun-write-map-function.md b/_zh-cn/overviews/scala3-book/fun-write-map-function.md index cbec37c7e7..6c97135a5f 100644 --- a/_zh-cn/overviews/scala3-book/fun-write-map-function.md +++ b/_zh-cn/overviews/scala3-book/fun-write-map-function.md @@ -60,7 +60,7 @@ def map[A](f: (Int) => A, xs: List[Int]): List[A] = for x <- xs yield f(x) ``` -### 使其通型化 +### 使其泛型化 作为奖励,请注意 `for` 表达式不做任何取决于 `List` 中的类型为 `Int` 的事情。 因此,您可以将类型签名中的 `Int` 替换为泛型类型参数 `B`: From 4073ceda0eb18f8df3646c35293b2dd038ea90c8 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 21 Aug 2022 19:21:20 +0800 Subject: [PATCH 42/53] Update _zh-cn/overviews/scala3-book/methods-most.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/methods-most.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/methods-most.md b/_zh-cn/overviews/scala3-book/methods-most.md index abc8cd96df..efbeebdecf 100644 --- a/_zh-cn/overviews/scala3-book/methods-most.md +++ b/_zh-cn/overviews/scala3-book/methods-most.md @@ -374,7 +374,7 @@ aCircle.area - 编写一个带有函数参数的方法 - 创建内嵌方法 - 处理异常 -- 使用可变参数输入参数 +- 使用可变参数作为输入参数 - 编写具有多个参数组的方法(部分应用的函数) - 创建具有泛型类型参数的方法 From 5a1aa41fc09d6021083bbce156d0c44c7c34c8f4 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 21 Aug 2022 19:21:32 +0800 Subject: [PATCH 43/53] Update _zh-cn/overviews/scala3-book/scala-for-java-devs.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/scala-for-java-devs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/scala-for-java-devs.md b/_zh-cn/overviews/scala3-book/scala-for-java-devs.md index af719abbab..cf733b0275 100644 --- a/_zh-cn/overviews/scala3-book/scala-for-java-devs.md +++ b/_zh-cn/overviews/scala3-book/scala-for-java-devs.md @@ -1,5 +1,5 @@ --- -title: 为 Java 开发者介绍Scala +title: 向 Java 开发者介绍Scala type: chapter description: This page is for Java developers who are interested in learning about Scala 3. num: 73 From 07192cfb19bf38826b8450ad8b860928547aed81 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 21 Aug 2022 19:55:26 +0800 Subject: [PATCH 44/53] Update _zh-cn/overviews/scala3-book/taste-objects.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/taste-objects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/taste-objects.md b/_zh-cn/overviews/scala3-book/taste-objects.md index 2cb04d4d14..076eda4a4c 100644 --- a/_zh-cn/overviews/scala3-book/taste-objects.md +++ b/_zh-cn/overviews/scala3-book/taste-objects.md @@ -1,5 +1,5 @@ --- -title: Singleton Objects +title: 单例对象 type: section description: This section provides an introduction to the use of singleton objects in Scala 3. num: 12 From 40f3e2c6acf421fd17080cbc59dabadc4160b332 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 21 Aug 2022 20:16:09 +0800 Subject: [PATCH 45/53] changed all review. --- .../overviews/scala3-book/domain-modeling-fp.md | 16 ++++++++-------- _zh-cn/overviews/scala3-book/scala-tools.md | 2 +- _zh-cn/overviews/scala3-book/tools-worksheets.md | 2 +- _zh-cn/overviews/scala3-book/types-generics.md | 2 +- _zh-cn/overviews/scala3-book/types-inferred.md | 2 +- .../overviews/scala3-book/types-intersection.md | 10 +++++----- .../overviews/scala3-book/types-opaque-types.md | 4 ++-- _zh-cn/overviews/scala3-book/types-others.md | 2 +- _zh-cn/overviews/scala3-book/types-structural.md | 12 ++++++------ _zh-cn/overviews/scala3-book/types-variance.md | 6 +++--- _zh-cn/overviews/scala3-book/why-scala-3.md | 2 +- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-fp.md b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md index 4fbcc32226..d89e51ca76 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-fp.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-fp.md @@ -1,5 +1,5 @@ --- -title: FP 建模 +title: 函数式领域建模 type: section description: This chapter provides an introduction to FP domain modeling with Scala 3. num: 22 @@ -50,12 +50,12 @@ FP设计以类似的方式实现: 在 Scala 中,描述编程问题的数据模型很简单: -- 如果您想使用不同的替代方案对数据进行建模,请使用 `enum` 结构 -- 如果您只想对事物进行分组(或需要更细粒度的控制),请使用 `case` 类 +- 如果您想使用不同的替代方案对数据进行建模,请使用 枚举 +- 如果您只想对事物进行分组(或需要更细粒度的控制),请使用 样例类 ### 描述替代方案 -简单地由不同的选择组成的数据,如面饼大小、面饼类型和馅料,使用 Scala 3 `enum` 结构进行简洁的建模: +简单地由不同的选择组成的数据,如面饼大小、面饼类型和馅料,使用 Scala 3 枚举进行简洁的建模: ```scala enum CrustSize: @@ -73,7 +73,7 @@ enum Topping: ### 描述复合数据 可以将披萨饼视为上述不同属性的_组件_容器。 -我们可以使用 `case` 类来描述 `Pizza` 由 `crustSize`、`crustType` 和可能的多个 `Topping` 组成: +我们可以使用 样例类来描述 `Pizza` 由 `crustSize`、`crustType` 和可能的多个 `Topping` 组成: ```scala import CrustSize.* @@ -103,7 +103,7 @@ println(myFavPizza.crustType) // prints Regular #### 更多数据模型 我们可能会以同样的方式对整个披萨订购系统进行建模。 -下面是一些用于对此类系统建模的其他 `case` 类: +下面是一些用于对此类系统建模的其他 样例类: ```scala case class Address( @@ -363,7 +363,7 @@ println(price(p4)) // prints 8.75 您可以将此方法视为“混合 FP/OOP 设计”,因为您: -- 使用不可变的 `case` 类对数据进行建模。 +- 使用不可变的 样例类对数据进行建模。 - 定义_同类型_数据中的行为(方法)。 - 将行为实现为纯函数:它们不会改变任何内部状态;相反,他们返回一个副本。 @@ -464,7 +464,7 @@ Pizza(Small, Thin, Seq(Cheese)) ## 这种方法的总结 -在 Scala/FP 中定义数据模型往往很简单:只需使用枚举对数据的变体进行建模,并使用 `case` 类对复合数据进行建模。 +在 Scala/FP 中定义数据模型往往很简单:只需使用枚举对数据的变体进行建模,并使用 样例类对复合数据进行建模。 然后,为了对行为建模,定义对数据模型的值进行操作的函数。 我们已经看到了组织函数的不同方法: diff --git a/_zh-cn/overviews/scala3-book/scala-tools.md b/_zh-cn/overviews/scala3-book/scala-tools.md index 1449ac690e..48e5522c7c 100644 --- a/_zh-cn/overviews/scala3-book/scala-tools.md +++ b/_zh-cn/overviews/scala3-book/scala-tools.md @@ -11,4 +11,4 @@ next-page: tools-sbt 本章介绍编写和运行 Scala 程序的两种方法: - 通过创建Scala项目,可能包含多个文件,并定义一个程序入口点, -- 通过与工作表交互,工作表是在单个文件中定义的程序,逐行执行。 +- 通过与练习册交互,练习册是在单个文件中定义的程序,逐行执行。 diff --git a/_zh-cn/overviews/scala3-book/tools-worksheets.md b/_zh-cn/overviews/scala3-book/tools-worksheets.md index 1c743ef250..2385e80f4d 100644 --- a/_zh-cn/overviews/scala3-book/tools-worksheets.md +++ b/_zh-cn/overviews/scala3-book/tools-worksheets.md @@ -1,5 +1,5 @@ --- -title: 工作表 +title: worksheet type: section description: This section looks at worksheets, an alternative to Scala projects. num: 71 diff --git a/_zh-cn/overviews/scala3-book/types-generics.md b/_zh-cn/overviews/scala3-book/types-generics.md index 7871b4c70e..d9b03e1ac0 100644 --- a/_zh-cn/overviews/scala3-book/types-generics.md +++ b/_zh-cn/overviews/scala3-book/types-generics.md @@ -41,6 +41,6 @@ println(stack.pop()) // prints 2 println(stack.pop()) // prints 1 ``` -> 有关如何用泛型类型表达可变的详细信息,请参阅[可变(Variance)部分][variance]。 +> 有关如何用泛型类型表达可变的详细信息,请参阅[型变(Variance)部分][variance]。 [variance]: {% link _overviews/scala3-book/types-variance.md %} diff --git a/_zh-cn/overviews/scala3-book/types-inferred.md b/_zh-cn/overviews/scala3-book/types-inferred.md index 645af43dbc..d20c704133 100644 --- a/_zh-cn/overviews/scala3-book/types-inferred.md +++ b/_zh-cn/overviews/scala3-book/types-inferred.md @@ -1,5 +1,5 @@ --- -title: 推断类型 +title: 类型推断 type: section description: This section introduces and demonstrates inferred types in Scala 3 num: 48 diff --git a/_zh-cn/overviews/scala3-book/types-intersection.md b/_zh-cn/overviews/scala3-book/types-intersection.md index d716d1b00e..60d4788b2f 100644 --- a/_zh-cn/overviews/scala3-book/types-intersection.md +++ b/_zh-cn/overviews/scala3-book/types-intersection.md @@ -1,5 +1,5 @@ --- -title: 交集类型 +title: 相交类型 type: section description: This section introduces and demonstrates intersection types in Scala 3. num: 50 @@ -8,9 +8,9 @@ next-page: types-union --- -用于类型,`&` 运算符创建一个所谓的_交集类型_。 +用于类型,`&` 运算符创建一个所谓的_相交类型_。 `A & B` 类型表示同时是 `A` 类型和 `B` 类型**两者**的值。 -例如,以下示例使用交集类型 `Resettable & Growable[String]`: +例如,以下示例使用相交类型 `Resettable & Growable[String]`: ```scala trait Resettable: @@ -26,10 +26,10 @@ def f(x: Resettable & Growable[String]): Unit = 在本例中的方法 `f` 中,参数 `x` 必须*同时*既是 `Resettable` 也是 `Growable[String]`。 -交集类型 `A & B` 的_成员_既有 `A` 的所有成员,也有 `B` 的所有成员。 +相交类型 `A & B` 的_成员_既有 `A` 的所有成员,也有 `B` 的所有成员。 因此,如图所示,`Resettable & Growable[String]` 具有成员方法 `reset` 和 `add`。 -交集类型可用于_结构性_地描述需求。 +相交类型可用于_结构性_地描述需求。 也就是说,在我们的示例 `f` 中,我们直接表示只要 `x` 是 `Resettable` 和 `Growable` 的子类型的任意值, 我们就感到满意。 我们**不**需要创建一个_通用_的辅助 trait,如下所示: diff --git a/_zh-cn/overviews/scala3-book/types-opaque-types.md b/_zh-cn/overviews/scala3-book/types-opaque-types.md index bd66bb4f95..d75e4c22ca 100644 --- a/_zh-cn/overviews/scala3-book/types-opaque-types.md +++ b/_zh-cn/overviews/scala3-book/types-opaque-types.md @@ -85,9 +85,9 @@ object LogarithmsImpl extends Logarithms: 在 `LogarithmsImpl` 的实现中,等式 `Logarithm = Double` 允许我们实现各种方法。 -#### 泄漏抽象 +#### 暴露抽象 -但是,这种抽象有点泄漏。 +但是,这种抽象有点暴露。 我们必须确保_只_针对抽象接口 `Logarithms` 进行编程,并且永远不要直接使用 `LogarithmsImpl`。 直接使用 `LogarithmsImpl` 会使等式 `Logarithm = Double` 对用户可见,用户可能会意外使用 `Double`,而实际上是需要 对数双精度。 例如: diff --git a/_zh-cn/overviews/scala3-book/types-others.md b/_zh-cn/overviews/scala3-book/types-others.md index fb1f979676..220fd7eec2 100644 --- a/_zh-cn/overviews/scala3-book/types-others.md +++ b/_zh-cn/overviews/scala3-book/types-others.md @@ -15,7 +15,7 @@ Scala还有其他几种高级类型,本书中没有介绍,包括: - 存在类型 - 高等类型 - 单例类型 -- 精简类型 +- 细化类型 - 种类多态性 有关这些类型的更多详细信息,请参阅[参考文档][reference]。 diff --git a/_zh-cn/overviews/scala3-book/types-structural.md b/_zh-cn/overviews/scala3-book/types-structural.md index 049c20ae77..662aa57cd9 100644 --- a/_zh-cn/overviews/scala3-book/types-structural.md +++ b/_zh-cn/overviews/scala3-book/types-structural.md @@ -1,5 +1,5 @@ --- -title: 构造类型 +title: 结构化类型 type: section description: This section introduces and demonstrates structural types in Scala 3. num: 55 @@ -21,12 +21,12 @@ NOTE: It would be nice to simplify this more. 这需要大量样板文件,这导致开发人员将静态类型的优势换成更简单的方案,其中列名表示为字符串并传递给其他运算符,例如 `row.select("columnName")`。 这种方法即便放弃了静态类型的优点,也仍然不如动态类型的版本自然。 -在您希望在动态上下文中支持简单的点表示法而又不失静态类型优势的情况下,构造类型会有所帮助。 +在您希望在动态上下文中支持简单的点表示法而又不失静态类型优势的情况下,结构化类型会有所帮助。 它们允许开发人员使用点表示法并配置应如何解析字段和方法。 ## 例子 -这是一个构造类型 `Person` 的示例: +这是一个结构化类型 `Person` 的示例: ```scala class Record(elems: (String, Any)*) extends Selectable: @@ -57,10 +57,10 @@ println(s"${person.name} is ${person.age} years old.") 该参数是一个序列,该序列的元素是 `String` 类型的标签和 `Any` 类型的值组成的对。 当您将 `Person` 创建为 `Record` 时,您必须使用类型转换断言该记录定义了正确类型的正确字段。 `Record` 本身的类型太弱了,所以编译器在没有用户帮助的情况下无法知道这一点。 -实际上,构造类型与其底层通用表示之间的连接很可能由数据库层完成,因此最终用户没必要关注。 +实际上,结构化类型与其底层通用表示之间的连接很可能由数据库层完成,因此最终用户没必要关注。 `Record` 扩展了标记 trait `scala.Selectable` 并定义了一个方法 `selectDynamic`,它将字段名称映射到其值。 -通过调用此方法来选择构造类型成员。 +通过调用此方法来选择结构化类型成员。 Scala 编译器把选择 `person.name` 和 `person.age` 翻译成: ```scala @@ -70,7 +70,7 @@ person.selectDynamic("age").asInstanceOf[Int] ## 第二个例子 -为了强化您刚刚看到的内容,这里有另一个名为 `Book` 的构造类型,它表示您可能从数据库中读取的一本书: +为了强化您刚刚看到的内容,这里有另一个名为 `Book` 的结构化类型,它表示您可能从数据库中读取的一本书: ```scala type Book = Record { diff --git a/_zh-cn/overviews/scala3-book/types-variance.md b/_zh-cn/overviews/scala3-book/types-variance.md index 6f00f6879d..aef80cb9cc 100644 --- a/_zh-cn/overviews/scala3-book/types-variance.md +++ b/_zh-cn/overviews/scala3-book/types-variance.md @@ -1,5 +1,5 @@ --- -title: 变型 +title: 型变 type: section description: This section introduces and demonstrates variance in Scala 3. num: 53 @@ -8,9 +8,9 @@ next-page: types-opaque-types --- -类型参数_变型_控制参数化类型(如类或 traits)的子类型。 +类型参数_型变_控制参数化类型(如类或 traits)的子类型。 -为了解释变型,让我们假设以下类型定义: +为了解释型变,让我们假设以下类型定义: ```scala trait Item { def productNumber: String } diff --git a/_zh-cn/overviews/scala3-book/why-scala-3.md b/_zh-cn/overviews/scala3-book/why-scala-3.md index 00c37f54af..c4b3f0eaa0 100644 --- a/_zh-cn/overviews/scala3-book/why-scala-3.md +++ b/_zh-cn/overviews/scala3-book/why-scala-3.md @@ -326,7 +326,7 @@ _简化_ 来自数十个更改和删除的特性。 _消除不一致_ 与Scala 3中的几十个[删除的特性][dropped]、[改变的特性][changed]和[增加的特性][add]有关。 此类别中一些最重要的功能是: -- Intersection类型 +- Intersection type - 联合类型 - 隐式函数类型 - 依赖函数类型 From e9086835b3be600bfd59d9015a814c3d49477b99 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 28 Aug 2022 11:11:05 +0800 Subject: [PATCH 46/53] Update _zh-cn/overviews/scala3-book/domain-modeling-oop.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/domain-modeling-oop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md index 789ca35ea2..319f0e1815 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md @@ -60,7 +60,7 @@ class Document(text: String) extends Showable: - 抽象方法(`def m(): T`) - 抽象值定义(`val x: T`) - 抽象类型成员(`type T`),可能有界限(`type T <: S`) -- 抽象给定(`given t: T`) +- 抽象given(`given t: T`) 上述每个特性都可用于指定对 trait 实现者的某种形式的要求。 From bf1864968b12a856dd2daa625be7f33da6d57913 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 28 Aug 2022 11:11:25 +0800 Subject: [PATCH 47/53] Update _zh-cn/overviews/scala3-book/domain-modeling-oop.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/domain-modeling-oop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md index 319f0e1815..597ca28563 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md @@ -131,7 +131,7 @@ val s4: Showable = s1 // ... and so on ... ``` -#### 扩展计划 +#### 扩展规划 如前所述,可以扩展另一个类: From 3db8200d9c0a54a53e35019beb5608b490b3fc70 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 28 Aug 2022 11:11:52 +0800 Subject: [PATCH 48/53] Update _zh-cn/overviews/scala3-book/methods-most.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/methods-most.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/methods-most.md b/_zh-cn/overviews/scala3-book/methods-most.md index efbeebdecf..4ff64a33cc 100644 --- a/_zh-cn/overviews/scala3-book/methods-most.md +++ b/_zh-cn/overviews/scala3-book/methods-most.md @@ -16,7 +16,7 @@ Scala 方法有很多特性,包括: - 通用(类型)参数 - 默认参数值 -- 多个参数组 +- 多个参数组(柯里化) - 上下文提供的参数 - 按名称参数 - ... From 349b19d78d99ce5b07c9285f2cae7ce05be78bfd Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 28 Aug 2022 11:12:03 +0800 Subject: [PATCH 49/53] Update _zh-cn/overviews/scala3-book/methods-most.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/methods-most.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/methods-most.md b/_zh-cn/overviews/scala3-book/methods-most.md index 4ff64a33cc..6208ce63c1 100644 --- a/_zh-cn/overviews/scala3-book/methods-most.md +++ b/_zh-cn/overviews/scala3-book/methods-most.md @@ -15,7 +15,7 @@ next-page: methods-main-methods Scala 方法有很多特性,包括: - 通用(类型)参数 -- 默认参数值 +- 参数的默认值 - 多个参数组(柯里化) - 上下文提供的参数 - 按名称参数 From 0ef10addb3234a1f59e1df83fc1a4049acf12355 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 28 Aug 2022 11:12:21 +0800 Subject: [PATCH 50/53] Update _zh-cn/overviews/scala3-book/methods-most.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/methods-most.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/methods-most.md b/_zh-cn/overviews/scala3-book/methods-most.md index 6208ce63c1..a6b46f3551 100644 --- a/_zh-cn/overviews/scala3-book/methods-most.md +++ b/_zh-cn/overviews/scala3-book/methods-most.md @@ -370,7 +370,7 @@ aCircle.area 还有更多关于方法的知识,包括如何: - 调用超类的方法 -- 定义和使用按名称参数 +- 定义和使用传名参数 - 编写一个带有函数参数的方法 - 创建内嵌方法 - 处理异常 From 850bee62cba484844f0ef9ba935bfefbe26c0351 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 28 Aug 2022 11:43:47 +0800 Subject: [PATCH 51/53] Update _zh-cn/overviews/scala3-book/domain-modeling-oop.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 梦境迷离 <568845948@qq.com> --- _zh-cn/overviews/scala3-book/domain-modeling-oop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md index 597ca28563..0f712c27c6 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md @@ -194,7 +194,7 @@ c1.count // 2 这些示例改编自 Martin Odersky 和 ​​Matthias Zenger 的论文 ["Scalable Component Abstractions"][scalable]。 如果您不了解示例的所有细节,请不要担心;它的主要目的是演示如何使用多种类型特性来构造更大的组件。 -我们的目标是定义一个具有_类型族_的软件组件,以后可以在组件的实现中对其进行细化。 +我们的目标是定义一个_种类繁多_的软件组件,而对组件的细化,可以放到以后的实现中 具体来说,以下代码将组件 `SubjectObserver` 定义为具有两个抽象类型成员的trait, `S` (用于主题)和 `O` (用于观察者): ```scala From 1cf9b12f250d796bc1704827f38a518b4572ae4d Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Sun, 28 Aug 2022 11:50:18 +0800 Subject: [PATCH 52/53] all have been changed according to review. --- _zh-cn/overviews/scala3-book/domain-modeling-oop.md | 2 +- _zh-cn/overviews/scala3-book/methods-most.md | 4 ++-- _zh-cn/overviews/scala3-book/why-scala-3.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md index 0f712c27c6..92b9c86597 100644 --- a/_zh-cn/overviews/scala3-book/domain-modeling-oop.md +++ b/_zh-cn/overviews/scala3-book/domain-modeling-oop.md @@ -194,7 +194,7 @@ c1.count // 2 这些示例改编自 Martin Odersky 和 ​​Matthias Zenger 的论文 ["Scalable Component Abstractions"][scalable]。 如果您不了解示例的所有细节,请不要担心;它的主要目的是演示如何使用多种类型特性来构造更大的组件。 -我们的目标是定义一个_种类繁多_的软件组件,而对组件的细化,可以放到以后的实现中 +我们的目标是定义一个_种类丰富_的软件组件,而对组件的细化,可以放到以后的实现中 具体来说,以下代码将组件 `SubjectObserver` 定义为具有两个抽象类型成员的trait, `S` (用于主题)和 `O` (用于观察者): ```scala diff --git a/_zh-cn/overviews/scala3-book/methods-most.md b/_zh-cn/overviews/scala3-book/methods-most.md index a6b46f3551..79e31c55bb 100644 --- a/_zh-cn/overviews/scala3-book/methods-most.md +++ b/_zh-cn/overviews/scala3-book/methods-most.md @@ -14,11 +14,11 @@ next-page: methods-main-methods Scala 方法有很多特性,包括: -- 通用(类型)参数 +- 泛型(类型)参数 - 参数的默认值 - 多个参数组(柯里化) - 上下文提供的参数 -- 按名称参数 +- 传名参数 - ... 本节演示了其中一些功能,但是当您定义一个不使用这些功能的“简单”方法时,语法如下所示: diff --git a/_zh-cn/overviews/scala3-book/why-scala-3.md b/_zh-cn/overviews/scala3-book/why-scala-3.md index c4b3f0eaa0..ae464e9884 100644 --- a/_zh-cn/overviews/scala3-book/why-scala-3.md +++ b/_zh-cn/overviews/scala3-book/why-scala-3.md @@ -31,8 +31,8 @@ NOTE: Could mention “grammar” as a way of showing that Scala isn’t a large Scala 比任何其他语言都更支持 FP 和 OOP 范式的融合。 正如 Martin Odersky 所说,Scala 的本质是在类型化环境中融合了函数式和面向对象编程,具有: -- 逻辑函数,以及 -- 模块化对象 +- 函数用于编写逻辑 (局部) +- 对象用于构建模块化 (整体) 模块化的一些最佳示例可能是标准库中的类。 例如,`List` 被定义为一个类---从技术上讲,它是一个抽象类---并且像这样创建了一个新实例: From 4e32489c253e19772013a4604ad7b0d770fcd089 Mon Sep 17 00:00:00 2001 From: Ben Luo Date: Fri, 2 Sep 2022 23:26:33 +0800 Subject: [PATCH 53/53] changed term and delete "next-page" in scala-features.md --- _zh-cn/overviews/scala3-book/scala-features.md | 2 +- _zh-cn/overviews/scala3-book/why-scala-3.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/_zh-cn/overviews/scala3-book/scala-features.md b/_zh-cn/overviews/scala3-book/scala-features.md index 9882b67911..d5e1ccef3e 100644 --- a/_zh-cn/overviews/scala3-book/scala-features.md +++ b/_zh-cn/overviews/scala3-book/scala-features.md @@ -4,7 +4,7 @@ type: chapter description: This page discusses the main features of the Scala 3 programming language. num: 2 previous-page: introduction -next-page: why-scala-3 +next-page: scala3: true partof: scala3-book diff --git a/_zh-cn/overviews/scala3-book/why-scala-3.md b/_zh-cn/overviews/scala3-book/why-scala-3.md index ae464e9884..00d7dac8b8 100644 --- a/_zh-cn/overviews/scala3-book/why-scala-3.md +++ b/_zh-cn/overviews/scala3-book/why-scala-3.md @@ -326,8 +326,8 @@ _简化_ 来自数十个更改和删除的特性。 _消除不一致_ 与Scala 3中的几十个[删除的特性][dropped]、[改变的特性][changed]和[增加的特性][add]有关。 此类别中一些最重要的功能是: -- Intersection type -- 联合类型 +- 交集类型 +- 并集类型 - 隐式函数类型 - 依赖函数类型 - trait 参数