Scala3 derives 解説

# はじめに
scala3のderivesを実装しながら解説していく。

# 参考資料
- https://docs.scala-lang.org/scala3/reference/metaprogramming/compiletime-ops.html

# derives とは
Scala3から `derives` という機能が追加された。
これはHaskellの deriving宣言 という**クラスメソッドの実装方法が自明な場合，deriving宣言を用いてinstance宣言を省略する機能**をscalaに持ち込んだものかと思う（多分）

haskellのderiving
```haskell
data Position = Position { x :: Double, y :: Double } deriving (Show, Monoid)
-- ↑ではShowとMonoidのインスタンスのinstance宣言を省略している
```

scalaのderives
```scala
case class Position(x: Double, y: Double) derives Show, Monoid
```

# 実装してみる
Scalaの場合は型クラスごとに生成方法を書いてあげる必要がある。
とりあえず型クラスを定義する
```scala
trait Monoid[A]:
  def empty: A
  def combine(x: A, y: A): A
  extension (x: A) def |+| (y: A): A = combine(x, y)

object Monoid:
  def apply[T](using m: Monoid[T]): Monoid[T] = m

trait Show[A]:
  def show(a: A): String

object Show:
  def apply[T](using s: Show[T]): Show[T] = s
  
extension [A](a: A)(using S: Show[A]) def show: String = S.show(a)
// Show内に記述すると同名メソッドでエラーになるので雑に外に出してる
```

次にADT内のフィールドに対応するインスタンスを定義する。
```scala
given Monoid[Double] with
  def empty = 0
  def combine(x: Double, y: Double): Double = x + y

given Show[Double] with
  def show(a: Double): String = a.toString()
```

ここから本題。
```scala
object Monoid:
  // さっきまでに書いたコードは省略

  import scala.compiletime.{erasedValue, summonInline}
  import scala.deriving.Mirror
  
  inline private def summonAll[T <: Tuple]: List[Monoid[?]] =
    inline erasedValue[T] match
      case _: EmptyTuple => Nil
      case _: (t *: ts)  => summonInline[Monoid[t]] :: summonAll[ts]

  inline given derived[A](using m: Mirror.ProductOf[A]): Monoid[A] = 
    new Monoid[A]:
      override def empty: A =
        val instances = summonAll[m.MirroredElemTypes]
        m.fromProduct(Tuple.fromArray(instances.map(_.empty).toArray))

      override def combine(x: A, y: A): A =
        val instances = summonAll[m.MirroredElemTypes]
        val xs             = x.asInstanceOf[Product].productIterator
        val ys             = y.asInstanceOf[Product].productIterator
        val combineds      = xs.zip(ys).zipWithIndex.map { 
          case ((xx, yy), i) =>
            instances(i).asInstanceOf[Monoid[Any]].combine(xx, yy)
        }
        m.fromProduct(Tuple.fromArray(combineds.toArray))
```
一旦Monoidまで。

```scala
inline private def summonAll[T <: Tuple]: List[Monoid[?]] =
  inline erasedValue[T] match
    case _: EmptyTuple => Nil
    case _: (t *: ts)  => summonInline[Monoid[t]] :: summonAll[ts]
```
summonAllではTupleを受け取りADTのフィールドに対応するMonoidをリストで返している。
erasedValue[T]は何かというと
> The erasedValue function pretends to return a value of its type argument T. Calling this function will always result in a compile-time error unless the call is removed from the code while inlining.

と公式にある通り、その型引数Tの値を返す*ふり*をする。
ここではTupleが空の場合と値が存在する場合とでパターンマッチングを行う為に使用しており、下のパターンマッチング `_: (t *: ts)` の t と ts にはそれぞれTupleの先頭の型と先頭以外の型のTupleが束縛されることになる。

`summonInline` はスコープ内に存在する指定の方のインスタンスを取得するAPIで、`scala.compiletime.summon`と同等の振る舞いをするが、呼び出しがインライン化されるまでインスタンスの取得は遅延される。

derivedは戻り値からわかるようにMonoidのインスタンスを返すものだが、**ADTのインスタンスを返すという点で通常のインスタンス定義とは異なる**。
また、メソッドの型からわかるようにderivedによってADTの型クラスインスタンスが生成される。

empty・combineの実装ではusing句で取得した`Mirror.ProductOf[A]` から *Productを継承している抽象型A* のメタ情報をsummonAllに渡す事で各フィールドに対応するMonoidインスタンスをリストで取得している。

```scala
m.fromProduct(Tuple.fromArray(instances.map(_.empty).toArray))
```
は見てなんとなくわかるとおりADTの各フィールドの値をsummonAllで取得したMonoidのemptyとして `scala.deriving.Mirror.Product::fromProduct`から対象のADTを生成している。

```scala
val xs             = x.asInstanceOf[Product].productIterator
val ys             = y.asInstanceOf[Product].productIterator
val combineds      = xs.zip(ys).zipWithIndex.map { 
  case ((xx, yy), i) =>
    instances(i).asInstanceOf[Monoid[Any]].combine(xx, yy)
}
```
xs・ysではそれぞれxとyのフィールドの値を`Iterator[Any]`として取得しており、なぜAnyで取得するかというと `instances: List[Monoid[?]]` から添字指定で取得した `Monoid[?]` を `Monoid[Any]` としてcombineを呼び出す為。circeでもこんな書き方だったので恐らくderivesを実装する際にはよく使うパターンぽい。

Monoidと同じようにShowを書いていく
```scala
object Show:
  // 先ほど記述したものは省略
  
  inline private def summonAll[T <: Tuple]: List[Show[?]] =
    inline erasedValue[T] match
      case _: EmptyTuple => Nil
      case _: (t *: ts)  => summonInline[Show[t]] :: summonAll[ts]

  inline private def summonLabels[T <: Tuple]: List[String] = 
    inline erasedValue[T] match 
      case _: EmptyTuple => Nil
      case _: (t *: ts)  => constValue[t].asInstanceOf[String] :: summonLabels[ts]

  inline given derived[A](using m: Mirror.ProductOf[A]): Show[A] = 
    new Show[A]:
      override def show(a: A): String =
        val instances = summonAll[m.MirroredElemTypes]
        val lables = summonLabels[m.MirroredElemLabels]
        val name = constValue[m.MirroredLabel]
        val as = a.asInstanceOf[Product].productIterator
        val shows = as.zipWithIndex.map { case (aa, i) => 
            instances(i).asInstanceOf[Show[Any]].show(aa)
        }
        s"$name { ${lables.zip(shows).map { case (l, s) => s"$l: $s" }.mkString(", ")} }"
```
おおかた先ほどのMonoidと同じではあるが、今回ADTを文字列に変換するにあたりフィールドの名前が欲しかったので `summonLabels` というメソッドを追加しているのと `constValue[m.MirroredLabel]` でクラス名を取得している。

# 出力を見る
いい感じに出力できているか見てみる。
```scala
case class Position(x: Double, y: Double) derives Show, Monoid

@main def main: Unit = 
  val p1 = Position(2.0, 3.0)
  val p2 = Position(1.0, 4.0)
  println { (p1 |+| p2).show } // Position { x: 3.0, y: 7.0 }
```
期待通りですね！！

# 今回謎だったこと。
今回記事を書くにあたり↓のようにsummonAllの高カインドを抽象化しようと思ったらエラーになってしまった🥺
わかる方コメントいただけると嬉しいです🙏

```scala
inline def summonAll[T <: Tuple, F[_]]: List[F[?]] =
  inline erasedValue[T] match
    case _: EmptyTuple => Nil
    case _: (t *: ts)  => summonInline[F[t]] :: summonAll[ts, F]

/**
[error] 13 |inline def summonAll[T <: Tuple, F[_]]: List[F[?]] =
[error]    |                                             ^^^^
[error]    |   unreducible application of higher-kinded type F to wildcard arguments
*/
```