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.confversion = "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} -> NothingTuples: 类似成员没名字的 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 classScala 和 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
Sto typeTis defined by a given instance which has typescala.Conversion[S, T].In Scala 2, an implicit conversion from type
Sto typeTis defined by either an implicit classTthat 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): UnitOpaque 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 相比,好处在于:可以表达第三方源码里的类型遵循某个行为,不必修改其源代码;
可以表达多个类型遵循某个行为,不必在他们之间引入子类型关系;