?? Scala3 Type Class Derivation 類型派生類

詳見代碼庫:https://github.com/leihuazhe/scala_scala3_samples

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,需要依照定義時的順序。

  • ordinalfromProduct 方法是根據 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 操作。

image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容