最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

scala - How to match sub-expression of type Array[Int]? - Stack Overflow

programmeradmin2浏览0评论

I'm learning Scala multi-stage metaprogramming. I'm currently working on an exercise that asks me to implement the dot-product (sum-of-product) between two vectors of the same length.

Writing the code, I need to match at compile-time Arrays and check if their length equals, before proceeding with the dot product. Otherwise, an exception is thrown. I'm using a FromExpr implicit object to specify the extraction by implementing its unapply method. And, this is what I came up with

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

The problem is, I get a None as a result in the enclosing match-case statement. Which is,

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 }
  }
}

This last snippet is not completed yet, but I'm trying to get past this wrong behaviour before continuing. I do get a correct behaviour for the case of empty Array (defined as val arr = Array[Int]()).

The test that allows me to asses the misbehaviour of the FromExpr implementation is

"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)
  }
}

which results in

99 was not equal to 1

While a RuntimeException is expected.

Any help or advice?

I'm learning Scala multi-stage metaprogramming. I'm currently working on an exercise that asks me to implement the dot-product (sum-of-product) between two vectors of the same length.

Writing the code, I need to match at compile-time Arrays and check if their length equals, before proceeding with the dot product. Otherwise, an exception is thrown. I'm using a FromExpr implicit object to specify the extraction by implementing its unapply method. And, this is what I came up with

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

The problem is, I get a None as a result in the enclosing match-case statement. Which is,

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 }
  }
}

This last snippet is not completed yet, but I'm trying to get past this wrong behaviour before continuing. I do get a correct behaviour for the case of empty Array (defined as val arr = Array[Int]()).

The test that allows me to asses the misbehaviour of the FromExpr implementation is

"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)
  }
}

which results in

99 was not equal to 1

While a RuntimeException is expected.

Any help or advice?

Share Improve this question edited Mar 1 at 9:27 Mark Rotteveel 109k229 gold badges156 silver badges220 bronze badges asked Dec 17, 2024 at 14:55 WujWuj 134 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

Your Expr[Array[Int]] values are not what you would get in runtime - it's AST. In this case - dot(v1, v2) - for the first argument you wouldn't get

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"
            )
          )
        )
      )
    )
  )
)

but

v1
// actually, it would be this tree:
Ident("v1")

So the information how the runtime value is constructed was already discarded.

And for a good reason. Your code could be modified to contain:

v1(0) = 10

which would change the value if v1 in runtime (IArray would not have this issue) - what then should be matched against in macro? Original value? Somehow computed new value?

The answer is none of that - you are parsing exactly the expression that was passed into the macro, and if it isn't a tree containing all the data that you need, you are not supporting it.

If you are brave, you can experiment with -Yretain-trees and obtaining the Tree associated with the Symbol, to see how some identifier was defined... but that's going to be error prone at best.

My suggestion would be:

  • support Array only for values directly constructed as arguments
  • support IArray for both values directly constructed as arguments, as well as with source accessible to the macro via their Symbol (IArray is immutable, so there shouldn't be a risk of getting different values from the val in compile time than in the runtime)
  • support everything else with a runtime computation, as there is no way to compute dot product in compile time of something that cannot be guaranteed to have the known shape
发布评论

评论列表(0)

  1. 暂无评论