https://docs.scala-lang.org 网站上文档的学习笔记。
Getting Started
-
使用 Coursier安装 amm, sbt, sbtn, scala, scala-cli, scalac, scalafmt。
-
创建项目:
sbt new scala/scala3.g8 # Scala 3 sbt new scala/hello-world.g8 # Scala 2
Scala 3 例子:
@main
def HelloWorld(args: String*): Unit =
println("Hello world!")
Scala 2 例子:
object Main {
def main(args: Array[String]): Unit = {
println("Hello world!")
}
}
// 在 Scala 3 里不再推荐这种写法
// https://www.scala-lang.org/api/current/scala/App.html
object Main extends App {
println("Hello, World!")
}
-
输入
sbt
命令进入 SBT console,输入~run
以在文件发生变动时重新运行 main。按回车键中断run
命令,输入exit
或者 Ctrl-D 退出 sbt。 -
可选操作:
-
配置
.scalafmt.conf
version = "3.7.15" runner.dialect = scala3
-
配置 project/build.properties
sbt.version=1.9.8
-
配置 project/plugins.sbt,参考 sbt-softwaremaill 和 sbt-typelevel。
addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "2.0.18") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "2.0.18") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "2.0.18") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-browser-test-js" % "2.0.18")
-
Tour of Scala
-
Basics: val, var, block, function, method, class, case class, object, trait
-
Unified types:
Any -> Matchable -> {AnyVal -> {Unit, Boolean, Int, ...}, AnyRef/Object -> {String, ...} -> Null} -> Nothing
-
Tuples: 类似成员没名字的 case class
-
Class composition with mixins:
class C2 extends C1, T1
,T1 不 extends 任何类时,类似引入 Java interface 的默认方法,T1 extends C1 或其父类时,变相实现菱形多父类继承。 -
Extractor objects: object 或者 class 上的:
-
unapply()
方法,接收对象,返回Boolean
或者Option[T]
或者Option[(T1, ..., T2)]
-
unapplySeq()
方法,接收对象,返回Option[Seq[T]]
-
-
Variaences:
-
class Foo[+A]
covariant class,class Foo[-A]
contravariant class,class Foo[A]
invariant class -
Scala 和 C# 采用了 declaration-site variance,在类型申明时标记,而 Java 在类型申明时只支持 invariance,采用了 use-site variance,在使用时标记(通过
<? extends T>
和<? super T>
)
-
-
Upper type bounds:
class Foo[A <: SomeClass]
-
Lower type bounds:
class Foo[A >: SomeClass]
,同时有下界上界时:class Foo[A >: LowerBound <: UpperBound]
-
Inner classes: 内部类是 path-dependent type,跟外部类的对象实例相关,使用
OuterClass#InnerClass
语法来表达 Java 语义的内部类。 -
Self-type: 标记当前 trait 在使用时必须混入另一个或多个 trait
trait T: this: T1 with T2 with T3 =>
-
Contextual parameters (implicit parameters)
-
Scala 2: 定义
implicit object xxx extends SomeTrait
,使用def foo(...)(implicit some: SomeTrait)
-
Scala 3: 定义
given SomeTrait ...
, 使用def foo(...)(using some: SomeTrait)
-
寻找 given value 的规则:在调用处代码的上下文直接可以访问,其次是在 companion object 中用 given 或者 implicit 标记的成员
-
-
Implicit conversions
-
In Scala 3, an implicit conversion from type
S
to typeT
is defined by a given instance which has typescala.Conversion[S, T]
. -
In Scala 2, an implicit conversion from type
S
to typeT
is defined by either an implicit classT
that has a single parameter of typeS
, an implicit value which has function typeS => T
, or by an implicit method convertible to a value of that type. -
Scala 2 的 implicit class 功能被 Scala 3 的 extension methods 代替。
-
-
Operators:操作符的优先级按第一个字符分类:
(characters not shown below) * / % + - : < > = ! & ^ | (all letters, $, _)
-
Packages and imports
import pkg.xxx
从当前包开始找pkg
,没有的话从_root_
开始找,如果有命名冲突,则需要import _root_.pkg.xxx
来指定完整包路径。import pkg.given
从 pkg 中导入所有 given valuesimport pkg.{given T1, given T2}
从 pkg 中导入符合类型的 given valuesimport xxx.*
,import xxx.{A, B}
,import xxx.A as B
-
Top level definitions in packages
-
Scala 3 直接支持 top level definitions, Scala 2 需要包到
package object
里,这个语法在 Scala 3 废弃了。 -
Scala 3 使用
private object xxx extends XTrait; private object yyy extends YTrait
然后export xxx.*, yyy.*
来聚合多个 package 里的定义,Scala 2 使用object Agg extends XTrait with YTrait
-
Scala 3 Book
-
A Taste of Scala
-
字符串:
s"$x and ${expression}"
,f"$i%03d and $x%.3f"
,raw"x\ny"
,多行字符串用"""
包围,用""" |xxx""".stripMargin
去掉缩进的空白字符 。 -
控制结构:
-
if then else
-
for generator/guard do statement, for generator/guard yield expression,generator/guard 可以省略括号,也可以用圆括号和花括号,yield 后面可以省略括号或者用圆括号
-
x match case … => …
-
try … catch case e: IOException => … finally …
-
while … do …
-
-
-
A First Look at Types
- Scala 不建议使用 null,使用
-Yexplicit-nulls -Ysafe-init
后 Null 成为 Matchable 的子类型。局部import scala.language.unsafeNulls
方便迁移老代码。
- Scala 不建议使用 null,使用
-
Domain Modeling
- Tools:
- Scala 2 的 sealed trait + case class + case object 可以被 Scala 3 的 enum 语法代替。
- Scala 3 的 trait 跟 class 一样也可以接受参数,例如
trait Animal(name: String)
- OOP Modeling:
- trait 里可以包含的 abstract member:
- abstract methods (
def m(): T
) - abstract value definitions (
val x: T
) - abstract type members (
type T
), potentially with bounds (type T <: S
) - abstract givens (
given t: T
) SCALA 3 ONLY
- abstract methods (
- Scala 3 推荐只在继承树的叶子节点引入 class,继承树的其它节点尽量用 trait。Scala 3 也不推荐从一个文件里 extends 另一个文件里定义的非抽象类,除非被扩展类用
open class C
标记了被允许扩展,否则从 Scala 3.4 开始就会警告。
- trait 里可以包含的 abstract member:
- FP Modeling: 四种给 case class 增加行为的方式:
- 用 companion object,局限在跟 case class 定义同一个文件里,使用
method(obj)
调用; - 直接在 case class 里定义方法,局限在跟 case class 定义同一个文件里,使用
method(obj)
调用; - 用 trait + object,可以在其它文件里定义,使用
method(obj)
调用; - 用 extension 扩展方法,可以在其它文件里定义,使用
obj.method()
调用
- 用 companion object,局限在跟 case class 定义同一个文件里,使用
- Tools:
-
Methods
- 对于无参数的 method,约定用
def foo() = xxx
表示有副作用,用def foo = xxx
表示无副作用。
- 对于无参数的 method,约定用
-
Packaging and Imports
- 默认导入:
java.lang.*
,scala.*
,scala.Predef.*
- 默认导入:
-
Functional error handling: 使用
Option[T]
、Some
、None
或者Either[T, E]
、Left[E]
、Right[T]
或者 scala.util 包里的Try[T]
、Success
、Failure
来表达错误,后者用于会抛出异常的场景。使用match case
或者for yield
来处理错误。 -
Algebraic Data Types: ADT( 的每个构造器返回的类型相同,GADT 的每个构造器返回的类型可以不同。
-
Variance: 泛型参数只用于方法返回值,可以用 covariant,只用于方法参数,可以用 contravariant,两者都用了,则只能用 invariant。
// 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
-
Opaque types:
opaque type NewType = OriginalType
,类似类型别名,但是不暴露真实类型。 -
Structual types: 扩展了
Selectable
的类,可以映射到一个强类型的类型别名,按强类型的成员来访问,例如可以用类型安全的方式访问数据库返回的记录。 -
Dependent Function Types: Scala 2 支持 dependent method type,让方法的返回值的类型依赖参数的值,Scala 3 进一步支持 dependent function type,让函数的返回值的类型依赖参数的值,搭配 extension method, context function, depdent function 可以方便库设计者。
-
Contextual Bounds:
def maxElement[A: Ord](as: List[A]): A
等价于def maxElement[A](as: List[A])(using Ord[A]): A
,也即[A: Ord]
约束等价于需要一个 context parameter(using Ord[A])
-
Type classes: 使用的 parameterized trait 如
trait Showable[A]: extension (a: A) def show: String
定义,并用given Showable[Person] with extension (p: Person) def show: String = ...
来为某个类提供实现。与经典的 trait 或者 Java 的 interface 相比,好处在于:-
可以表达第三方源码里的类型遵循某个行为,不必修改其源代码;
-
可以表达多个类型遵循某个行为,不必在他们之间引入子类型关系;
-