允许按名称参数的隐式 class 的宏变体

Macro variant of implicit class that allows for by-name argument

对于DSL,我想介绍一个dup扩展方法,基本上调用Vector.fill,例如

import scala.collection.immutable.{IndexedSeq => Vec}

implicit final class Dup[A](private val in: A) extends AnyVal {
  def dup(n: Int): Vec[A] = Vector.fill(n)(in)
}

3 dup 4  // Vector(3, 3, 3, 3)

现在我想将参数设为别名值,以便以下内容能够正常工作:

math.random dup 4  // wrong: four times the same value

我正在查看 this question,所以显然没有具有普通值 class 的解决方案,只有:

final class Dup[A](in: () => A) {
  def dup(n: Int): Vec[A] = Vector.fill(n)(in())
}
implicit def Dup[A](in: => A): Dup[A] = new Dup(() => in)

math.random dup 4   // ok

...取消值的优点 -class 不涉及装箱。

所以我想知道,是否可以编写一个宏来提供非实例化解决方案,其中参数是按名称命名的?

为什么不呢?

// Doesn't matter if it's value class or not, code generated by macro
// will contain no references to it.
implicit final class Dup[A](in: A) {
  def dup(n: Int): Vec[A] = macro Macros.dupImpl[A]
}
object Dup {
  def dup[A](in: => A, n: Int) = Vector.fill(n)(in)
}

宏实现:

import scala.reflect.macros.blackbox

object Macros {
  def dupImpl[A](c: blackbox.Context)(n: c.Expr[Int]): c.Tree = {
    import c.universe._
    val q"$conv($in)" = c.prefix.tree
    q"Dup.dup($in, $n)"
  }
}

可以假定 c.prefix 包含包含在隐式转换中的 in 参数的树(如果不是,我们可以添加一些验证代码并发出编译错误)。我们简单地展开它并获得表示 in 的原始树。然后我们直接将它传递给 Dup.dup 在最终生成的代码中完全丢弃隐式转换。

剩下的唯一实例化将是 Function0 对象的实例化,该对象将代替按名称参数传递,但这是不可避免的。