https://docs.scala-lang.org 网站上文档的学习笔记。

Getting Started

  1. 使用 Coursier安装 amm, sbt, sbtn, scala, scala-cli, scalac, scalafmt。

  2. 创建项目:

    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!")
}
  1. 输入 sbt 命令进入 SBT console,输入 ~run 以在文件发生变动时重新运行 main。按回车键中断 run 命令,输入 exit 或者 Ctrl-D 退出 sbt。

  2. 可选操作:

    1. 配置 .scalafmt.conf

      version = "3.7.15"
      runner.dialect = scala3
      
    2. 配置 project/build.properties

      sbt.version=1.9.8
      
    3. 配置 project/plugins.sbt,参考 sbt-softwaremaillsbt-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

  1. Basics: val, var, block, function, method, class, case class, object, trait

  2. Unified types: Any -> Matchable -> {AnyVal -> {Unit, Boolean, Int, ...}, AnyRef/Object -> {String, ...} -> Null} -> Nothing

  3. Tuples: 类似成员没名字的 case class

  4. Class composition with mixins: class C2 extends C1, T1 ,T1 不 extends 任何类时,类似引入 Java interface 的默认方法,T1 extends C1 或其父类时,变相实现菱形多父类继承。

  5. Extractor objects: object 或者 class 上的:

    1. unapply() 方法,接收对象,返回 Boolean 或者 Option[T] 或者 Option[(T1, ..., T2)]

    2. unapplySeq() 方法,接收对象,返回 Option[Seq[T]]

  6. Variaences:

    1. class Foo[+A] covariant class, class Foo[-A] contravariant class, class Foo[A] invariant class

    2. Scala 和 C# 采用了 declaration-site variance,在类型申明时标记,而 Java 在类型申明时只支持 invariance,采用了 use-site variance,在使用时标记(通过 <? extends T><? super T>)

  7. Upper type bounds: class Foo[A <: SomeClass]

  8. Lower type bounds: class Foo[A >: SomeClass] ,同时有下界上界时:class Foo[A >: LowerBound <: UpperBound]

  9. Inner classes: 内部类是 path-dependent type,跟外部类的对象实例相关,使用 OuterClass#InnerClass 语法来表达 Java 语义的内部类。

  10. Self-type: 标记当前 trait 在使用时必须混入另一个或多个 trait

    trait T:
       this: T1 with T2 with T3 =>
    
  11. Contextual parameters (implicit parameters)

    1. Scala 2: 定义 implicit object xxx extends SomeTrait ,使用 def foo(...)(implicit some: SomeTrait)

    2. Scala 3: 定义given SomeTrait ... , 使用 def foo(...)(using some: SomeTrait)

    3. 寻找 given value 的规则:在调用处代码的上下文直接可以访问,其次是在 companion object 中用 given 或者 implicit 标记的成员

  12. Implicit conversions

    1. In Scala 3, an implicit conversion from type S to type T is defined by a given instance which has type scala.Conversion[S, T].

    2. In Scala 2, an implicit conversion from type S to type T is defined by either an implicit class T that has a single parameter of type S, an implicit value which has function type S => T, or by an implicit method convertible to a value of that type.

    3. Scala 2 的 implicit class 功能被 Scala 3 的 extension methods 代替。

  13. Operators:操作符的优先级按第一个字符分类:

    (characters not shown below)
    * / %
    + -
    :
    < >
    = !
    &
    ^
    |
    (all letters, $, _)
    
  14. Packages and imports

    1. import pkg.xxx 从当前包开始找 pkg,没有的话从 _root_ 开始找,如果有命名冲突,则需要 import _root_.pkg.xxx 来指定完整包路径。
    2. import pkg.given 从 pkg 中导入所有 given values
    3. import pkg.{given T1, given T2} 从 pkg 中导入符合类型的 given values
    4. import xxx.*, import xxx.{A, B}, import xxx.A as B
  15. Top level definitions in packages

    1. Scala 3 直接支持 top level definitions, Scala 2 需要包到 package object 里,这个语法在 Scala 3 废弃了。

    2. 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

  1. A Taste of Scala

    1. 字符串: s"$x and ${expression}"f"$i%03d and $x%.3f"raw"x\ny",多行字符串用 """ 包围,用 """ |xxx""".stripMargin 去掉缩进的空白字符 。

    2. 控制结构:

      1. if then else

      2. for generator/guard do statement, for generator/guard yield expression,generator/guard 可以省略括号,也可以用圆括号和花括号,yield 后面可以省略括号或者用圆括号

      3. x match case … => …

      4. trycatch case e: IOException => … finally

      5. whiledo

  2. A First Look at Types

    1. Scala 不建议使用 null,使用 -Yexplicit-nulls -Ysafe-init 后 Null 成为 Matchable 的子类型。局部 import scala.language.unsafeNulls 方便迁移老代码。
  3. Domain Modeling

    1. Tools:
      • Scala 2 的 sealed trait + case class + case object 可以被 Scala 3 的 enum 语法代替。
      • Scala 3 的 trait 跟 class 一样也可以接受参数,例如 trait Animal(name: String)
    2. OOP Modeling:
      1. 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: TSCALA 3 ONLY
      2. Scala 3 推荐只在继承树的叶子节点引入 class,继承树的其它节点尽量用 trait。Scala 3 也不推荐从一个文件里 extends 另一个文件里定义的非抽象类,除非被扩展类用 open class C标记了被允许扩展,否则从 Scala 3.4 开始就会警告。
    3. FP Modeling: 四种给 case class 增加行为的方式:
      1. 用 companion object,局限在跟 case class 定义同一个文件里,使用 method(obj) 调用;
      2. 直接在 case class 里定义方法,局限在跟 case class 定义同一个文件里,使用 method(obj) 调用;
      3. 用 trait + object,可以在其它文件里定义,使用 method(obj) 调用;
      4. 用 extension 扩展方法,可以在其它文件里定义,使用 obj.method() 调用
  4. Methods

    1. 对于无参数的 method,约定用 def foo() = xxx 表示有副作用,用 def foo = xxx 表示无副作用。
  5. Packaging and Imports

    1. 默认导入:java.lang.*, scala.*, scala.Predef.*
  6. Functional error handling: 使用 Option[T]SomeNone 或者 Either[T, E]Left[E]Right[T] 或者 scala.util 包里的 Try[T]SuccessFailure 来表达错误,后者用于会抛出异常的场景。使用 match case 或者 for yield 来处理错误。

  7. Algebraic Data Types: ADT( 的每个构造器返回的类型相同,GADT 的每个构造器返回的类型可以不同。

  8. 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
    
  9. Opaque types: opaque type NewType = OriginalType,类似类型别名,但是不暴露真实类型。

  10. Structual types: 扩展了 Selectable 的类,可以映射到一个强类型的类型别名,按强类型的成员来访问,例如可以用类型安全的方式访问数据库返回的记录。

  11. Dependent Function Types: Scala 2 支持 dependent method type,让方法的返回值的类型依赖参数的值,Scala 3 进一步支持 dependent function type,让函数的返回值的类型依赖参数的值,搭配 extension method, context function, depdent function 可以方便库设计者。

  12. 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])

  13. 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 相比,好处在于:

    1. 可以表达第三方源码里的类型遵循某个行为,不必修改其源代码;

    2. 可以表达多个类型遵循某个行为,不必在他们之间引入子类型关系;