Scala3での型クラスの自動生成

Scalaは、タイプクラスと呼ばれる追加機能をクラスに与えるために、広範なアプローチを使用しています。このアプローチに遭遇したことがない人は、この記事を読むことをお勧めします。このアプローチにより、クラスの機能のいくつかの側面のコードを、クラスの実際の実装とは別に保持することができます。そして、クラス自体のコードにアクセスすることさえせずにそれを作成します。特に、このアプローチは正当化され、特定の形式にシリアル化/逆シリアル化する機能をクラスに与える場合に推奨されます。たとえば、PlayフレームワークのJsonライブラリは、タイプクラスを使用して、オブジェクトをjson形式で表すためのルールを設定します。





型クラスが多数の異なるクラス(シリアル化/逆シリアル化など)で使用されることを意図している場合、それが機能するクラスごとに型クラスコードを書くことは不合理で面倒です。多くの場合、対象となるクラスの属性のセットを自動的に認識して、型クラスの実装を生成できます。残念ながら、現在のバージョンのscalaでは、型クラスの自動生成は困難です。自分でマクロを作成するか、サードパーティのフレームワークを使用して、同じくマクロベースのシェイプレスマグノリアなどのタイプクラスを生成する必要があります





リリースに向けて急速に進んでいるScala3には、型クラスの自動生成用の言語が組み込まれています。この記事では、具体的な型クラスを例として使用して、このメカニズムの使用法を理解しようとしています。





タイプクラス宣言

例として、かなり人工的なタイプのクラスが使用されます。これをInverterと呼びます。これには1つのメソッドが含まれます。





trait Inverter[T] {

  def invert(value: T): T

}
      
      



"" . , - , - NOT. . type class , , .





- type class . given ( implicit Scala 2) Inverter Inverter:





object Inverter {

  given Inverter[String] = new Inverter[String] {
    override def invert(str: String): String =
      str.reverse
  }

  given Inverter[Int] = new Inverter[Int] {
    override def invert(value: Int): Int =
      -value
  }
  
  given Inverter[Boolean] = new Inverter[Boolean] {
    override def invert(value: Boolean): Boolean =
      !value
  }
  
}
      
      



Inverter . derived[T] Inverter[T]. . type class ( shapeless 3). . derived Mirror.Of[T]. . Mirror.Of[T] :





  • case case





  • (enum enum cases)





  • sealed trait- case case .





type class .





runtime , compile time Scala 3 ( , , , inline).





derived . case ( ).





  inline def derived[T](using m: Mirror.Of[T]): Inverter[T] = {
    val elemInstances = summonAll[m.MirroredElemTypes]
    inline m match {
      case p: Mirror.ProductOf[T] =>
        productInverter[T](p, elemInstances)
      case s: Mirror.SumOf[T] => ???
    }
  }

  inline def summonAll[T <: Tuple]: List[Inverter[_]] =
    inline erasedValue[T] match
      case _: EmptyTuple => List()
      case _: (t *: ts) => summonInline[Inverter[t]] :: summonAll[ts]
      
      



. Miror.Of[T] MirroredElemTypes. case . , Inverter . summonAll. summonAll given summonInline. , summonAll type class .





Inverter - (case , case , ) (sealed trait enum). , productInverter, Inverter Inverter :





def productInverter[T](p: Mirror.ProductOf[T], elems: List[Inverter[_]]): Inverter[T] = {
    new Inverter[T] {
      def invert(value: T): T = {
        val oldValues = value.asInstanceOf[Product].productIterator
        val newValues = oldValues.zip(elems)
          .map { case (value, inverter) =>
            inverter.asInstanceOf[Inverter[Any]].invert(value)
          }
          .map(_.asInstanceOf[AnyRef])
          .toArray
        p.fromProduct(Tuple.fromArray(newValues))
      }
    }
  }
      
      



. -, . trait Product, . - Inverter Inverter. , -, . fromProduct Mirror .





derived

derived type class . . - case derives type class. :





case class Sample(intValue: Int, stringValue: String, boolValue: Boolean) derives Inverter
      
      



Inverter[Sample] Sample. summon :





println(summon[Inverter[Sample]].invert(Sample(1, "abc", false)))
// : Sample(-1,cba,true)
      
      



type class .





, type class. given derived:





case class Sample(intValue: Int, stringValue: String, boolValue: Boolean)

@main def mainProc = {
  
  given Inverter[Sample] = Inverter.derived
  println(summon[Inverter[Sample]].invert(Sample(1, "abc", false)))
  // : Sample(-1,cba,true)
  
} 
      
      



type class . case type class . :





case class InnerSample(s: String)
case class OuterSample(inner: InnerSample)
      
      



type class:





  given Inverter[InnerSample] = Inverter.derived
  given Inverter[OuterSample] = Inverter.derived
  println(summon[Inverter[OuterSample]].invert(OuterSample(InnerSample("abc"))))
  // : OuterSample(InnerSample(cba))
      
      



type class Mirror.Of. given:





  inline given[T] (using m: Mirror.Of[T]): Inverter[T] = Inverter.derived[T]
  
  println(summon[Inverter[OuterSample]].invert(OuterSample(InnerSample("abc"))))
  // : OuterSample(InnerSample(cba))
      
      



type class . trait , ( import ) , :





trait AutoInverting {
  inline given[T] (using m: Mirror.Of[T]): Inverter[T] = Inverter.derived[T]
}
      
      



type class

type class type class . .





case :





case class SampleUnprotected(value: String)
case class SampleProtected(value: String)
case class Sample(prot: SampleProtected, unprot: SampleUnprotected)
      
      



SampleProtected Inverter, value. type class Sample:





  inline given[T] (using m: Mirror.Of[T]): Inverter[T] = Inverter.derived[T]
  
  given Inverter[SampleProtected] = new Inverter[SampleProtected] {
    override def invert(prot: SampleProtected): SampleProtected = SampleProtected(prot.value)
  }
  
  println(summon[Inverter[Sample]].invert(Sample(SampleProtected("abc"), SampleUnprotected("abc"))))
  // : Sample(SampleProtected(abc),SampleUnprotected(cba))
      
      



Inverter Sample Inverter SampleProtected. .





sealed trait enum

type class case ( ) type class sealed trait . derived , :





  inline def derived[T](using m: Mirror.Of[T]): Inverter[T] = {
    val elemInstances = summonAll[m.MirroredElemTypes]
    inline m match {
      case p: Mirror.ProductOf[T] =>
        productInverter[T](p, elemInstances, getFields[p.MirroredElemLabels])
      case s: Mirror.SumOf[T] => 
        sumInverter(s, elemInstances)
    }
  }

  def sumInverter[T](s: Mirror.SumOf[T], elems: List[Inverter[_]]): Inverter[T] = {
    new Inverter[T] {
      def invert(value: T): T = {
        val index = s.ordinal(value)
        elems(index).asInstanceOf[Inverter[Any]].invert(value).asInstanceOf[T]
      }
    }
  }
      
      



. Mirror . ordinal Mirror. . Inverter ( ) .





. sealed trait Either Option:





def checkInverter[T](value: T)(using inverter: Inverter[T]): Unit = {
  println(s"$value => ${inverter.invert(value)}")
}
  
@main def mainProc = {
  
  inline given[T] (using m: Mirror.Of[T]): Inverter[T] = Inverter.derived[T]
  
  given Inverter[SampleProtected] = new Inverter[SampleProtected] {
    override def invert(prot: SampleProtected): SampleProtected = SampleProtected(prot.value)
  }
  
  val eitherSampleLeft: Either[SampleProtected, SampleUnprotected] = Left(SampleProtected("xyz"))
  checkInverter(eitherSampleLeft)
  // : Left(SampleProtected(xyz)) => Left(SampleProtected(xyz))
  val eitherSampleRight: Either[SampleProtected, SampleUnprotected] = Right(SampleUnprotected("xyz"))
  checkInverter(eitherSampleRight)
  // : Right(SampleUnprotected(xyz)) => Right(SampleUnprotected(zyx))
  val optionalValue: Option[String] = Some("123")
  checkInverter(optionalValue)
  // : Some(123) => Some(321)
  val optionalValue2: Option[String] = None
  checkInverter(optionalValue2)
  // : None => None
  checkInverter((6, "abc"))
  // : (6,abc) => (-6,cba)
}
      
      



Inverter type class summon. Either ( SumOf, ProductOf).





type class , /. , . type class . Inverter : . . derived:





  inline def derived[T](using m: Mirror.Of[T]): Inverter[T] = {
    val elemInstances = summonAll[m.MirroredElemTypes]
    inline m match {
      case p: Mirror.ProductOf[T] =>
        productInverter[T](p, elemInstances, getFields[p.MirroredElemLabels])
      case s: Mirror.SumOf[T] => 
        sumInverter(s, elemInstances)
    }
  }

  inline def getFields[Fields <: Tuple]: List[String] =
    inline erasedValue[Fields] match {
      case _: (field *: fields) => constValue[field].toString :: getFields[fields]
      case _ => List()
    }

  def productInverter[T](p: Mirror.ProductOf[T], elems: List[Inverter[_]], labels: Seq[String]): Inverter[T] = {
    new Inverter[T] {
      def invert(value: T): T = {
        val newValues = value.asInstanceOf[Product].productIterator
          .zip(elems).zip(labels)
          .map { case ((value, inverter), label) =>
            if (label.startsWith("__"))
              value
            else
              inverter.asInstanceOf[Inverter[Any]].invert(value)
          }
          .map(_.asInstanceOf[AnyRef])
          .toArray
        p.fromProduct(Tuple.fromArray(newValues))
      }
    }
  }
      
      



:





case class Sample(value: String, __hidden: String)
      
      



このようなクラスの場合、値を反転する必要がありますが、__ hiddenを反転しないでください。





  inline given[T] (using m: Mirror.Of[T]): Inverter[T] = Inverter.derived[T]
  
  println(summon[Inverter[Sample]].invert(Sample("abc","abc")))
  // : Sample(cba,abc)
      
      



結論

ご覧のとおり、型クラス生成の組み込み実装は非常に使いやすく、非常に便利で、基本的な使用パターンをカバーしています。このメカニズムにより、ほとんどの場合、マクロなしで、タイプクラスを生成するためのサードパーティライブラリなしで実行できるように思われます。





この記事で説明する最後の例のソースコードは、ここで再生できます








All Articles