使用子句
函式程式設計傾向於將大多數依賴關係表示為簡單的函式參數化。這很簡潔且強大,但有時會導致函式需要許多參數,其中相同的值會在許多函式的長呼叫鏈中重複傳遞。情境參數在此處有所幫助,因為它們使編譯器能夠合成重複引數,而不是讓程式設計師必須明確撰寫它們。
例如,使用先前定義的 給定實例,可以定義一個針對存在排序的任何參數運作的 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_n
。using
子句中不支援可變參數參數。
類別內容參數
若要讓類別內容參數在類別主體外可見,可以透過新增 val
或 var
修飾詞將其設為成員。
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 ‘)’