0

我是 Scala 2 Macros 的初学者(在我切换到 Dotty 之前),在尝试了无形类型类派生之后,我想更进一步,编写一个宏,可以为任何scala.Product没有它的类型生成类型类实例。

(为了举例,让我们忽略嵌套递归类型,所以我的目标是平面案例类。)

我的类型类是一个抽象类Coder[T](例如带有encode()/的特征decode())。

所以生成的代码为:

case class Pojo(
  s: String,
  i: Int,
  l: List[Int]
)

应该是这样的:

import com.github.fpopic.scalamacros.Pojo
import org.apache.beam.sdk.coders.Coder
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import java.util

class PojoCoder extends Coder[Pojo] {

  import com.github.fpopic.scalamacros.beam.DefMacroCoder.{
    stringCoder,
    intCoder,
    listCoder
  }

  override def encode(value: Pojo, os: OutputStream): Unit = {
    stringCoder.encode(value.s, os)
    intCoder.encode(value.i, os)
    listCoder(intCoder).encode(value.l, os)
  }

  override def decode(is: InputStream): Pojo = {
    Pojo(
      s = stringCoder.decode(is),
      i = intCoder.decode(is),
      l = listCoder(intCoder).decode(is)
    )
  }

  override def getCoderArguments: util.List[_ <: Coder[_]] = {
    Collections.emptyList()
  }

  override def verifyDeterministic(): Unit =  ()
}

(删除了完全指定的类名以提高可读性)

在宏中,我尝试:

  • 检查weakTypeOf[P]
  • 遍历每个案例类构造函数字段(类型F
    • 隐式找到它们的类型类Coder[F]实例并将其添加到树中
    • 并将它们的encode()decode()表达式附加到有助于最终Coder[P]方法的树中。
def materializeProductCoder[P: c.WeakTypeTag](c: blackbox.Context): c.Expr[Coder[P]] = {
    import c.universe._
    val tpe = c.weakTypeOf[P]
    val helper = new MacrosHelper[c.type](c)

    val expressions =
      helper.getPrimaryConstructorMembers(tpe).map { field =>
        val fieldTerm = field.asTerm.name // e.g. value.s (for now just s)
        val fieldType = field.typeSignature.finalResultType // e.g. String

        val fieldCoderName = c.freshName(TermName("coder")) // e.g. give friendly name coder$...
        val fieldCoderInstance = // e.g. finds instance of Coder[String]
          c.typecheck(
            tree = q"""_root_.scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[${fieldType}]]""",
            silent = false
          )

        val fieldCoderExpression =
          q"private val ${fieldCoderName}: org.apache.beam.sdk.coders.Coder[${fieldType}] = ${fieldCoderInstance}"

        val fieldEncodeExpression =
          q"${fieldCoderName}.encode(value.${fieldTerm}, os)" // replace with full relative name (with dots) instead of value

        val fieldDecodeExpression =
          q"${field.asTerm} = ${fieldCoderName}.decode(is)"

        (fieldCoderExpression, fieldEncodeExpression, fieldDecodeExpression)
      }

    val fieldCodersExpression = expressions.map(_._1).distinct
    val coderEncodeExpresions = expressions.map(_._2)
    val coderDecodeExpresions = expressions.map(_._3)

    val coderExpression =
      q"""{
            new org.apache.beam.sdk.coders.Coder[${tpe}] {

              {import ${c.prefix}._}

              ..${fieldCodersExpression}

              override def encode(value: ${tpe}, os: java.io.OutputStream): _root_.scala.Unit = {
                ..${coderEncodeExpresions}
              }

              override def decode(is: java.io.InputStream): ${tpe} = {
                ${tpe.typeConstructor}(
                  ..${coderDecodeExpresions}
                )
              }

              override def getCoderArguments: java.util.List[_ <: org.apache.beam.sdk.coders.Coder[_]] = {
                java.util.Collections.emptyList
              }

              override def verifyDeterministic(): _root_.scala.Unit = ()
            }
          }
      """

    val ret = coderExpression
    c.Expr[Coder[P]](ret)
  }

但调用后出现错误sbt Test / compile

(在导入和隐式搜索方面有点挣扎,所以现在有中间private vals,并且 distinct 没用)

{
  final class $anon extends org.apache.beam.sdk.coders.Coder[com.github.fpopic.scalamacros.beam.Pojo] {
    def <init>() = {
      super.<init>();
      ()
    };
    {
      import DefMacroCoder._;
      ()
    };
    private val coder$macro$1: org.apache.beam.sdk.coders.Coder[String] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[String]](DefMacroCoder.stringCoder);
    private val coder$macro$2: org.apache.beam.sdk.coders.Coder[Int] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[Int]](DefMacroCoder.intCoder);
    private val coder$macro$3: org.apache.beam.sdk.coders.Coder[List[Int]] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[List[Int]]](DefMacroCoder.listCoder[Int](DefMacroCoder.intCoder));
    override def encode(value: com.github.fpopic.scalamacros.beam.Pojo, os: java.io.OutputStream): _root_.scala.Unit = {
      coder$macro$1.encode(value.s, os);
      coder$macro$2.encode(value.i, os);
      coder$macro$3.encode(value.l, os)
    };
    override def decode(is: java.io.InputStream): com.github.fpopic.scalamacros.beam.Pojo = com.github.fpopic.scalamacros.beam.Pojo(s = coder$macro$1.decode(is), i = coder$macro$2.decode(is), l = coder$macro$3.decode(is));
    override def getCoderArguments: java.util.List[_$1] forSome { 
      <synthetic> type _$1 <: org.apache.beam.sdk.coders.Coder[_$2] forSome { 
        <synthetic> type _$2
      }
    } = java.util.Collections.emptyList;
    override def verifyDeterministic(): _root_.scala.Unit = ()
  };
  new $anon()
}
[error] .../DefMacroCoderSpec.scala:17:56: com.github.fpopic.scalamacros.beam.Pojo does not take parameters
[error]     val coder: Coder[Pojo] = DefMacroCoder.productCoder[Pojo]
[error]                                                        ^
[error] one error found

我相信来自这里但不完全理解编译器试图告诉我什么?

完整代码示例的链接可以在这里
找到 CI 错误的链接可以在这里找到。

4

1 回答 1

1

你实例化你的类的方式是错误的:

${tpe.typeConstructor}(...)

它应该是

new $tpe(...)

或者,如果您想使用案例类伴随对象apply而不是普通构造函数来执行此操作:

${tpe.typeSymbol.companion}(...)

注意:类型构造函数(也称为更高种类的类型)与类构造函数无关

于 2021-05-07T13:45:52.897 回答