In scala 2, can macro or any language feature be used to rewrite the abstract type reification mechanism in all subclasses? How about scala 3?
Asked Answered
R

1

2

It is known in scala 2 that macros are strictly local and are only executed once, when the class is defined. This feature seems particularly weak when combining with abstract type, as the process to convert abstract types into concrete one generally bypasses macro and uses its own primitive rules.

One simple example that demonstrates a counter-intuitive outcome is in the following test code:

  trait BB {

    def ttag = implicitly[TypeTag[this.type]]
  }

  case class AA() extends BB

  it("can TypeTag") {

    val kk = AA()

    TypeViz(kk.ttag).peek // this function visualise the full type tree of the type tag
  }

if executed, the type of kk turns out to be:

-+ BB.this.type
 !-+ InfoCTSpec.this.BB
   !-+ Object
     !-- Any

Oops, the type AA is completely ignored, because implicitly[TypeTag[this.type]] is backed by a built-in macro implicit, which is only executed ONCE when BB is defined, not when AA is defined and reify the actual kk.this.type. I find it quite unwieldy and prone to cause some other features (e.g. pattern matching, type lambda) to degrade due to runtime type erasure.

I'd like to write/use a language extension to, e.g. make TypeTag[this.type] a subtype of AA, WITHOUT introducing runtime overhead & out-of-scope context objects (so, NO IMPLICIT). How can I do this with the least amount of hacking? I'm open to very hardcore solutions like compiler extension and macro, but an elegant work-around that can be smoothly carried over to scala 3/dotty is obviously preferred.

P.S. it appears that the dotty "inline/compiletime" feature has partially implemented what I have envisioned. Is this the correct impression?

Ratcliff answered 25/4, 2021 at 21:1 Comment(5)
What is the meta problem you want to solve?Hessite
Oh it is simple: I was using singleton-ops (similar to compiletime ops in dotty), then I found that unlike dotty, it keeps all the type constructors, which are full of complex type aliases, so the end type after ops exceeds the 65535 byte limitation of java stringRatcliff
The "How about Scala 3?" part could maybe be a new question, since the same approach likely won't work for both Scala 2 and Scala 3.Eal
yeah they should have entire different mechanisms. I only mention scala 3 to see if it is trivialRatcliff
@Ratcliff Where to get the code of TypeViz.peek?Clipclop
C
0

You are free to write macros and compiler plugins but the conventional mechanism to postpone implicit resolution from definition site to call site is to replace implicitly with an implicit parameter

trait BB {
  def ttag(implicit tt: TypeTag[this.type]) = tt
}

When doing implicit resolution with type parameters, why does val placement matter?

WITHOUT introducing runtime overhead & out-of-scope context objects (so, NO IMPLICIT).

It's not clear why you want to avoid implicits. Implicits are resolved at compile time. If you replace this with macros or compiler plugins then anyway you'll kind of resolve implicits manually at compile time.


Alternatively you can postpone implicit resolution with a macro (this is like inlining in Scala 3)

import scala.language.experimental.macros
import scala.reflect.macros.whitebox

trait BB {
  def ttag: Any = macro Macros.ttagImpl
}

object Macros {
  def ttagImpl(c: whitebox.Context): c.Tree = {
    import c.universe._
    q"_root_.scala.Predef.implicitly[_root_.scala.reflect.runtime.universe.TypeTag[this.type]]"
  }
}

or

import scala.reflect.runtime.{universe => ru}

object Macros {
  def ttagImpl(c: whitebox.Context): c.Tree = {
    import c.universe._
    c.inferImplicitValue(appliedType(typeOf[ru.TypeTag[_]].typeConstructor, internal.thisType(c.internal.enclosingOwner.owner/*c.macroApplication.symbol*/)), silent = false)
  }
}

Implicit Json Formatter for value classes in Scala

Clipclop answered 20/6, 2021 at 13:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.