【问题标题】:How to distinguish compiler-inferred implicit conversion from explicitly invoked one?如何区分编译器推断的隐式转换和显式调用的转换?
【发布时间】:2013-03-19 19:26:35
【问题描述】:

让我们想象一下将这两个等价的表达式传递给一个 Scala 宏:

  • 带有编译器推断的隐式转换:1+"foo"
  • 显式调用隐式转换:any2stringadd(1)+"foo"

有没有办法在宏里面区分这两者?

【问题讨论】:

    标签: scala implicit-conversion scala-2.10 scala-macros


    【解决方案1】:

    首先,1 + "foo" 的情况会很棘手,因为实际上并没有发生任何隐式转换:Int 本身 really, truly does have this + method (unfortunately)。

    因此,如果这是您的用例,那么您就不走运了,但是可以做您所描述的更普遍的事情。我将在下面的示例中假设以下设置:

    case class Foo(i: Int)
    case class Bar(s: String)
    implicit def foo2bar(foo: Foo) = Bar(foo.i.toString)
    

    首先是优雅的方法:

    object ConversionDetector {
      import scala.language.experimental.macros
      import scala.reflect.macros.Context
    
      def sniff[A](tree: _): Boolean = macro sniff_impl[A]
      def sniff_impl[A: c.WeakTypeTag](c: Context)(tree: c.Tree) = {
        // First we confirm that the code typechecks at all:
        c.typeCheck(tree, c.universe.weakTypeOf[A])
    
        // Now we try it without views:
        c.literal(
          c.typeCheck(tree, c.universe.weakTypeOf[A], true, true, false).isEmpty
        )
      }
    }
    

    根据需要工作:

    scala> ConversionDetector.sniff[Bar](Foo(42))
    res1: Boolean = true
    
    scala> ConversionDetector.sniff[Bar](foo2bar(Foo(42)))
    res2: Boolean = false
    

    不幸的是,这需要无类型的宏,目前仅在 Macro Paradise 中可用。

    你可以在 2.10 中使用普通的旧 def 宏来获得你想要的东西,但这有点小技巧:

    object ConversionDetector {
      import scala.language.experimental.macros
      import scala.reflect.macros.Context
    
      def sniff[A](a: A) = macro sniff_impl[A]
      def sniff_impl[A: c.WeakTypeTag](c: Context)(a: c.Expr[A]) = {
        import c.universe._
    
        c.literal(
          a.tree.exists {
            case app @ Apply(fun, _) => app.pos.column == fun.pos.column
            case _ => false
          }
        )
      }
    }
    

    再说一遍:

    scala> ConversionDetector.sniff[Bar](Foo(42))
    res1: Boolean = true
    
    scala> ConversionDetector.sniff[Bar](foo2bar(Foo(42)))
    res2: Boolean = false
    

    诀窍是在我们的抽象语法树中寻找我们看到函数应用的地方,然后检查Apply节点和它的fun子节点的位置是否在同一列,这表明该方法call 未明确存在于源中。

    【讨论】:

    • 感谢您的回答。我不知道Int 有一个接受String 的方法+,但无论如何我的意思是一般情况。我也在考虑使用Positions,但这确实闻起来有点“hacky”。我认为如果Trees 有一些标志会说“这棵树是由编译器自动推断的”,那会很酷。
    • 我刚刚在scala-reflect 来源中发现了一个comment,它谈到了Apply AST 上的一些潜在标志,这些标志将指示隐式转换。看起来现在有一个单独的类来表明这一点(不幸的是,它是内部的)。
    • 不幸的是,使用 Position 技巧将意味着您的代码在 REPL 中使用时会崩溃。 @ghik,我认为您的最后一条评论走在了正确的轨道上,@EugeneBurmako 的回答对此进行了详细说明。
    【解决方案2】:

    这是一个技巧,但它可能会对您有所帮助:

    import scala.reflect.macros.Context
    import language.experimental.macros
    
    object Macros {
      def impl(c: Context)(x: c.Expr[Int]) = {
        import c.universe._
        val hasInferredImplicitArgs = x.tree.isInstanceOf[scala.reflect.internal.Trees#ApplyToImplicitArgs]
        val isAnImplicitConversion = x.tree.isInstanceOf[scala.reflect.internal.Trees#ApplyImplicitView]
        println(s"x = ${x.tree}, args = $hasInferredImplicitArgs, view = $isAnImplicitConversion")
        c.literalUnit
      }
    
      def foo(x: Int) = macro impl
    }
    
    import language.implicitConversions
    import scala.reflect.ClassTag
    
    object Test extends App {
      def bar[T: ClassTag](x: T) = x
      implicit def foo(x: String): Int = augmentString(x).toInt
      Macros.foo(2)
      Macros.foo(bar(2))
      Macros.foo("2")
    }
    
    08:30 ~/Projects/210x/sandbox (2.10.x)$ ss
    x = 2, args = false, view = false
    x = Test.this.bar[Int](2)(ClassTag.Int), args = true, view = false
    x = Test.this.foo("2"), args = false, view = true
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-04
      • 1970-01-01
      • 2014-11-15
      • 1970-01-01
      相关资源
      最近更新 更多