我正在学习 Scala 多阶段元编程。
我目前正在做一项练习,要求我实现两个相同长度向量之间的点积(积和)。
编写代码时,我需要在编译时匹配
Array
并检查它们的长度是否相等,然后再继续进行点积。否则,会抛出异常。
我使用 FromExpr 隐式对象通过实现其
unapply
方法来指定提取。而且,这就是我想出的
given ArrayFromExpr: FromExpr[Array[Int]] with
def unapply(x: Expr[Array[Int]])(using Quotes): Option[Array[Int]] =
x match
case '{ Array[Int]()(using $ct1) } => Some(Array[Int]())
case '{ Array[Int]((${Varargs(Exprs(elements))})*) } => Some(Array(elements*))
case _ => None
问题是,我在封闭的 match-case 语句中得到 None 作为结果。这是,
def dotImpl(v1: Expr[Array[Int]], v2: Expr[Array[Int]])(using q: Quotes): Expr[Int] = {
(v1.value, v2.value) match {
case (Some(arr1), Some(arr2)) if arr1.length != arr2.length => throw RuntimeException("Cannot compute dot product of arrays having different lengths.")
case (Some(arr1), Some(arr2)) if arr1.length == 0 => '{ 0 }
case (Some(arr1), Some(arr2)) => computeDotProduct(arr1, arr2)
case (None, None) => '{ 99 }
case _ => '{ 999 }
}
}
最后一个片段尚未完成,但我试图在继续之前克服这种错误的行为。 对于空数组(定义为
val arr = Array[Int]()
)的情况,我确实得到了正确的行为。
允许我评估
FromExpr
实施的不当行为的测试是
"dot product for different length arrays" should "throw" in {
val v1 = Array[Int](2, 1)
val v2 = Array[Int](1, 2, 3)
dot(v1, v2) shouldBe 1 // this is for a better test output
a [RuntimeException] should be thrownBy {
dot(v1, v2)
}
}
导致
99 was not equal to 1
虽然预计会出现
RuntimeException
。
有什么帮助或建议吗?
您的
Expr[Array[Int]]
值不是您在运行时得到的值 - 它是 AST。在这种情况下 - dot(v1, v2)
- 对于第一个参数,你不会得到
Array[Int](1, 2, 3)
// actually, it would be this tree:
Apply(
Apply(
TypeApply(Select(Ident("Array"), "apply"), List(TypeIdent("Int"))),
List(
Typed(Repeated(
List(
Literal(IntConstant(1)),
Literal(IntConstant(2)),
Literal(IntConstant(3))
),
Inferred()
), Inferred() )
)
),
List(
Apply(
TypeApply(Select(Ident("ClassTag"), "apply"), List(Inferred())),
List(
Literal(
ClassOfConstant(
TypeRef(
TermRef(ThisType(TypeRef(NoPrefix(), "<root>")), "scala"),
"Int"
)
)
)
)
)
)
)
但是
v1
// actually, it would be this tree:
Ident("v1")
因此,如何构造运行时值的信息已经被丢弃。
这是一个很好的理由。您的代码可以修改为包含:
v1(0) = 10
如果
v1
在运行时会改变值 - 那么在宏中应该匹配什么?原值?以某种方式计算出新值?
答案都不是 - 您正在准确解析传递到宏中的表达式,如果它不是包含您需要的所有数据的树,则您不支持它。
如果您足够勇敢,您可以尝试使用
-Yretain-trees
并获取与 Tree
关联的 Symbol
,以了解某些标识符是如何定义的......但这充其量也很容易出错。
我的建议是:
Array
直接构造为参数的值IArray
直接构造为参数的两个值,以及可通过宏访问的源 Symbol