Geração automática de classes de tipo no Scala 3

Scala usa uma abordagem abrangente para dotar classes com funcionalidades adicionais chamadas classes de tipo. Para aqueles que nunca encontraram essa abordagem, recomendo a leitura deste artigo . Essa abordagem permite que você mantenha o código de alguns aspectos do funcionamento da classe separadamente da implementação real da classe. E criá-lo sem nem mesmo ter acesso ao código da própria classe. Em particular, essa abordagem é justificada e recomendada ao dotar classes com a capacidade de serializar / desserializar em um formato específico. Por exemplo, a biblioteca Json da estrutura Play usa classes de tipo para definir as regras para representar objetos no formato json.





Se a classe de tipo se destina a ser usada em um grande número de classes diferentes (como em serialização / desserialização), escrever o código de classe de tipo para cada classe com a qual deve trabalhar é irracional e trabalhoso. Em muitos casos, você pode gerar uma implementação de classe de tipo sabendo automaticamente o conjunto de atributos da classe para a qual se destina. Infelizmente, na versão atual do scala, a geração automática da classe de tipo é difícil. Ele exige que você mesmo escreva macros ou use estruturas de terceiros para gerar classes de tipo, como sem forma ou magnólia , que também são baseadas em macro.





Scala 3, que está avançando rapidamente para o lançamento, possui uma linguagem integrada para geração automática de classes de tipo. Este artigo tenta entender o uso desse mecanismo usando uma classe de tipo concreto como exemplo.





Declaração de classe de tipo

Como exemplo, uma classe de tipo um tanto artificial será usada, que chamaremos de Inverter. Ele conterá um método:





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)
      
      



Para tal classe, o valor deve ser invertido, mas __hidden não deve ser invertido:





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



conclusões

Como você pode ver, a implementação embutida da geração da classe de tipo é bastante utilizável, bastante conveniente e cobre os padrões básicos de uso. Parece-me que este mecanismo permitirá, na maioria dos casos, fazer sem macros e sem bibliotecas de terceiros para gerar classe de tipo.





Você pode jogar ao redor com o código fonte para o exemplo final abordados neste artigo .








All Articles