Scala 3 - 元编程 - 如何匹配 Array[Int] 类型的子表达式?

问题描述 投票:0回答:1

我正在学习 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

有什么帮助或建议吗?

metaprogramming scala-3
1个回答
0
投票

您的

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
  • 通过运行时计算支持其他所有内容,因为无法在无法保证具有已知形状的东西的编译时计算点积
© www.soinside.com 2019 - 2024. All rights reserved.