Наследование конструктора Scala 3 с помощью макросов

Каждый класс, реализующий трейт, должен объявить конструктор, который устанавливает поля трейта:

sealed trait WithPayload:
    def description: String
    def payload1: Int
    def payload2: Long

// All WithPayload's fields have to be listed.
final case class Foo(
    override val payload1: Int,
    override val payload2: Long
) extends WithPayload:
    override def description = "foo"

// All WithPayload's fields have to be listed again.
final case class Bar(
    override val payload1: Int,
    override val payload2: Long
) extends WithPayload:
    override def description = "bar"

Есть ли способ избавиться от повторяющихся объявлений конструктора с помощью макроса, например

#define EXTENDS_WITH_PAYLOAD ( \
    override val payload1: Int, \
    override val payload2: Long \
) extends WithPayload

а потом:

final case class Foo EXTENDS_WITH_PAYLOAD:
    override def description = "foo"

final case class Bar EXTENDS_WITH_PAYLOAD:
    override def description = "bar"

А как насчет использования параметров черт?

Gastón Schabas 25.04.2023 12:37

С параметрами типажей вам нужно не только объявить аргумент конструктора, но и передать его в трейт, что является еще большим дублированием: case class C(field1: Int, field2: Long) extends T(field1, field2).

Nil Admirari 25.04.2023 12:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("enable macro annotations")
class extendsWithPayload extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro ExtendsWithPayloadMacros.macroTransformImpl
}

object ExtendsWithPayloadMacros {
  def macroTransformImpl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._
    annottees match {
      case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
        val parents1 = parents :+ tq"WithPayload"
        val newParams = Seq(q"override val payload1: Int", q"override val payload2: Long")
        val paramss1 = paramss match {
          case Nil => Seq(newParams)
          case params :: paramss1 => (params ++ newParams) :: paramss1
        }
        q"""
          $mods class $tpname[..$tparams] $ctorMods(...$paramss1) extends { ..$earlydefns } with ..$parents1 { $self =>
            ..$stats
          }

          ..$tail
        """
    }
  }
}
sealed trait WithPayload {
  def description: String
  def payload1: Int
  def payload2: Long
}

@extendsWithPayload
final case class Foo() {
  override def description = "foo"
}

@extendsWithPayload
final case class Bar() {
  override def description = "bar"
}

//final case class Foo extends WithPayload with scala.Product with scala.Serializable {
//    override <caseaccessor> <paramaccessor> val payload1: Int = _;
//    override <caseaccessor> <paramaccessor> val payload2: Long = _;
//    def <init>(payload1: Int, payload2: Long) = {
//      super.<init>();
//      ()
//    };
//    override def description = "foo"
//  };
//  ()
//}
//final case class Bar extends WithPayload with scala.Product with scala.Serializable {
//    override <caseaccessor> <paramaccessor> val payload1: Int = _;
//    override <caseaccessor> <paramaccessor> val payload2: Long = _;
//    def <init>(payload1: Int, payload2: Long) = {
//      super.<init>();
//      ()
//    };
//    override def description = "bar"
//  };
//  ()
//}
  • В Scala 3 также появятся аннотации макросов, но новые определения можно увидеть только внутри расширения макроса.

Аннотации макросов в Scala 3 ( ответ)

Как сгенерировать класс в Dotty с помощью макроса? ( ответ)

Макрос Scala 3 для создания enum

Как создать конструктор без параметров во время компиляции с помощью макроса scala 3?

Что можно сделать с помощью MacroAnnotaiton, чего нельзя сделать с помощью макросов в Scala 3?

scalaVersion := "3.3.0-RC4"
import scala.annotation.{MacroAnnotation, experimental}
import scala.collection.mutable
import scala.quoted.*

/*sealed*/ trait WithPayload:
  def description: String
  def payload1: Int
  def payload2: Long

@experimental
class extendsWithPayload extends MacroAnnotation:
  def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
    import quotes.reflect.*
    tree match
      case ClassDef(className, ctr, parents, self, body) =>
        val res = List(ClassDef.copy(tree)(className, ctr, parents :+ TypeTree.of[WithPayload], self, body))
        println(res.map(_.show))
        res
@extendsWithPayload @experimental
final case class Foo():
  override def description = "foo" // method description overrides nothing

@extendsWithPayload @experimental
final case class Bar():
  override def description = "bar" // method description overrides nothing

summon[Foo <:< WithPayload] // Cannot prove that Foo <:< WithPayload
val foo = new Foo()
foo: WithPayload // Found: (foo: Foo), Required: WithPayload

//List(@scala.annotation.experimental @Macros.extendsWithPayload final case class Foo() extends Macros.WithPayload {
//  override def hashCode(): scala.Int = scala.runtime.ScalaRunTime._hashCode(Foo.this)
//  override def equals(x$0: scala.Any): scala.Boolean = Foo.this.eq(x$0.$asInstanceOf$[java.lang.Object]).||(x$0 match {
//    case x$0: App.Foo @scala.unchecked =>
//      true
//    case _ =>
//      false
//  })
//  override def toString(): java.lang.String = scala.runtime.ScalaRunTime._toString(Foo.this)
//  override def canEqual(that: scala.Any): scala.Boolean = that.isInstanceOf[App.Foo @scala.unchecked]
//  override def productArity: scala.Int = 0
//  override def productPrefix: scala.Predef.String = "Foo"
//  override def productElement(n: scala.Int): scala.Any = n match {
//    case _ =>
//      throw new java.lang.IndexOutOfBoundsException(n.toString())
//  }
//  override def description: java.lang.String = "foo"
//})
//List(@scala.annotation.experimental @Macros.extendsWithPayload final case class Bar() extends Macros.WithPayload {
//  override def hashCode(): scala.Int = scala.runtime.ScalaRunTime._hashCode(Bar.this)
//  override def equals(x$0: scala.Any): scala.Boolean = Bar.this.eq(x$0.$asInstanceOf$[java.lang.Object]).||(x$0 match {
//    case x$0: App.Bar @scala.unchecked =>
//      true
//    case _ =>
//      false
//  })
//  override def toString(): java.lang.String = scala.runtime.ScalaRunTime._toString(Bar.this)
//  override def canEqual(that: scala.Any): scala.Boolean = that.isInstanceOf[App.Bar @scala.unchecked]
//  override def productArity: scala.Int = 0
//  override def productPrefix: scala.Predef.String = "Bar"
//  override def productElement(n: scala.Int): scala.Any = n match {
//    case _ =>
//      throw new java.lang.IndexOutOfBoundsException(n.toString())
//  }
//  override def description: java.lang.String = "bar"
//})
  • Например, вы можете использовать генерацию кода с Scalameta

Аннотация макроса для переопределения функции toString Scala ( ответ)

Есть ли в Java или Scala способ добавить обратный вызов в исключение, чтобы при перехвате исключения вызывался обратный вызов? ( ответ)

Условная компиляция Scala

Как объединить несколько импортов в scala?

Scalac 2.13 компилирует большой автоматически сгенерированный файл scala: слишком большой метод

проект/сборка.sbt

libraryDependencies ++= Seq(
  "org.scalameta" %% "scalameta" % "4.7.7"
)

build.sbt

ThisBuild / scalaVersion := "3.2.2"

lazy val common = project

lazy val before = project
  .dependsOn(common)

lazy val after = project
//.dependsOn(common)
  .settings(
    Compile / unmanagedSourceDirectories += (Compile / sourceManaged).value
  )

lazy val transform = taskKey[Unit]("Transform sources")

transform := {
  val inputDir  = (before / Compile / scalaSource).value
  val outputDir = (after / Compile / sourceManaged).value
  Generator.gen(inputDir, outputDir)
}

проект/Генератор.scala

import sbt.*

object Generator {
  val ALL: Seq[String] = Seq()

  def isAll(filesToTransform: Seq[String]): Boolean = filesToTransform.isEmpty

  def gen(
           inputDir: File,
           outputDir: File,
           filesToTransform: Seq[String] = ALL,
         ): Unit = {
    val finder: PathFinder = inputDir ** "*.scala"
    val scalametaTransformer = new AnnotationProcessor()

    for (inputFile <- finder.get) yield {
      val inputFileName = inputFile.name
      val inputStr = IO.read(inputFile)
      val transform: String => String =
        if (isAll(filesToTransform) || filesToTransform.contains(inputFileName))
          (scalametaTransformer(_: String))
        else identity
      val outputStr = transform(inputStr)
      val outputFile = outputDir / inputFile.relativeTo(inputDir).get.toString
      IO.write(outputFile, outputStr)
    }
  }
}

проект/AnnotationProcessor.scala

import scala.meta.*

class AnnotationProcessor extends TreeTransformer {
  val isExtendsWithPayload: Mod => Boolean = { case mod"@extendsWithPayload" => true; case _ => false }

  override def apply(tree: Tree): Tree = {
    val tree1 = tree match {
      case q"..$mods class $tname[..$tparams] ..$ctorMods (...$paramss) $template" if mods.exists(isExtendsWithPayload) =>
        val mods1 = mods.filterNot(isExtendsWithPayload)
        template match {
          case template"{ ..$earlyStats } with ..$inits { $self => ..$stats }" =>
            val inits1 = inits :+ init"WithPayload"
            val template1 = template"{ ..$earlyStats } with ..$inits1 { $self => ..$stats }"
            val newParams = List(param"override val payload1: Int", param"override val payload2: Long")
            val paramss1: List[Term.ParamClause] = paramss match {
              case Nil => List(newParams)
              case params :: paramss1 => (params ++ newParams) :: paramss1
            }
            q"..$mods1 class $tname[..$tparams] ..$ctorMods (...$paramss1) $template1"
        }
      case _ => tree
    }

    super.apply(tree1)
  }
}

проект/StringTransformer.scala

trait StringTransformer {
  def apply(str: String): String
}

проект/TreeTransformer.scala

import scala.meta.*

trait TreeTransformer extends Transformer with StringTransformer {
  override def apply(str: String): String = {
    val origTree = dialects.Scala3(str).parse[Source].get
    val newTree  = apply(origTree)
    newTree.toString
  }
}

общий/src/основной/скала/extendsWithPayload.scala

import scala.annotation.StaticAnnotation

class extendsWithPayload extends StaticAnnotation 

до/src/main/scala/App.scala

sealed trait WithPayload:
  def description: String
  def payload1: Int
  def payload2: Long

@extendsWithPayload
final case class Foo():
  override def description = "foo"

  @extendsWithPayload
  class Nested():
    override def description = "nested"

@extendsWithPayload
final case class Bar():
  override def description = "bar"

final case class Baz()

Выполнить sbt after/clean transform

после/target/scala-3.2.2/src_managed/main/App.scala

sealed trait WithPayload {
  def description: String
  def payload1: Int
  def payload2: Long
}
final case class Foo(override val payload1: Int, override val payload2: Long) extends WithPayload {
  override def description = "foo"
  class Nested(override val payload1: Int, override val payload2: Long) extends WithPayload { override def description = "nested" }
}
final case class Bar(override val payload1: Int, override val payload2: Long) extends WithPayload { override def description = "bar" }
final case class Baz()
  • Или вы можете использовать препроцессор C++ (#define), как вы написали.

#define на Java

gcc -xc App.scala -E -P -o App1.scala

App.скала

#define EXTENDS_WITH_PAYLOAD ( \
    override val payload1: Int, \
    override val payload2: Long \
) extends WithPayload

sealed trait WithPayload:
    def description: String
    def payload1: Int
    def payload2: Long

final case class Foo EXTENDS_WITH_PAYLOAD:
    override def description = "foo"

final case class Bar EXTENDS_WITH_PAYLOAD:
    override def description = "bar"

App1.scala

sealed trait WithPayload:
    def description: String
    def payload1: Int
    def payload2: Long
final case class Foo ( override val payload1: Int, override val payload2: Long ) extends WithPayload:
    override def description = "foo"
final case class Bar ( override val payload1: Int, override val payload2: Long ) extends WithPayload:
    override def description = "bar"
  • Или вы можете определить Scalafix правило перезаписи.

Можно ли с помощью макроса модифицировать сгенерированный код вызова экземпляра структурной типизации?

scala.meta родитель родителя Defn.Object (Scalameta + Semanticdb)

Квазицитаты в Scalafix

Большое спасибо, но я, вероятно, просто продолжу копипаст :)

Nil Admirari 25.04.2023 15:40

Другие вопросы по теме