Type Class Derivation
, 定義一個類 A,該類有一個特殊的方法。而通過派生類我們可以將定義的類 派生自A,這樣新定義的類也擁有了A的方法。
官方說明: Type Class Derivation 是一種為滿足一些簡單條件的類型類自動生成給定實例的方法。
從這個意義上說,類型類(Type Class) 是具有Type參數的任意的trait或者class類型,該類型參數確定被操作的類型。
例子,給定如下的 ADT tree:
enum Tree[T] derives Eq, Ordering, Show:
case Branch(left: Tree[T], right: Tree[T])
case Leaf(elem: T)
derives
語法 為當前Tree的伴生對象中的 Eq、Ordering 和 Show 類型類生成以下 given instance.
given [T: Eq] : Eq[Tree[T]] = Eq.derived
given [T: Ordering] : Ordering[Tree] = Ordering.derived
given [T: Show] : Show[Tree] = Show.derived
我們說 Tree 是派生類型(deriving type),而 Eq、Ordering 和 Show 實例是派生實例。
Types supporting derives clauses
所有的數據類型都支持擁有 derives 子句。本文檔主要關注數據類型,這些數據類型也具有可用的 Mirror type class 的 given instance。
Mirror 類型類在編譯時給出。
Mirror type class 實例在類型級別(type level)提供有關類型(type)的組件(component)和標簽(labelling)的信息。它們還提供最小術語級別的基礎設施,以允許更高級別的庫提供全面的派生支持(comprehensive derivation support)。
sealed trait Mirror:
/** the type being mirrored */
/** 被 mirrored 的原始類型 */
type MirroredType
/** the type of the elements of the mirrored type */
/** mirrored type 的 element 的類型 */
type MirroredElemTypes
/** The mirrored *-type */
type MirroredMonoType
/** The name of the type */
type MirroredLabel <: String
/** The names of the elements of the type */
/** mirrored type 的 element 的 name */
type MirroredElemLabels <: Tuple
object Mirror:
/** The Mirror for a product type */
trait Product extends Mirror:
/** Create a new instance of type `T` with elements * taken from product `p`.
*/
def fromProduct(p: scala.Product): MirroredMonoType
trait Sum extends Mirror:
/** The ordinal number of the case class of `x`. * For enums, `ordinal(x) == x.ordinal`
*/
def ordinal(x: MirroredMonoType): Int
end Mirror
Product類型( case classes and objects, and enum cases ) 的 Mirror 類型是 Mirror 的子類: Mirror.Product 。
Sum類型 (sealed class or traits with product children, and enums) 的 Mirror 類型是 Mirror 的子類: Mirror.Sum。
針對上述 Tree ADT 由編譯器生成的 Mirror 如下:
enum Tree[T] derives Eq, Ordering, Show:
case Branch(left: Tree[T], right: Tree[T])
case Leaf(elem: T)
// Mirror for Tree
new Mirror.Sum:
//被Mirror的原始類型是:Tree
type MirroredType = Tree
//被Mirror的類型的元素的類型:
type MirroredElemTypes[T] = (Branch[T], Leaf[T])
//The mirrored *-type
type MirroredMonoType = Tree[_]
//
type MirroredLabel = "Tree"
//
type MirroredElemLabels = ("Branch", "Leaf")
//
def ordinal(x: MirroredMonoType): Int = x match
case _: Branch[_] => 0
case _: Leaf[_] => 1
// Mirror for Branch
new Mirror.Product:
type MirroredType = Branch
type MirroredElemTypes[T] = (Tree[T], Tree[T])
type MirroredMonoType = Branch[_]
type MirroredLabel = "Branch"
type MirroredElemLabels = ("left", "right")
def fromProduct(p: Product): MirroredMonoType =
new Branch(...)
// Mirror for Leaf
new Mirror.Product:
type MirroredType = Leaf
type MirroredElemTypes[T] = Tuple1[T]
type MirroredMonoType = Leaf[_]
type MirroredLabel = "Leaf"
type MirroredElemLabels = Tuple1["elem"]
def fromProduct(p: Product): MirroredMonoType =
new Leaf(...)
- Tree 對象是 enums對象,因此它的Mirror子類是 Mirror.Sum。
- Branch 和 Leaf 是 enum cases, 因此它的Mirror子類是 Mirror.Product。
Mirror 的部分特性說明
Mirror 使用的是 types 而不是 terms 進行編碼。這意味著它是編譯階段的,如果沒有使用它,則不會占用運行時內存。
Mirror 會結合 Scala3 的 metaprogramming 特性一起使用。MirroredType 和 MirroredElemTypes 類型 與 Mirror 作為實例的 data type 的類型相匹配。
不管是 Product 還是 Sum 類型,在 MirroredElemTypes 中的元素的順序與定義的順序是一致的。例如 Tree Mirror 中的 MirroredElemTypes,Branch 必須
優先于 Leaf,需要依照定義時的順序。ordinal
和fromProduct
方法是根據MirroredMonoType
定義的,它是 kind-* 的類型,通過通配符類型參數從 MirroredType 獲得。
Type classes supporting automatic deriving
支持自動派生的類型類
如果Scala trait 或者 class的伴生對象 定義了一個名為 derived
的方法,則該 trait 或 class 可以出現在派生子句(derives clause)中。類型類 TC[_]
的 派生方法(derived) 的簽名和實現是任意的,但它通常具有以下形式:
import scala.deriving.Mirror
def derived[T](using Mirror.Of[T]): TC[T] = ...
derived 方法持有了一個 Mirror 的上下文,以此來表示派生類型 T 的類型信息(Shape)。然后根據此 shape 來實現派生的方法。
因此,如果想對某個類進行派生,上述信息是必須的。derived 可以以間接的方式擁有 Mirror 信息(比如通過scala3的 Macro的傳遞,或者運行時起的反射)。我們預計(直接或間接)基于 Mirror 的實現將是最常見的,這也是本文檔所強調的(emphasises)。
我們先嘗試使用比較低階的方式來實現 derived 派生方法(通常在開發和生產中我們不會這么去做,但是去熟悉 derived 還是有必要的),然后在下一節使用 Macro 來實現派生方法。
使用 Low Level 實現類型派生方法
定義一個 trait Equal,它有一個方法 equal 可以比較給定的兩個相同類型的對象是否相等。
trait Equal[T]:
/**
* 比較 x 和 y 是否相等
*/
def equal(x: T, y: T): Boolean
定義一個 Opt enum 派生自 Equal, 這樣 Opt 就擁有了 Equal 的 equal 方法的能力。
enum Opt[+T] derives Equal :
case Some(t: T)
case None
核心代碼:
trait Equal[T]:
/**
* 比較 x 和 y 是否相等
*/
def equal(x: T, y: T): Boolean
//在派生類中提供 Equal 的 Mirror 信息
//通過實現 derived 方法,并生產一個 inline given instance
object Equal:
given Equal[Int] with
def equal(x: Int, y: Int) = x == y
var count = 0
// derived is defined as an inline given,意味著該方法會在調用時(call sites)進行 擴展 expanded
inline given derived[T](using m: Mirror.Of[T]): Equal[T] =
println(s"derived,T: ${Log.describe[T]},MirroredElemTypes: ${Log.describe[m.MirroredElemTypes]}")
//獲取 Mirror 的 Type 的元素類型.
count = 0
val elemInstances = summonAll[m.MirroredElemTypes]
println(s"elemInstances: $elemInstances")
//cause derived is inline, the match will be resolved at compile-time and only the left-hand side of the matching case will be inlined into the generated code with types refined as revealed by the match
inline m match
case s: Mirror.Sum ?
eqSum(s, elemInstances)
case p: Mirror.Product ?
eqProduct(p, elemInstances)
def eqSum[T](s: Mirror.SumOf[T], elems: List[Equal[_]]): Equal[T] =
new Equal[T] :
override def equal(x: T, y: T): Boolean =
val ordx: Int = s.ordinal(x)
val elem: Equal[_] = elems(ordx)
(ordx == s.ordinal(y)) && check(elem)(x, y)
def eqProduct[T](s: Mirror.ProductOf[T], elems: List[Equal[_]]): Equal[T] =
new Equal[T] :
override def equal(x: T, y: T): Boolean = {
iterator(x).zip(iterator(y)).zip(elems.iterator).forall {
case ((x, y), elem) => check(elem)(x, y)
}
}
def check(elem: Equal[_])(x: Any, y: Any): Boolean =
val res = elem.asInstanceOf[Equal[Any]].equal(x, y)
println(s"res: $res")
res
def iterator[T](p: T) = p.asInstanceOf[Product].productIterator
//https://stackoverflow.com/questions/65747525/printing-mirroredelemtypes-in-scala-3
//check T
//1.(Sm[T] *: (Opt.Nn,EmptyTuple))
//2.
inline def summonAll[T <: Tuple]: List[Equal[_]] =
count += 1
println(s"count: ${count}, log: " + Log.describe[T])
inline erasedValue[T] match
case _: EmptyTuple => Nil
case x: (t *: ts) =>
val si = summonInline[Equal[t]]
println(s"count: ${count}, t: " + Log.describe[t] + s",si: $si")
si :: summonAll[ts]
end Equal
執行分析
@main def test1(): Unit =
import Opt.*
//1
val eqoi: Equal[Opt[Int]] = summon[Equal[Opt[Int]]]
//2
val isEqual = eqoi.equal(Some(23), Some(23))
println(s"isEqual: $isEqual")
第一步:
val eqoi: Equal[Opt[Int]] = summon[Equal[Opt[Int]]]
通過 summon 去尋找 Equal[Opt[Int]] 的具體實現,實際編譯成代碼之后,調用的是 Opt 的派生方法 derived,如下:
val eqoi: Equal[Opt[Int]] = Opt.derived$Equal[scala.Int](Equal.given_Equal_Int)
然后 Opt.derived$Equal 實際調用如下(以下代碼是編譯器生成的方法):
def derived$Equal[T](implicit `x$0?`: Equal[T]): Equal[Opt[T]] =
Equal.derived[Opt[T]](
Opt.$asInstanceOf$[scala.deriving.Mirror {
type MirroredType >: Opt[T] <: Opt[T]
type MirroredMonoType >: Opt[T] <: Opt[T]
type MirroredElemTypes >: scala.Nothing <: scala.Tuple
} & scala.deriving.Mirror.Sum {
type MirroredMonoType >: Opt[T] <: Opt[T]
type MirroredType >: Opt[T] <: Opt[T]
type MirroredLabel >: "Opt" <: "Opt"
} {
type MirroredElemTypes >: scala.*:[Opt.Some[T], scala.*:[Opt.None, scala.Tuple$package.EmptyTuple]] <: scala.*:[Opt.Some[T], scala.*:[Opt.None, scala.Tuple$package.EmptyTuple]]
type MirroredElemLabels >: scala.*:["Some", scala.*:["None", scala.Tuple$package.EmptyTuple]] <: scala.*:["Some", scala.*:["None", scala.Tuple$package.EmptyTuple]]
}]
)
方法中去調用了我們在 Equal 伴生類中實現的 derived 方法,傳遞的 type 為 Opt[T], Mirror 信息如上代碼所示,該 Mirror 信息包含了 Opt 及其子元素的各類 Type 信息,方便后續 Equal 進行派生實現類的查找。
接下來看 Equal.derived
方法:
inline given derived[T](using m: Mirror.Of[T]): Equal[T] =
//獲取 Mirror 的 Type 的元素類型.
val elemInstances = summonAll[m.MirroredElemTypes]
println(s"elemInstances: $elemInstances")
//cause derived is inline, the match will be resolved at compile-time and only the left-hand side of the matching case will be inlined into the generated code with types refined as revealed by the match
inline m match
case s: Mirror.Sum ?
println(s"Mirror.Sum: $elemInstances")
eqSum(s, elemInstances)
case p: Mirror.Product ?
println(s"Mirror.Product: $elemInstances")
eqProduct(p, elemInstances)
- 第一步根據給定的 Mirror,通過 summonAll 去獲取它的子元素信息
m.MirroredElemType
. - 第二步:判斷 m 具體是 Mirror 的什么子類,在根據對應的子類信息,去生成不同的 Equal 實現類。
- 返回結果是 Equal[T] 的具體實現,即最開始 main 方法中通過
summon[Equal[Opt[Int]]]
最終獲取到該 Opt[Int] 的最終 Equal[Opt[Int] 的實現類,然后就可以通過這個實現類調用 equal 方法去判斷 Opt[Int] 的兩個對象是否相等了。
summonAll[m.MirroredElemTypes]
方法
inline def summonAll[T <: Tuple]: List[Equal[_]] =
println(s"count: ${count}, log: " + Log.describe[T])
inline erasedValue[T] match
case _: EmptyTuple => Nil
case x: (t *: ts) =>
println(s"count: ${count}, t: " + Log.describe[t])
summonInline[Equal[t]] :: summonAll[ts]
該方法通過提取 m.MirroredElemTypes
子元素信息,最終給這些子元素尋找對應的 Equal[_] 的實現類,以方便在最終對 Opt[T] 對象進行 equal 時,可以遞歸的去對其子元素進行 equal。
以 Opt[T] 為例,上文生成的代碼我們可以看到, Opt[T] 傳入的 Mirror 類型如下:
scala.deriving.Mirror {
type MirroredType >: Opt[T] <: Opt[T]
type MirroredMonoType >: Opt[T] <: Opt[T]
type MirroredElemTypes >: scala.Nothing <: scala.Tuple
} & scala.deriving.Mirror.Sum {
type MirroredMonoType >: Opt[T] <: Opt[T]
type MirroredType >: Opt[T] <: Opt[T]
type MirroredLabel >: "Opt" <: "Opt"
} {
type MirroredElemTypes >: scala.*:[Opt.Some[T], scala.*:[Opt.None, scala.Tuple$package.EmptyTuple]] <: scala.*:[Opt.Some[T], scala.*:[Opt.None, scala.Tuple$package.EmptyTuple]]
type MirroredElemLabels >: scala.*:["Some", scala.*:["None", scala.Tuple$package.EmptyTuple]] <: scala.*:["Some", scala.*:["None", scala.Tuple$package.EmptyTuple]]
}
- 該 Mirror 的 MirroredElemTypes 由一個元祖表示,大概可以寫成如下形式:
(Opt.Some[T] *: (Opt.None *: EmptyTuple])
即主要包含,Some[T],None 兩個元素,加上最后一個空的 EmptyTuple。
因此我們看 summonAll 的執行流程,第一步,會進入到下面這個步驟:
case x: (t *: ts) =>
println(s"count: ${count}, t: " + Log.describe[t])
summonInline[Equal[t]] :: summonAll[ts]
其中,t 即為 Opt.Some[T], 因此通過 summonInline[Equal[Opt.Some[T]]] 尋找其 Equal 的實現類。
summonInline 方法會再次觸發調用 Opt.derived$Equal 方法去尋找 Equal 實現類,因此在這里最終會遞歸的調用 Equal.derived 方法,最終再次進入到 summonAll 方法。
此時 Mirror 類型為 Opt.Some[T], 而其 MirroredElemTypes 類型則為:
(T *: EmptyTuple])
然后再次進入 case x: (t *: ts) =>
階段時,因為我們的 main 例子中,Opt[T] 為 Opt[Int],因此,這里通過 summonInline[Equal[Int]] 拿到的值是 Equal 伴生類中定義的 given int 實例,即:
object Equal:
//
given Equal[Int] with
def equal(x: Int, y: Int) = x == y
因此,在之前的 summonInline[Equal[t]] :: summonAll[ts]
中,前者已經走完,然后開始走后者,summonAll[ts], 此時,繼續遞歸調用 summonAll 方法,此時,MirroredElemTypes 的結果如下:
(Opt.None *: EmptyTuple)
繼續往下走, summonInline 需要獲取的結構變成 summonInline[Equal[Opt.None]]
此方法會再次進入, Equal.derived 方法,其中 T 類型為 Opt.None
, mirror.MirroredElemTypes 格式變成:
(EmptyTuple)
最終獲取為空,然后在 Equal.derived 方法中,調用完 summonAll
后,繼續根據 None 的 Mirror 類型,生成對應的 Equal 實例返回。
經過多輪遞歸調用后, summonAll 最終返回的結果是:
elemInstances:
List(Equal$$anon$2@511baa65, Equal$$anon$2@340f438e)
最后,再通過 eqSum
或者 eqProduct
包裝成最終的 Equal 返回。
總結:
通過 summon[Equal[Opt[T]] 會返回一個 Equal[Opt[T]] 的實現類,而該實現類會嵌套的持有多個 Opt[T] 元素的子類型的 Equal 實現類,然后最終再 Opt[T] 間通過調用 Equal.equal 時,請求會逐級轉發到 Equal 持有的其他 Equal 中。
Derives 這種實現,在設計模式中,我們稱為這種設計模式為 裝飾器模式,典型的如 Java 中的各類 IO 操作。