Génération automatique des classes de types dans Scala 3

Scala utilise une approche étendue pour doter les classes de fonctionnalités supplémentaires appelées classes de types. Pour ceux qui n'ont jamais rencontré cette approche, je recommande de lire cet article . Cette approche vous permet de conserver le code de certains aspects du fonctionnement de la classe séparément de l'implémentation réelle de la classe. Et créez-le sans même avoir accès au code de la classe elle-même. En particulier, cette approche est justifiée et est recommandée pour doter les classes de la possibilité de sérialiser / désérialiser dans un format spécifique. Par exemple, la bibliothèque Json du framework Play utilise des classes de types pour définir les règles de représentation des objets au format json.





Si la classe de type est destinée à être utilisée dans un grand nombre de classes différentes (comme dans la sérialisation / désérialisation), alors l'écriture du code de classe de type pour chaque classe avec laquelle elle devrait fonctionner est irrationnelle et laborieuse. Dans de nombreux cas, vous pouvez générer automatiquement une implémentation d'une classe de type en connaissant l'ensemble des attributs de la classe à laquelle elle est destinée. Malheureusement, dans la version actuelle de scala, la génération automatique de la classe de type est difficile. Cela vous oblige à écrire vous-même des macros ou à utiliser des frameworks tiers pour générer des classes de types telles que shapeless ou magnolia , qui sont également basées sur des macros.





Scala 3, qui évolue rapidement vers la sortie, dispose d'un langage intégré pour la génération automatique de classe de type. Cet article tente de comprendre l'utilisation de ce mécanisme en utilisant une classe de type concrète comme exemple.





Déclaration de classe de type

A titre d'exemple, une classe de type plutôt artificielle sera utilisée, que nous appellerons Inverter. Il contiendra une méthode:





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)
      
      



Pour une telle classe, la valeur doit être inversée, mais __hidden ne doit pas être inversé:





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



conclusions

Comme vous pouvez le voir, l'implémentation intégrée de la génération de classe de type est tout à fait utilisable, assez pratique et couvre les modèles d'utilisation de base. Il me semble que ce mécanisme permettra dans la plupart des cas de se passer de macros et de bibliothèques tierces pour générer des classes de types.





Vous pouvez jouer autour avec le code source pour l'exemple final couvert dans cet article .








All Articles