cv.cm

編碼 · 程式語言

Scala 入門教學

閱讀約 9 分鐘

簡答

Scala 是靜態型別的 JVM 語言,結合物件導向與函數式程式設計,廣泛用於 Apache Spark 資料工程與並行後端。它的主要特色是型別推斷、不可變集合、case class 與模式比對。本頁以 Scala 3 介紹環境設定、值、流程控制、函式、集合、trait、enum,以及用 Option、Either 和 Try 處理錯誤。

Scala 是什麼、可以拿來做什麼

Scala 是結合物件導向與函數式程式設計的靜態型別語言,由 EPFL 的 Martin Odersky 創造,主要執行於 Java 虛擬機器(JVM),另有用於瀏覽器的 Scala.js 與產生原生執行檔的 Scala Native。Scala 程式碼可以使用任何 Java 函式庫。

Scala 廣泛用於資料工程(Apache Spark 就是用 Scala 寫成的)、並行與分散式後端(Akka、Pekko、ZIO、Cats Effect),以及金融與串流系統。本教學使用 Scala 3 語法,允許以縮排表示程式區塊,使用大括號也仍然可行。

安裝 Scala 並執行 Hello, world

建議使用 Coursier 安裝(cs setup),它會一併安裝 scala 指令與 JDK。在 Scala 3 中,標註 @main 的方法就是程式進入點。

// hello.scala
@main def hello(): Unit =
  println("Hello, world!")
scala run hello.scala
# Hello, world!

較大型的專案通常使用 sbt 或 Mill 建置。Scastie(scastie.scala-lang.org)可以直接在瀏覽器中執行 Scala,不必安裝任何東西。

值、變數與基本型別

用 val 宣告不可變的值,用 var 宣告可變的變數。型別會自動推斷,也可以寫在冒號後面。慣用的 Scala 幾乎處處都使用 val。

val name = "Scala"            // String
var count: Int = 1            // mutable
count += 1
val ratio = 3.0 / 4           // Double: 0.75
val big = 12345678901L        // Long
val ok = count > 1            // Boolean
println(s"$name: count=$count ratio=$ratio ok=$ok")
// Scala: count=2 ratio=0.75 ok=true
println(f"pi is ${math.Pi}%.3f")   // pi is 3.142
型別範例說明
Int4232 位元有號整數
Long42L64 位元有號整數
Double3.1464 位元浮點數
Booleantruetrue 或 false
String"hi"Java 字串;支援 s"..." 與 f"..." 插值器
Unit()類似 void:沒有有意義的值

Scala 中一切都是運算式:if、程式區塊與 match 都會回傳值,所以你很少需要暫時用的 var。

流程控制與模式比對

if 會回傳值。for 可以走訪範圍與集合,搭配 yield 則會產生新的集合。match 是 Scala 強大的模式比對,也是這個語言的招牌功能。

val n = 7
val parity = if n % 2 == 0 then "even" else "odd"
println(parity)                        // odd
for i <- 1 to 3 do print(s"$i ")       // 1 2 3
println()
val squares = for i <- 1 to 4 yield i * i
println(squares)                       // Vector(1, 4, 9, 16)
def describe(x: Any): String = x match
  case 0             => "zero"
  case i: Int if i < 0 => "negative int"
  case i: Int        => s"int $i"
  case s: String     => s"string of length ${s.length}"
  case (a, b)        => s"pair $a and $b"
  case _             => "something else"
println(describe(-5))                  // negative int
println(describe("hey"))               // string of length 3
println(describe((1, "x")))            // pair 1 and x

1 to 3 包含 3;1 until 3 則在 3 之前停止。最後的 case _ 是萬用字元,可比對任何值;少了它,沒有比對到的值會拋出 MatchError。

函式與高階函式

方法用 def 定義。函式是值:可以儲存、當作引數傳遞,也可以被回傳。參數可以有預設值,呼叫時也可以使用具名引數。

def add(a: Int, b: Int): Int = a + b
def greet(name: String, greeting: String = "Hello"): String =
  s"$greeting, $name!"
val double = (x: Int) => x * 2        // function value
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
println(add(2, 3))                    // 5
println(greet("Ann"))                 // Hello, Ann!
println(greet("Ann", greeting = "Hi")) // Hi, Ann!
println(applyTwice(double, 5))        // 20
println(List(1, 2, 3, 4).filter(_ % 2 == 0).map(_ * 10)) // List(20, 40)

_ * 10 中的底線是單參數 Lambda x => x * 10 的簡寫。

集合

標準集合預設是不可變的:map、filter 與 :+ 等操作會回傳新集合,原本的集合保持不變。可變版本放在 scala.collection.mutable。

val nums = List(5, 3, 8)
val more = nums :+ 1                  // List(5, 3, 8, 1); nums unchanged
println(more.sorted)                  // List(1, 3, 5, 8)
println(nums.sum)                     // 16
val v = Vector("a", "b", "c")         // fast indexed access
println(v(1))                         // b
val ages = Map("Ann" -> 31, "Bo" -> 25)
println(ages("Ann"))                  // 31
println(ages.get("Zed"))              // None
println(ages.getOrElse("Zed", 0))     // 0
val unique = Set(1, 2, 2, 3)
println(unique.size)                  // 3
val words = List("apple", "avocado", "banana")
println(words.groupBy(_.head)('a'))  // List(apple, avocado)
集合適用情境
List不可變鏈結串列;在前端新增與存取頭尾都很快
Vector不可變的索引序列;很好的通用預設選擇
Map鍵值查找
Set元素不重複與成員檢查
Array可變、固定大小的 Java 陣列

Case class、trait 與 enum

case class 是不可變的資料型別,會自動取得相等比較、toString、copy 與模式比對支援。trait 類似介面,但可以包含程式碼。Scala 3 新增了 enum,用於代數資料型別。

trait Shape:
  def area: Double
case class Circle(r: Double) extends Shape:
  def area: Double = math.Pi * r * r
case class Rect(w: Double, h: Double) extends Shape:
  def area: Double = w * h
enum Color:
  case Red, Green, Blue
@main def shapes(): Unit =
  val all: List[Shape] = List(Circle(1), Rect(2, 3))
  all.foreach(s => println(f"$s area=${s.area}%.2f"))
  // Circle(1.0) area=3.14
  // Rect(2.0,3.0) area=6.00
  val r = Rect(2, 3)
  println(r == Rect(2, 3))            // true
  println(r.copy(h = 10))             // Rect(2.0,10.0)
  println(Color.values.toList)        // List(Red, Green, Blue)

其他招牌功能還包括 given 與 using(情境參數,以前稱為 implicit)、擴充方法、opaque type,以及 Int | String 這類聯集型別。

用 Option、Either 與 Try 處理錯誤

Scala 支援 try、catch 與 finally,但慣用的寫法是用型別來表示失敗。Option 不是 Some(value) 就是 None;Either 持有 Left 錯誤或 Right 結果;Try 則把例外包裝成 Failure。

import scala.util.{Try, Success, Failure}
def parseInt(s: String): Option[Int] = s.toIntOption
def safeDivide(a: Int, b: Int): Either[String, Int] =
  if b == 0 then Left("division by zero") else Right(a / b)
println(parseInt("42"))               // Some(42)
println(parseInt("x"))                // None
println(safeDivide(10, 2))            // Right(5)
println(safeDivide(1, 0))             // Left(division by zero)
Try("abc".toInt) match
  case Success(n) => println(s"got $n")
  case Failure(e) => println(s"failed: ${e.getClass.getSimpleName}")
// failed: NumberFormatException

完整的小程式:成績報告

這個程式解析 name,score 格式的每一行,略過無效的行,並印出每位學生的平均分數。它用到 case class、Option、groupBy 與字串格式化。

case class Score(name: String, value: Int)
def parse(line: String): Option[Score] =
  line.split(",").map(_.trim) match
    case Array(name, v) => v.toIntOption.map(Score(name, _))
    case _              => None
@main def report(): Unit =
  val lines = List("ann, 90", "bo, 70", "ann, 80", "broken line", "bo, x")
  val scores = lines.flatMap(parse)
  val averages = scores.groupBy(_.name).view.mapValues { list =>
    list.map(_.value).sum.toDouble / list.size
  }.toList.sortBy(_._1)
  for (name, avg) <- averages do
    println(f"$name%-4s $avg%.1f")
// ann  85.0
// bo   70.0

接下來可以學什麼

  • docs.scala-lang.org 上的 Scala 3 Book,從基礎到情境抽象都有涵蓋。
  • Scala Toolkit,用於處理檔案、JSON 與 HTTP 等日常任務。
  • 若想用 Scala 做資料處理,可參考 Apache Spark 文件。
  • scala-lang.org 連結的課程,包括 EPFL 的函數式程式設計課程。

常見問題

我該學 Scala 2 還是 Scala 3?

新專案請學 Scala 3。Scala 2.13 仍存在於許多既有程式碼庫中,包括 Spark 生態系的部分內容,而且大多數核心概念是相通的。

Scala 是函數式語言嗎?

Scala 同時是物件導向與函數式語言。每個值都是物件,但函式是一等值,集合預設不可變,模式比對也是核心。

Scala 的 val 和 var 有什麼差別?

val 定義無法重新指派的值,var 定義可以重新指派的變數。慣用的 Scala 偏好 val 搭配不可變集合。

什麼是 case class?

case class 是專為不可變資料設計的類別。編譯器會產生結構相等比較、易讀的 toString、copy 方法,以及模式比對支援。

Scala 現在還有人用嗎?

有。它仍是 Apache Spark、串流與資料平台,以及 ZIO、Cats Effect 等函數式後端函式庫的核心語言。

全部編碼教學