在 GitHub 上編輯此頁面

內容函數

脈絡函式 是(僅有)脈絡參數的函式。其類型為脈絡函式類型。以下是脈絡函式類型的範例

import scala.concurrent.ExecutionContext

type Executable[T] = ExecutionContext ?=> T

使用 ?=> 作為「箭頭」符號來撰寫內容函數。它們套用於綜合論證,就像套用帶有內容參數的方法一樣。例如

given ec: ExecutionContext = ...

def f(x: Int): ExecutionContext ?=> Int = ...

// could be written as follows with the type alias from above
// def f(x: Int): Executable[Int] = ...

f(2)(using ec)   // explicit argument
f(2)             // argument is inferred

相反地,如果表達式 E 的預期類型是內容函數類型 (T_1, ..., T_n) ?=> U,而且 E 尚未是內容函數文字,則會透過將 E 改寫為以下內容,將其轉換為內容函數文字

(x_1: T1, ..., x_n: Tn) ?=> E

其中名稱 x_1、...、x_n 是任意的。此擴充會在表達式 E 進行類型檢查之前執行,這表示 x_1、...、x_n 可在 E 中作為給定值使用。

就像其類型一樣,內容函數文字使用 ?=> 作為參數和結果之間的箭頭來撰寫。它們與一般函數文字不同之處在於其類型是內容函數類型。

例如,繼續使用前一個定義,

def g(arg: Executable[Int]) = ...

g(22)      // is expanded to g((ev: ExecutionContext) ?=> 22)

g(f(2))    // is expanded to g((ev: ExecutionContext) ?=> f(2)(using ev))

g((ctx: ExecutionContext) ?=> f(3))  // is expanded to g((ctx: ExecutionContext) ?=> f(3)(using ctx))
g((ctx: ExecutionContext) ?=> f(3)(using ctx)) // is left as it is

範例:建造器模式

內容函數類型具有相當大的表達能力。例如,以下是如何使用它們來支援「建造器模式」,目標是建構像這樣的表格

table {
  row {
    cell("top left")
    cell("top right")
  }
  row {
    cell("bottom left")
    cell("bottom right")
  }
}

構想是為 TableRow 定義類別,允許透過 add 新增元素

import scala.collection.mutable.ArrayBuffer

class Table:
  val rows = new ArrayBuffer[Row]
  def add(r: Row): Unit = rows += r
  override def toString = rows.mkString("Table(", ", ", ")")

class Row:
  val cells = new ArrayBuffer[Cell]
  def add(c: Cell): Unit = cells += c
  override def toString = cells.mkString("Row(", ", ", ")")

case class Cell(elem: String)

然後,可以使用內容函數類型作為參數來定義 tablerowcell 建構函數方法,以避免否則必要的管道樣板。

def table(init: Table ?=> Unit) =
  given t: Table = Table()
  init
  t

def row(init: Row ?=> Unit)(using t: Table) =
  given r: Row = Row()
  init
  t.add(r)

def cell(str: String)(using r: Row) =
  r.add(new Cell(str))

有了這樣的設定,上述表格建構程式碼會編譯並擴充為

table { ($t: Table) ?=>

  row { ($r: Row) ?=>
    cell("top left")(using $r)
    cell("top right")(using $r)
  }(using $t)

  row { ($r: Row) ?=>
    cell("bottom left")(using $r)
    cell("bottom right")(using $r)
  }(using $t)
}

範例:後置條件

作為一個較大的範例,以下是使用擴充方法 ensuring 定義建構,用於檢查任意後置條件的方法,以便可以簡單地透過 result 參照已檢查的結果。此範例結合不透明類型別名、內容函數類型和擴充方法,以提供零開銷抽象。

object PostConditions:
  opaque type WrappedResult[T] = T

  def result[T](using r: WrappedResult[T]): T = r

  extension [T](x: T)
    def ensuring(condition: WrappedResult[T] ?=> Boolean): T =
      assert(condition(using x))
      x
end PostConditions
import PostConditions.{ensuring, result}

val s = List(1, 2, 3).sum.ensuring(result == 6)

說明

我們使用內容函數類型 WrappedResult[T] ?=> Boolean 作為 ensuring 條件的類型。因此,傳遞給 ensuring 的引數(例如 (result == 6))將在作用域中有一個類型為 WrappedResult[T] 的給定值,以傳遞給 result 方法。

WrappedResult 是新的類型,用於確保我們不會在作用域中取得不需要的給定值(在所有涉及內容參數的情況中,這都是良好的做法)。

由於 WrappedResult 是一個不透明的類型別名,因此其值不需要裝箱,而由於 ensuring 已新增為延伸方法,因此其引數也不需要裝箱。因此,ensuring 的實作在效率上接近手動編寫的最佳可能程式碼

val s =
  val result = List(1, 2, 3).sum
  assert(result == 6)
  result

參考

如需更多資訊,請參閱 部落格文章(使用已取代的不同語法)。

更多詳細資料