`Coder[P <: Product]` 的 Scala 2 宏类型 class 推导以错误 `P does not take parameters` 结束
Scala 2 macro type class derivation for `Coder[P <: Product]` ends with error `P does not take parameters`
我是 Scala 2 宏的初学者(在我切换到 Dotty 之前),在尝试了无形类型 class 推导之后,我想更进一步并编写一个可以生成类型的宏 class 没有它的任何 scala.Product
实例。
(为了举例,让我们忽略嵌套递归类型,所以我的目标是扁平化 classes。)
我的类型 class 是抽象的 class 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 = ()
}
(删除了完全指定的 class 名称以提高可读性)
在宏中我尝试:
- 检查
weakTypeOf[P]
- 迭代每个案例 class 构造函数字段(
F
类型)
- 隐式找到它们的类型class
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 val
s,而 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: org.apache.beam.sdk.coders.Coder[String] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[String]](DefMacroCoder.stringCoder);
private val coder$macro: org.apache.beam.sdk.coders.Coder[Int] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[Int]](DefMacroCoder.intCoder);
private val coder$macro: 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.encode(value.s, os);
coder$macro.encode(value.i, os);
coder$macro.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.decode(is), i = coder$macro.decode(is), l = coder$macro.decode(is));
override def getCoderArguments: java.util.List[_] forSome {
<synthetic> type _ <: org.apache.beam.sdk.coders.Coder[_] forSome {
<synthetic> type _
}
} = 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
我相信这来自 here 但不完全理解编译器试图告诉我什么?
您实例化 class 的方式是错误的:
${tpe.typeConstructor}(...)
应该是
new $tpe(...)
或者如果你想用 case class 伴生对象的 apply
而不是普通构造函数来做到这一点:
${tpe.typeSymbol.companion}(...)
注意:类型构造函数(也称为更高级类型)与class构造函数[=无关14=]
我是 Scala 2 宏的初学者(在我切换到 Dotty 之前),在尝试了无形类型 class 推导之后,我想更进一步并编写一个可以生成类型的宏 class 没有它的任何 scala.Product
实例。
(为了举例,让我们忽略嵌套递归类型,所以我的目标是扁平化 classes。)
我的类型 class 是抽象的 class 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 = ()
}
(删除了完全指定的 class 名称以提高可读性)
在宏中我尝试:
- 检查
weakTypeOf[P]
- 迭代每个案例 class 构造函数字段(
F
类型)- 隐式找到它们的类型class
Coder[F]
实例并将其添加到树 - 并将它们的
encode()
和decode()
表达式附加到有助于最终Coder[P]
方法的树中。
- 隐式找到它们的类型class
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 val
s,而 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: org.apache.beam.sdk.coders.Coder[String] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[String]](DefMacroCoder.stringCoder);
private val coder$macro: org.apache.beam.sdk.coders.Coder[Int] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[Int]](DefMacroCoder.intCoder);
private val coder$macro: 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.encode(value.s, os);
coder$macro.encode(value.i, os);
coder$macro.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.decode(is), i = coder$macro.decode(is), l = coder$macro.decode(is));
override def getCoderArguments: java.util.List[_] forSome {
<synthetic> type _ <: org.apache.beam.sdk.coders.Coder[_] forSome {
<synthetic> type _
}
} = 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
我相信这来自 here 但不完全理解编译器试图告诉我什么?
您实例化 class 的方式是错误的:
${tpe.typeConstructor}(...)
应该是
new $tpe(...)
或者如果你想用 case class 伴生对象的 apply
而不是普通构造函数来做到这一点:
${tpe.typeSymbol.companion}(...)
注意:类型构造函数(也称为更高级类型)与class构造函数[=无关14=]