For a DSL, I want to introduce a dup
extension method that basically calls Vector.fill
, e.g.
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)
Now I want to make the argument a by-name value, so that the following would work correctly:
math.random dup 4 // wrong: four times the same value
I am looking at this question, so apparently there is no solution with a plain value class, only:
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
...undoing the advantage of a value-class that there is no boxing involved.
So I was wondering, would it be possible to write a macro that provides a no-instantiation solution where the argument is by-name?
Why not?
// 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)
}
Macro impl:
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)"
}
}
The c.prefix
can be assumed to contain a tree for the in
argument wrapped in an implicit conversion (we can add some validation code and emit compilation error if it's not). We simply unwrap it and obtain the original tree that represents in
. We then pass it directly to Dup.dup
completely discarding implicit conversion in the finally generated code.
The only instantition left will be instantiation of Function0
object that will be passed in place of by-name parameter, but this is unavoidable.