程式結構類型 - 更多詳細資訊
語法
SimpleType ::= ... | Refinement
Refinement ::= ‘{’ RefineStatSeq ‘}’
RefineStatSeq ::= RefineStat {semi RefineStat}
RefineStat ::= ‘val’ VarDcl | ‘def’ DefDcl | ‘type’ {nl} TypeDcl
結構型別的實作
標準函式庫定義一個通用標記特徵 scala.Selectable
trait Selectable extends Any
仰賴 Java 反射 的 Selectable
實作可在標準函式庫中取得:scala.reflect.Selectable
。對於無法使用 Java 反射的平台,可以構想其他實作。
Selectable
的實作必須提供 selectDynamic
和 applyDynamic
方法,或兩者都提供。這些方法可以是 Selectable
實作的成員,也可以是擴充方法。
selectDynamic
方法會取得欄位名稱,並傳回與該名稱相關聯的 Selectable
值。其簽章應採用以下形式
def selectDynamic(name: String): T
通常,回傳型別 T
為 Any
。
與 scala.Dynamic
不同,updateDynamic
方法沒有特殊意義。不過,我們保留在未來賦予其意義的權利。因此,建議不要在 Selectable
中定義任何稱為 updateDynamic
的成員。
applyDynamic
方法用於套用至引數的選取。它會取得方法名稱,以及可能表示其參數型別的 Class
,以及要傳遞給函數的引數。其簽章應採用以下兩個形式之一
def applyDynamic(name: String)(args: Any*): T
def applyDynamic(name: String, ctags: Class[?]*)(args: Any*): T
兩個版本都將實際參數傳遞給 args
參數。第二個版本除了會接收 java.lang.Class
es 的 vararg 參數外,還會識別方法的參數類別。如果 applyDynamic
是使用 Java 反射實作的,則需要此參數,但它在其他情況下也可能很有用。selectDynamic
和 applyDynamic
也可以在使用子句中採用其他內容參數。這些參數會在呼叫位置以正常方式解析。
給定類型為 C { Rs }
的值 v
,其中 C
是類別參考,而 Rs
是結構性精簡宣告,且給定類型為 U
的 v.a
,我們會考慮三種不同的情況
-
如果
U
是值類型,我們會將v.a
對應到v.selectDynamic("a").asInstanceOf[U]
-
如果
U
是方法類型(T11, ..., T1n)...(TN1, ..., TNn): R
且它不是相依方法類型,我們會將v.a(a11, ..., a1n)...(aN1, ..., aNn)
對應到v.applyDynamic("a")(a11, ..., a1n, ..., aN1, ..., aNn) .asInstanceOf[R]
如果此呼叫解析為採用
Class[?]*
參數的第二個形式的applyDynamic
方法,我們會進一步將此呼叫改寫為v.applyDynamic("a", c11, ..., c1n, ..., cN1, ... cNn)( a11, ..., a1n, ..., aN1, ..., aNn) .asInstanceOf[R]
其中每個
c_ij
都是形式參數Tij
類型的文字java.lang.Class[?]
,亦即classOf[Tij]
。 -
如果
U
既不是值類型,也不是方法類型或相依方法類型,則會發出錯誤。
請注意,v
的靜態類型不一定必須符合 Selectable
,也不需要將 selectDynamic
和 applyDynamic
作為成員。只要有一個隱式轉換可以將 v
轉換為 Selectable
就足夠了,而且選擇方法也可以作為 擴充方法 使用。
結構類型限制
-
無法透過結構呼叫來呼叫相依方法。
-
精緻化可能不會引入重載:如果精緻化指定方法
m
的簽章,而且m
也在精緻化的父類型中定義,則新簽章必須正確覆寫現有簽章。 -
結構精緻化的子類型化必須保留已清除的參數類型:假設我們要證明
S <: T { def m(x: A): B }
。然後,S
必須有一個成員方法m
,可以接受類型為A
的引數。此外,如果m
不是T
的成員(即精緻化是結構性的),則會套用額外條件。在這種情況下,S
的成員定義m
將有一個類型為A'
的參數。額外條件是A'
和A
的清除相同。以下是範例class Sink[A] { def put(x: A): Unit = {} } val a = Sink[String]() val b: { def put(x: String): Unit } = a // error b.put("abc") // looks for a method with a `String` parameter
倒數第二行沒有類型正確,因為類別
Sink
中put
的參數類型的清除為Object
,但是b
類型的put
參數的清除為String
。這個額外條件是必要的,因為我們必須使用某種(尚未知道的)反射形式來呼叫b
類型中put
等結構成員。此條件可確保精緻化的靜態已知參數類型對應到執行時期所選呼叫目標的參數類型(清除後)。大多數反射調度演算法需要知道確切的清除參數類型。例如,如果上面的範例會進行類型檢查,則最後一行中的呼叫
b.put("abc")
會在b
的執行時期類型中尋找一個put
方法,該方法會接受一個String
參數。但是put
方法是來自類別Sink
的方法,該方法會接受一個Object
參數。因此,呼叫會在執行時期失敗,並出現NoSuchMethodException
。我們可能會希望有一個「更智慧」的反射式調度演算法,它不需要精確的參數類型比對。不幸的是,只要有重載的可能性,這總是會遇到歧義。例如,繼續上面的範例,我們可能會引入一個新的
Sink
子類別Sink1
,並變更a
的定義如下class Sink1[A] extends Sink[A] { def put(x: "123") = ??? } val a: Sink[String] = Sink1[String]()
現在在
b
的執行時期類型中有兩個put
方法,分別以擦除的參數類型Object
和String
。然而,動態調度仍然需要轉到第一個put
方法,即使第二個看起來更匹配。對於我們實際上可以在不知道精確參數類型的情況下實作反射的案例(例如,如果靜態重載被動態調度的多重方法取代),有一個逃生艙口。對於延伸
scala.Selectable.WithoutPreciseParameterTypes
的類型,會省略簽章檢查。範例trait MultiMethodSelectable extends Selectable.WithoutPreciseParameterTypes: // Assume this version of `applyDynamic` can be implemented without knowing // precise parameter types `paramTypes`: def applyDynamic(name: String, paramTypes: Class[_]*)(args: Any*): Any = ??? class Sink[A] extends MultiMethodSelectable: def put(x: A): Unit = {} val a = new Sink[String] val b: MultiMethodSelectable { def put(x: String): Unit } = a // OK
與 Scala 2 結構化類型的差異
- Scala 2 透過 Java 反射支援結構化類型。與 Scala 3 不同,結構化呼叫不依賴於
Selectable
等機制,而且無法避免反射。 - 在 Scala 2 中,精煉可以引入重載。
- 在 Scala 2 中,精煉允許變動的
var
。在 Scala 3 中,不再允許。 - Scala 2 沒有對結構化類型的子類型化施加「相同擦除」限制。它允許一些呼叫在執行時期失敗。
背景
如需更多資訊,請參閱 重新思考結構化類型。