Scala 3 — 書籍

依賴函數類型

語言
此文件頁面專屬於 Scala 3,且可能涵蓋 Scala 2 中沒有的新概念。除非另有說明,此頁面中的所有程式碼範例都假設您使用 Scala 3。

依賴函數類型描述函數類型,其中結果類型可能取決於函數的參數值。依賴類型和依賴函數類型的概念較為進階,通常只有在設計自己的函式庫或使用進階函式庫時才會遇到。

依賴方法類型

讓我們考慮以下異質資料庫範例,它可以儲存不同型別的值。金鑰包含有關對應值型別的資訊

trait Key { type Value }

trait DB {
  def get(k: Key): Option[k.Value] // a dependent method
}

給定一個金鑰,方法 get 讓我們存取映射,並有可能傳回型別為 k.Value 的儲存值。我們可以將這個路徑依賴型別讀為:「根據參數 k 的具體型別,我們傳回一個匹配值」。

例如,我們可以有以下金鑰

object Name extends Key { type Value = String }
object Age extends Key { type Value = Int }

現在,對方法 get 的以下呼叫會進行型別檢查

val db: DB = ...
val res1: Option[String] = db.get(Name)
val res2: Option[Int] = db.get(Age)

呼叫方法 db.get(Name) 會傳回型別為 Option[String] 的值,而呼叫 db.get(Age) 會傳回型別為 Option[Int] 的值。傳回型別取決於傳遞給 get 的參數的具體型別,因此稱為依賴型別

依賴函數類型

如上所見,Scala 2 已經支援依賴方法型別。但是,建立型別為 DB 的值相當麻煩

// a user of a DB
def user(db: DB): Unit =
  db.get(Name) ... db.get(Age)

// creating an instance of the DB and passing it to `user`
user(new DB {
  def get(k: Key): Option[k.Value] = ... // implementation of DB
})

我們需要手動建立 DB 的匿名內部類別,實作 get 方法。對於依賴建立許多不同 DB 執行個體的程式碼,這非常繁瑣。

特質 DB 僅有一個抽象方法 get。如果我們可以使用 lambda 語法,那不是很好嗎?

user { k =>
  ... // implementation of DB
}

事實上,這現在在 Scala 3 中是可能的!我們可以將 DB 定義為依賴函數型別

type DB = (k: Key) => Option[k.Value]
//        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
//      A dependent function type

給定這個 DB 定義,對 user 的上述呼叫會進行型別檢查,就像這樣。

您可以在 參考文件 中閱讀更多有關相依函數類型的內部結構。

案例研究:數值表達式

假設我們想要定義一個模組,用於抽象數字的內部表示。這可能很有用,例如,實作自動導數的函式庫。

我們從定義數字模組開始

trait Nums:
  // the type of numbers is left abstract
  type Num

  // some operations on numbers
  def lit(d: Double): Num
  def add(l: Num, r: Num): Num
  def mul(l: Num, r: Num): Num

我們省略 Nums 的具體實作,但作為練習,您可以透過指定 type Num = Double 並相應地實作方法來實作 Nums

使用我們數字抽象的程式現在具有下列類型

type Prog = (n: Nums) => n.Num => n.Num

val ex: Prog = nums => x => nums.add(nums.lit(0.8), x)

計算像 ex 這樣的程式的導數的函數類型為

def derivative(input: Prog): Double

有了相依函數類型的便利性,使用不同的程式呼叫此函數非常方便

derivative { nums => x => x }
derivative { nums => x => nums.add(nums.lit(0.8), x) }
// ...

回想一下,上述編碼中的相同程式會是

derivative(new Prog {
  def apply(nums: Nums)(x: nums.Num): nums.Num = x
})
derivative(new Prog {
  def apply(nums: Nums)(x: nums.Num): nums.Num = nums.add(nums.lit(0.8), x)
})
// ...

與內容函數結合

擴充方法、內容函數 和相依函數的結合為函式庫設計師提供了強大的工具。例如,我們可以如下精簡上述函式庫

trait NumsDSL extends Nums:
  extension (x: Num)
    def +(y: Num) = add(x, y)
    def *(y: Num) = mul(x, y)

def const(d: Double)(using n: Nums): n.Num = n.lit(d)

type Prog = (n: NumsDSL) ?=> n.Num => n.Num
//                       ^^^
//     prog is now a context function that implicitly
//     assumes a NumsDSL in the calling context

def derivative(input: Prog): Double = ...

// notice how we do not need to mention Nums in the examples below?
derivative { x => const(1.0) + x }
derivative { x => x * x + const(2.0) }
// ...

此頁面的貢獻者