Scala 3 — 書籍

為何選擇 Scala 3?

語言

使用 Scala 有許多好處,尤其是 Scala 3。很難列出 Scala 的所有好處,但「十大」清單可能如下所示

  1. Scala 融合了函式程式設計 (FP) 和物件導向程式設計 (OOP)
  2. Scala 是靜態型別,但通常感覺像動態型別語言
  3. Scala 的語法簡潔,但仍然可讀;它通常被稱為表達性
  4. Scala 2 中的隱含是一個定義特徵,它們在 Scala 3 中已得到改進和簡化
  5. Scala 與 Java 無縫整合,因此您可以建立包含 Scala 和 Java 混合程式碼的專案,且 Scala 程式碼可以輕鬆使用數千個現有的 Java 函式庫
  6. Scala 可用於伺服器,也可透過 Scala.js 用於瀏覽器
  7. Scala 標準函式庫有數十種預建的函式方法,可節省您的時間,並大幅減少撰寫自訂 for 迴圈和演算法的需求
  8. 「最佳實務」已內建到 Scala 中,它支持不可變性、匿名函式、高階函式、模式比對、預設無法延伸的類別,以及更多
  9. Scala 生態系統提供世界上最現代化的 FP 函式庫
  10. 強大的型別系統

1) FP/OOP 融合

Scala 比任何其他語言更支援 FP 和 OOP 範例的融合。正如 Martin Odersky 所述,Scala 的精髓在於在類型化設定中融合函式式和物件導向程式設計,其中

  • 函式用於邏輯,而
  • 物件用於模組化

模組化的最佳範例之一可能是標準函式庫中的類別。例如,List 被定義為一個類別,技術上來說,它是一個抽象類別,而新的實例會像這樣建立

val x = List(1, 2, 3)

然而,程式設計師眼中看似簡單的 List 實際上是由多個專門類型組合而成,其中包括名為 IterableSeqLinearSeq 的特質。這些類型也由其他小型模組化程式碼單元組成。

除了從一系列模組化特質建立 List 等類型之外,List API 還包含數十個其他方法,其中許多是高階函式

val xs = List(1, 2, 3, 4, 5)

xs.map(_ + 1)         // List(2, 3, 4, 5, 6)
xs.filter(_ < 3)      // List(1, 2)
xs.find(_ > 3)        // Some(4)
xs.takeWhile(_ < 3)   // List(1, 2)

在這些範例中,清單中的值無法修改。List 類別是不可變的,因此所有這些方法都會傳回新值,如每個註解中的資料所示。

2) 動態感

Scala 的類型推論通常讓語言感覺是動態類型的,即使它是靜態類型的。這在變數宣告中是正確的

val a = 1
val b = "Hello, world"
val c = List(1,2,3,4,5)
val stuff = ("fish", 42, 1_234.5)

在將匿名函式傳遞給高階函式時也是如此

list.filter(_ < 4)
list.map(_ * 2)
list.filter(_ < 4)
    .map(_ * 2)

以及在定義方法時

def add(a: Int, b: Int) = a + b

在 Scala 3 中,這比以往任何時候都更為真實,例如在使用聯合類型

// union type parameter
def help(id: Username | Password) =
  val user = id match
    case Username(name) => lookupName(name)
    case Password(hash) => lookupPassword(hash)
  // more code here ...

// union type value
val b: Password | Username = if (true) name else password

3) 簡潔的語法

Scala 是一種低儀式感、「簡潔但仍可讀」的語言。例如,變數宣告很簡潔

val a = 1
val b = "Hello, world"
val c = List(1,2,3)

建立特質、類別和列舉等類型很簡潔

trait Tail:
  def wagTail(): Unit
  def stopTail(): Unit

enum Topping:
  case Cheese, Pepperoni, Sausage, Mushrooms, Onions

class Dog extends Animal, Tail, Legs, RubberyNose

case class Person(
  firstName: String,
  lastName: String,
  age: Int
)

高階函式很簡潔

list.filter(_ < 4)
list.map(_ * 2)

所有這些表達式以及更多表達式都很簡潔,而且仍然非常容易閱讀:我們稱之為表達

4) 簡化的隱含式

Scala 2 中的隱含式是一個主要的區別設計功能。它們代表了抽象化 over context 的基本方式,並採用統一的範例,服務於各種使用案例,其中包括

  • 實作 類型類別
  • 建立內容
  • 相依性注入
  • 表達功能

從那時起,其他語言採用了類似的概念,這些概念都是核心概念 術語推論 的變體:給定一個類型,編譯器會綜合一個具有該類型的「正規」術語。

雖然隱含式是 Scala 2 中的定義功能,但它們的設計在 Scala 3 中已獲得極大的改善

  • 定義「給定」值的方法只有一個
  • 引入隱含式參數和引數的方法只有一個
  • 有一種單獨的方法來匯入給定值,而不會讓它們隱藏在大量的正常匯入中
  • 定義隱含式轉換的方法只有一個,會清楚標示為隱含式轉換,而且不需要特殊語法

這些變更的優點包括

  • 新的設計避免功能互動,讓語言更一致
  • 它讓隱含式更容易學習,更難濫用
  • 它大幅提升使用隱含式的 95% Scala 程式碼的清晰度
  • 它有潛力以有原則的方式啟用術語推論,而且這種方式也能被存取且友善

這些功能在其他章節中有詳細說明,因此請參閱 內容抽象簡介,以及 givenusing 子句 章節,以取得更多詳細資訊。

5) 無縫 Java 整合

Scala/Java 互動在許多方面都是無縫的。例如

  • 您可以在 Scala 專案中使用數以千計的 Java 函式庫
  • Scala String 本質上是 Java String,並新增額外的功能
  • Scala 會無縫使用 Java java.time._ 套件中的日期/時間類別

您也可以在 Scala 中使用 Java 集合類別,而為了提供更多功能,Scala 包含方法,讓您可以將它們轉換為 Scala 集合。

雖然幾乎每個互動都是無縫的,但 「與 Java 互動」章節 示範如何更好地一起使用一些功能,包括如何使用

  • Scala 中的 Java 集合
  • Scala 中的 Java Optional
  • Scala 中的 Java 介面
  • Java 中的 Scala 集合
  • Java 中的 Scala Option
  • Java 中的 Scala 特質
  • Java 程式碼中會擲回例外狀況的 Scala 方法
  • Java 中的 Scala 變數參數

請參閱該章節以取得這些功能的更多詳細資訊。

6) 伺服器端與用戶端

Scala 可以搭配強大的架構在伺服器端使用

  • Play Framework 讓您可以建置高度可擴充的伺服器端應用程式和微服務
  • Akka Actors 讓您可以使用 Actor 模型大幅簡化分散式和並行軟體應用程式

Scala 也能搭配 Scala.js 專案 在瀏覽器中使用,這是一個 JavaScript 的類型安全替代方案。Scala.js 生態系 有數十個函式庫 讓您可以在瀏覽器中使用 React、Angular、jQuery 和許多其他 JavaScript 和 Scala 函式庫。

除了這些工具之外,Scala Native 專案「是一個特別為 Scala 設計的最佳化即時編譯器和輕量級受控執行時期」。它讓您可以使用純粹的 Scala 程式碼建置「系統」樣式的二進位可執行應用程式,而且還能讓您使用較低層級的原語。

7) 標準函式庫方法

您幾乎不需要再撰寫自訂 for 迴圈,因為 Scala 標準函式庫中的數十個預先建置函數方法將會為您節省時間,並協助讓程式碼在不同的應用程式中更一致。

以下範例顯示一些內建集合方法,還有許多方法未在此列出。雖然這些範例都使用 List 類別,但相同的這些方法也可以搭配其他集合類別使用,例如 SeqVectorLazyListSetMapArrayArrayBuffer

以下是一些範例

List.range(1, 3)                          // List(1, 2)
List.range(start = 1, end = 6, step = 2)  // List(1, 3, 5)
List.fill(3)("foo")                       // List(foo, foo, foo)
List.tabulate(3)(n => n * n)              // List(0, 1, 4)
List.tabulate(4)(n => n * n)              // List(0, 1, 4, 9)

val a = List(10, 20, 30, 40, 10)          // List(10, 20, 30, 40, 10)
a.distinct                                // List(10, 20, 30, 40)
a.drop(2)                                 // List(30, 40, 10)
a.dropRight(2)                            // List(10, 20, 30)
a.dropWhile(_ < 25)                       // List(30, 40, 10)
a.filter(_ < 25)                          // List(10, 20, 10)
a.filter(_ > 100)                         // List()
a.find(_ > 20)                            // Some(30)
a.head                                    // 10
a.headOption                              // Some(10)
a.init                                    // List(10, 20, 30, 40)
a.intersect(List(19,20,21))               // List(20)
a.last                                    // 10
a.lastOption                              // Some(10)
a.map(_ * 2)                              // List(20, 40, 60, 80, 20)
a.slice(2, 4)                             // List(30, 40)
a.tail                                    // List(20, 30, 40, 10)
a.take(3)                                 // List(10, 20, 30)
a.takeRight(2)                            // List(40, 10)
a.takeWhile(_ < 30)                       // List(10, 20)
a.filter(_ < 30).map(_ * 10)              // List(100, 200, 100)

val fruits = List("apple", "pear")
fruits.map(_.toUpperCase)                 // List(APPLE, PEAR)
fruits.flatMap(_.toUpperCase)             // List(A, P, P, L, E, P, E, A, R)

val nums = List(10, 5, 8, 1, 7)
nums.sorted                               // List(1, 5, 7, 8, 10)
nums.sortWith(_ < _)                      // List(1, 5, 7, 8, 10)
nums.sortWith(_ > _)                      // List(10, 8, 7, 5, 1)

8) 內建最佳實務

Scala 慣用語法在許多方面鼓勵最佳實務。對於不變性,我們鼓勵您建立不變的 val 宣告

val a = 1                 // immutable variable

我們也鼓勵您使用不變集合類別,例如 ListMap

val b = List(1,2,3)       // List is immutable
val c = Map(1 -> "one")   // Map is immutable

案例類別主要用於 網域建模,其參數是不變的

case class Person(name: String)
val p = Person("Michael Scott")
p.name           // Michael Scott
p.name = "Joe"   // compiler error (reassignment to val name)

如前一節所示,Scala 集合類別支援高階函數,您可以將方法(未顯示)和匿名函數傳遞給它們

a.dropWhile(_ < 25)
a.filter(_ < 25)
a.takeWhile(_ < 30)
a.filter(_ < 30).map(_ * 10)
nums.sortWith(_ < _)
nums.sortWith(_ > _)

match 表達式讓你可以使用樣式比對,而且它們真的是會傳回值的表達式

val numAsString = i match {
  case 1 | 3 | 5 | 7 | 9 => "odd"
  case 2 | 4 | 6 | 8 | 10 => "even"
  case _ => "too big"
}
val numAsString = i match
  case 1 | 3 | 5 | 7 | 9 => "odd"
  case 2 | 4 | 6 | 8 | 10 => "even"
  case _ => "too big"

因為它們可以傳回值,所以通常會用作方法的主體

def isTruthy(a: Matchable) = a match {
  case 0 | "" => false
  case _ => true
}
def isTruthy(a: Matchable) = a match
  case 0 | "" => false
  case _ => true

9) 生態系統函式庫

CatsZio 這樣的 Scala 函式庫是 FP 社群中的領先函式庫。所有像高性能、類型安全、並行、非同步、資源安全、可測試、函式式、模組化、二進位相容、高效、效應/有效果等時髦用語都可以用在這些函式庫上。

我們可以列出數百個函式庫,但幸運的是它們都列在另一個位置:有關這些詳細資訊,請參閱 “Awesome Scala” 清單

10) 強類型系統

Scala 有強類型系統,而且在 Scala 3 中進一步改進。Scala 3 的目標很早就定義,與類型系統相關的目標包括

  • 簡化
  • 消除不一致
  • 安全性
  • 人體工學
  • 效能

簡化是透過數十項變更和取消的功能而來。例如,從 Scala 2 中的過載 implicit 關鍵字變更為 Scala 3 中的 givenusing 這些術語,讓語言更清楚,特別是對於初學開發人員來說。

消除不一致與 Scala 3 中數十項 取消的功能變更的功能新增的功能 有關。此類別中一些最重要的功能是

  • 交集類型
  • 聯集類型
  • 隱式函式類型
  • 相依函式類型
  • 特質參數
  • 泛型元組

安全性與多項新增和變更的功能有關

  • 多重相等
  • 限制隱式轉換
  • 空值安全性
  • 安全初始化

人體工學的良好範例是列舉和擴充方法,它們已以非常易讀的方式新增到 Scala 3

// enumeration
enum Color:
  case Red, Green, Blue

// extension methods
extension (c: Circle)
  def circumference: Double = c.radius * math.Pi * 2
  def diameter: Double = c.radius * 2
  def area: Double = math.Pi * c.radius * c.radius

效能與多個領域有關。其中之一是 不透明類型。在 Scala 2 中,曾嘗試建立解決方案來遵循領域驅動設計 (DDD) 的做法,為值提供更有意義的類型。這些嘗試包括

  • 類型別名
  • 值類別
  • 案例類別

不幸的是,所有這些方法都有弱點,如 不透明類型 SIP 中所述。相反,如該 SIP 中所述,不透明類型的目標是「這些包裝器類型的運算不應在執行階段產生任何額外開銷,同時仍提供編譯階段的類型安全使用。」

有關更多類型系統詳細資訊,請參閱 參考文件

其他優異功能

Scala 有許多優異功能,而選擇前 10 名清單可能會很主觀。多項調查顯示,不同群組的開發人員喜愛不同的功能。希望您在使用此語言時,能發現更多優異的 Scala 功能。

此頁面的貢獻者