2004:Scala 誕生#
事件#
Scala 由瑞士洛桑聯邦理工學院(EPFL)的 Martin Odersky 設計,2003 年內部使用,2004 年公開 1.0 版,2006 年的 2.0 版做了重大重新設計。Odersky 先前參與了 Java 泛型的設計:他創造 Pizza 語言(1997),再與 Philip Wadler 合作將其簡化為 GJ(Generic Java),GJ 成為 Java 5 泛型的基礎。Scala 的目標是「可純縮放(scalable)」:將物件導向與函數式程式設計無縫融合於 JVM 之上。
語法/特性加入的理論與實用原因#
1. 函數式 + 物件導向的統合於 JVM#
- 理論原因:Odersky 主張「函數式是值的世界、物件導向是抽象的世界」,兩者可以統一——每個值都是物件、每個運算都是方法呼叫,函數本身是一等公民物件。
- 實用原因:Java 1.4 沒有函數值、泛型與模式匹配,但擁有龐大的 JVM 生態;編譯到 JVM 位元組碼可以直接重用所有 Java 函式庫。
- 彌補缺陷:函數式語言(ML、Haskell)無法重用 JVM 生態;Java 沒有函數式表達力——Scala 兩邊都補上。
// 函數是一等公民,集合操作管道化
val names = people.filter(_.age > 18).map(_.name).sorted
對照 Java 1.4:同樣的過濾排序要寫匿名類別與手動迴圈,Scala 一行管道解決:
// Java 1.4:沒有函數值,只能用匿名類別 + 迴圈
List adultNames = new ArrayList();
for (Iterator it = people.iterator(); it.hasNext(); ) {
Person p = (Person) it.next(); // 強制轉型
if (p.getAge() > 18) adultNames.add(p.getName());
}
Collections.sort(adultNames);
2. case class 與模式匹配#
- 理論原因:將 ML 的代數資料型別(ADT)與模式匹配引進物件世界:case class 是「不可變資料 + 自動生成的結構相等性 + 可分解性」,模式匹配在理論上對應資料建構子的對偶(deconstruction)。
- 實用原因:編譯器會對匹配的完整性(exhaustiveness)做靜態檢查,減少漏處理分支的 bug;這成為 Scala 處理資料的主流風格。
- 彌補缺陷:Java 的 instanceof + 強制轉型既冗長又不安全;case class 模式匹配提供型別安全的分解。
sealed trait Shape
case class Circle(r: Double) extends Shape
case class Rect(w: Double, h: Double) extends Shape
def area(s: Shape): Double = s match {
case Circle(r) => math.Pi * r * r
case Rect(w, h) => w * h
}
對照 Java:同樣的形狀分解要靠 instanceof + 強制轉型,漏分支也不會被編譯器警告:
// Java 1.4:instanceof 冗長、轉型不安全、漏處理分支無法靜態檢查
double area(Shape s) {
if (s instanceof Circle) {
return Math.PI * ((Circle) s).r * ((Circle) s).r;
} else if (s instanceof Rect) {
Rect r = (Rect) s;
return r.w * r.h;
}
throw new IllegalArgumentException("unknown shape"); // 只能靠執行期才發現
}
sealed trait 封閉型別階層,編譯器檢查 match 的完整性;模式匹配還能解構嵌套結構:
sealed trait Expr
case class Add(a: Expr, b: Expr) extends Expr
case class Num(n: Int) extends Expr
case class Neg(a: Expr) extends Expr
// 巢狀模式匹配:直接解構運算式樹
def eval(e: Expr): Int = e match {
case Num(n) => n
case Neg(a) => -eval(a)
case Add(Num(a), Num(b)) => a + b // 特殊分支:兩個常數相加
case Add(a, b) => eval(a) + eval(b)
}
3. trait 與 mix-in 繼承#
- 理論原因:trait 是「無建構子參數的介面 + 部分實作」,以線性化(linearization)規則解決多重繼承的菱形問題,理論上接近 mix-in 模組。
- 實用原因:比 Java 介面(當時無預設方法)更有表達力,比抽象類別更靈活可組合。
- 彌補缺陷:Java 介面無法攜帶實作,程式碼重用受限。
trait 混合(mix-in):以 with 疊加多個 trait,線性化規則保證組合順序明確:
trait Greeter { def greet(name: String): String = s"Hello, $name" }
trait FormalGreeter extends Greeter {
override def greet(name: String): String = s"Dear ${name},你好"
}
// 線性化:with 的後者覆蓋前者
class Service extends Greeter with FormalGreeter
new Service().greet("阿明") // "Dear 阿明,你好"
4. implicit(隱式轉換與隱式參數)#
- 理論原因:以型別推論機制解決「型別類別式多載」:隱式參數讓演算法可以泛化到任意型別而不改變呼叫端語法,理論上是 Haskell 型別類別的替身。
- 實用原因:2.10(2012)起 implicits 擴展(implicit class、implicit macro)成為生態核心;2021 年 Scala 3 以 given/using 重新設計,讓隱式機制更明確可預測。
- 彌補缺陷:Java 完全沒有擴展既有型別、泛化多載的機制。
implicit 的兩個典型用法:隱式參數扮演 Haskell 型別類別字典的角色,隱式類別替既有型別加方法:
// 隱式參數:演算法泛化到任意型別,呼叫端語法不變(Scala 2.10 前樣式)
def max[T](a: T, b: T)(implicit ord: Ordering[T]): T =
if (ord.gt(a, b)) a else b
max(3, 5) // Ordering[Int] 由編譯器自動找到
max("apple", "banana") // Ordering[String] 同樣自動找到
// 隱式類別(implicit class):為既有型別「擴充」方法
implicit class RichString(s: String) {
def shout: String = s.toUpperCase + "!"
}
"hello".shout // "HELLO!"
5. Higher-kinded types#
- 理論原因:允許型別抽象以「型別建構子」為參數(如
F[_]),理論上支援泛化到容器層次的抽象(monad、functor 抽象)。 - 實用原因:使 Scalaz、Cats 等函數式函式庫能在 Scala 上重現 Haskell 的抽象層次。
- 彌補缺陷:Java 泛型(GJ)不支援高階多型,無法表達
Monad<F>這類抽象。
高階型別(Higher-kinded types):以 F[_] 抽象出「容器」層次,這是 Java 泛型做不到的:
// F[_] 是型別建構子參數:F 可代入 List、Option 等「容器」
trait Functor[F[_]] {
def fmap[A, B](fa: F[A])(f: A => B): F[B]
}
implicit val listFunctor: Functor[List] = new Functor[List] {
def fmap[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f)
}
implicit val optFunctor: Functor[Option] = new Functor[Option] {
def fmap[A, B](fa: Option[A])(f: A => B): Option[B] = fa.map(f)
}
def twice[F[_]](fa: F[Int])(implicit F: Functor[F]): F[Int] =
F.fmap(fa)(_ * 2)
twice(List(1, 2, 3)) // List(2, 4, 6)
twice(Some(21)) // Some(42)
JVM 互操作:Scala 直接呼叫所有 Java API,無需任何橋接層:
// 直接使用 Java 標準函式庫
import java.util.HashMap
val m = new HashMap[String, Int]()
m.put("one", 1)
m.get("one") // 1
// Scala 集合與 Java 日期 API 混用
import java.time.LocalDate
val days = (1 to 7).map(LocalDate.now.plusDays(_)).toList
彌補了什麼缺陷#
Scala 彌補了「Java 沒有函數值與模式匹配、函數式語言不能重用 JVM 生態」的雙向缺陷。2008 年 Twitter 為了應對流量成長,將後端從 Ruby 遷移到 Scala,成為著名案例,帶動 Scala 在產業的採用。伏筆也埋下:語言過於複雜、implicit 氾濫、跨版本二進制不相容等問題累積多年,最終促成 2021 年的 Scala 3 大改版。