Scala 宏生成的依赖类型似乎“不起作用" [英] Dependent type seems to “not work” when generated by Scala macro

查看:42
本文介绍了Scala 宏生成的依赖类型似乎“不起作用"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为handwavey 标题道歉.我不完全确定如何简洁地表达这个问题,因为我以前从未遇到过这样的事情.

Apologies for the handwavey title. I’m not entirely sure how to phrase the question succinctly, since I’ve never encountered something like this before.


我有以下特征,其中 U 类型用于保存 无形可扩展记录 类型:

I have the following trait, where the type U is meant to hold a Shapeless extensible record type:

trait Flattened[T] {
  type U <: shapeless.HList
  def fields: U
}

我正在使用黑盒宏(出于本问题范围之外的原因)来创建 trait 的新实例:

I’m using a blackbox macro (for reasons outside the scope of this question) to create new instances of the trait:

object flatten {

  import scala.language.experimental.macros
  import scala.reflect.macros.blackbox.Context

  def apply[T](struct: T): Flattened[T] =
    macro impl[T]

  def impl[T : c.WeakTypeTag](c: Context)(struct: c.Tree): c.Tree = {
    import c.universe._

    // AST representing a Shapeless extensible record (actual
    // implementation uses values in `struct` to construct this
    // AST, but I've simplified it for this example).
    val fields: Tree = q"""("a" ->> 1) :: ("b" ->> "two") :: ("c" ->> true) :: HNil"""

    // Result type of the above AST
    val tpe: TypeTree = TypeTree(c.typecheck(q"$fields").tpe)

    q"""
    new Flattened[${weakTypeOf[T]}] {
      import shapeless._
      import syntax.singleton._
      import record._

      type U = $tpe
      val fields = $fields
    }
    """
  }
}


问题是,当我使用这个宏创建一个新的 Flattened 实例时,fields 的类型不再是一个可扩展的记录:

The problem is, when I use this macro to create a new instance of Flattened, the type of fields is no longer an extensible record:

import shapeless._
import syntax.singleton._
import record._

val s = "some value... it doesn't matter for this example, since it isn't used. I'm just putting something here so the example compiles and runs in a REPL."
val t = flatten(s)

val fields = t.fields
// fields: t.U = 1 :: "two" :: true :: HNil

fields("a")  // compile error!

// The compile error is:
//     cmd5.sc:1: No field String("a") in record ammonite.$sess.cmd4.t.U
//     val res5 = fields("a")
//                      ^
//     Compilation Failed


奇怪的是,如果我手动执行宏所做的工作,它会起作用:

Oddly, if I do by hand what the macro does, it works:

// I can't actually instantiate a new `Flattened[_]` manually, since
// defining the type `U` would be convoluted (not even sure it can be
// done), so an object with the same field is the next best thing.
object Foo {
  import shapeless._
  import syntax.singleton._
  import record._

  val fields = ("a" ->> 1) :: ("b" ->> "two") :: ("c" ->> true) :: HNil
}

val a = Foo.fields("a")
// a: Int = 1

val b = Foo.fields("b")
// b: String = "two"

val c = Foo.fields("c")
// c: Boolean = true

为什么会出现这种差异,我如何才能使宏版本与手动版本的行为相同?

推荐答案

您的宏可能没有问题.这是类型签名:

Nothing's wrong with your macro, probably. It's the type signatures:

def apply[T](struct: T): Flattened[T] = macro impl[T]
def impl[T : c.WeakTypeTag](c: Context)(struct: c.Tree): c.Tree

您正在使用黑盒宏,并且根据 文档,黑盒宏对其签名是真实的.也就是说,即使 impl 产生一个 Flattened[T] { type U = ... },它是一个黑盒宏的事实意味着 scalac 总是把它包裹在 (_: Flattened[T]) 中,忘记了细化中 U 的定义.使其成为白盒宏:

You are using a blackbox macro, and, according to the documentation, blackbox macros are true to their signatures. That is, even though impl produces a Flattened[T] { type U = ... }, the fact that it is a blackbox macro means that scalac always wraps it in (_: Flattened[T]), forgetting the definition of U in the refinement. Make it a whitebox macro:

// import scala.reflect.macros.blackbox.context // NO!
import scala.reflect.macros.whitebox.context
def impl[T: c.WeakTypeTag](c: Context)(struct: c.Tree): c.Tree

这篇关于Scala 宏生成的依赖类型似乎“不起作用"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆