在 GitHub 上編輯此頁面

使用子句

函式程式設計傾向於將大多數依賴關係表示為簡單的函式參數化。這很簡潔且強大,但有時會導致函式需要許多參數,其中相同的值會在許多函式的長呼叫鏈中重複傳遞。情境參數在此處有所幫助,因為它們使編譯器能夠合成重複引數,而不是讓程式設計師必須明確撰寫它們。

例如,使用先前定義的 給定實例,可以定義一個針對存在排序的任何參數運作的 max 函數,如下所示

def max[T](x: T, y: T)(using ord: Ord[T]): T =
  if ord.compare(x, y) < 0 then y else x

在此,ord 是使用 using 子句引入的內容參數max 函數可以套用如下

max(2, 3)(using intOrd)

(using intOrd) 部分傳遞 intOrd 作為 ord 參數的參數。但內容參數的重點在於這個參數也可以省略(而且通常會省略)。因此,下列應用程式同樣有效

max(2, 3)
max(List(1, 2, 3), Nil)

匿名內容參數

在許多情況下,內容參數的名稱根本不需要明確提到,因為它只用於其他內容參數的合成參數中。在這種情況下,可以避免定義參數名稱,而只提供其類型。範例

def maximum[T](xs: List[T])(using Ord[T]): T =
  xs.reduceLeft(max)

maximum 僅採用類型為 Ord[T] 的內容參數,然後將其作為推論參數傳遞給 max。參數的名稱被省略了。

一般來說,內容參數可以定義為完整的參數清單 (p_1: T_1, ..., p_n: T_n) 或僅作為類型序列 T_1, ..., T_nusing 子句中不支援可變參數參數。

類別內容參數

若要讓類別內容參數在類別主體外可見,可以透過新增 valvar 修飾詞將其設為成員。

class GivenIntBox(using val usingParameter: Int):
  def myInt = summon[Int]

val b = GivenIntBox(using 23)
import b.usingParameter
summon[Int]  // 23

這比建立明確的 given 成員更可取,因為後者會在類別主體內造成歧義

class GivenIntBox2(using usingParameter: Int):
  given givenMember: Int = usingParameter
  def n = summon[Int]  // ambiguous given instances: both usingParameter and givenMember match type Int

GivenIntBox 外部,usingParameter 看起來就像在類別中定義為 given usingParameter: Int,特別是它必須按照 匯入 given 一節中所述匯入。

val b = GivenIntBox(using 23)
// Works:
import b.given
summon[Int]  // 23
usingParameter  // 23

// Fails:
import b.*
summon[Int]      // No given instance found
usingParameter   // Not found

推論複雜參數

以下是另外兩個具有類型為 Ord[T] 的內容參數的方法

def descending[T](using asc: Ord[T]): Ord[T] = new Ord[T]:
  def compare(x: T, y: T) = asc.compare(y, x)

def minimum[T](xs: List[T])(using Ord[T]) =
  maximum(xs)(using descending)

minimum 方法的右側將 descending 作為明確參數傳遞給 maximum(xs)。有了這個設定,下列呼叫都是良好的,而且它們都正規化為最後一個

minimum(xs)
maximum(xs)(using descending)
maximum(xs)(using descending(using intOrd))

多個 using 子句

定義中可以有多個 using 子句,而且 using 子句可以與一般參數子句自由混合。範例

def f(u: Universe)(using ctx: u.Context)(using s: ctx.Symbol, k: ctx.Kind) = ...

多個 using 子句在應用程式中從左到右比對。範例

object global extends Universe { type Context = ... }
given ctx : global.Context with { type Symbol = ...; type Kind = ... }
given sym : ctx.Symbol
given kind: ctx.Kind

然後,下列呼叫都是有效的(而且正規化為最後一個)

f(global)
f(global)(using ctx)
f(global)(using ctx)(using sym, kind)

f(global)(using sym, kind) 會產生型別錯誤。

召喚實例

Predef 中的 summon 方法會傳回特定型別的給定值。例如,Ord[List[Int]] 的給定值實例是由

summon[Ord[List[Int]]]  // reduces to listOrd(using intOrd)

summon 方法僅定義為內容參數上的(非擴充)恆等函數。

def summon[T](using x: T): x.type = x

語法

以下是參數和引數的新語法,視為從 Scala 3 的標準無上下文語法 的增量。using 是軟關鍵字,僅在參數或引數清單的開頭識別。它可以在其他任何地方用作一般識別碼。

ClsParamClause      ::=  ... | UsingClsParamClause
DefParamClause      ::=  ... | UsingParamClause
UsingClsParamClause ::=  ‘(’ ‘using’ (ClsParams | Types) ‘)’
UsingParamClause    ::=  ‘(’ ‘using’ (DefTermParams | Types) ‘)’
ParArgumentExprs    ::=  ... | ‘(’ ‘using’ ExprsInParens ‘)’