Нахождение второго соответствия неявного - PullRequest
2 голосов
/ 26 июня 2019

Рассмотрим следующую настройку:

trait Foo[A]
object Foo extends Priority2

trait Priority0 {
   implicit def foo1: Foo[Int] = new Foo[Int] {}
}
trait Priority1 extends Priority0 {
   implicit def foo2: Foo[Boolean] = new Foo[Boolean] {}
}
trait Priority2 extends Priority1 {
   implicit def foo3: Foo[Double] = new Foo[Double] {}
}

Теперь, в REPL (загрузив код выше), я могу сделать следующее:

scala> def implicitlyFoo[A](implicit foo: Foo[A]) = foo
implicitlyFoo: [A](implicit foo: Foo[A])Foo[A]

scala> implicitlyFoo
res1: Foo[Double] = Priority2$$anon$3@79703b86

Есть ли способ кодировать с помощью некоторой магии уровня типа, которую я хочу пропустить через экземпляры с помощью A =:= Double, но все же позволить выводу типа выяснить, что такое A?

Я не хочу, чтобы тень foo3. Это MVCE: в моем реальном случае foo3 - это def с другими неявными аргументами (и может играть косвенную роль в получении других Foo).

Я пробовал =:!= из бесформенного, но безрезультатно:

scala> import shapeless._
import shapeless._

scala> def implicitlyFoo2[A](implicit foo: Foo[A], ev: A =:!= Double) = foo
implicitlyFoo2: [A](implicit foo: Foo[A], implicit ev: A =:!= Double)Foo[A]

scala> implicitlyFoo2
<console>:16: error: ambiguous implicit values:
 both method neqAmbig1 in package shapeless of type [A]=> A =:!= A
 and method neqAmbig2 in package shapeless of type [A]=> A =:!= A
 match expected type Double =:!= Double
       implicitlyFoo2
       ^

1 Ответ

0 голосов
/ 27 июня 2019

Грязный взлом состоит в том, чтобы понизить контекст макроса до его реализации и использовать внутренние компоненты компилятора.

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

  trait Foo[A] {
    def say: String
  }

  trait Priority0 {
    implicit def foo1: Foo[Int] = new Foo[Int] {
      override def say: String = "int"
    }
  }

  trait Priority1 extends Priority0 {
    implicit def foo2: Foo[Boolean] = new Foo[Boolean] {
      override def say: String = "bool"
    }
  }

  trait Priority2 extends Priority1 {
    implicit def foo3: Foo[Double] = new Foo[Double] {
      override def say: String = "double"
    }
  }

  object Foo extends Priority2


  def materializeSecondFoo[A]: Foo[A] = macro impl

  def impl(c: whitebox.Context): c.Tree = {
    import c.universe._

    val context = c.asInstanceOf[reflect.macros.runtime.Context]
    val global: context.universe.type = context.universe
    val analyzer: global.analyzer.type = global.analyzer

    var infos = List[analyzer.ImplicitInfo]()

    new analyzer.ImplicitSearch(
      tree = EmptyTree.asInstanceOf[global.Tree],
      pt = typeOf[Foo[_]].asInstanceOf[global.Type],
      isView = false,
      context0 = global.typer.context.makeImplicit(reportAmbiguousErrors = false),
      pos0 = c.enclosingPosition.asInstanceOf[global.Position]
    ) {
      override def searchImplicit(
                                   implicitInfoss: List[List[analyzer.ImplicitInfo]],
                                   isLocalToCallsite: Boolean
                                 ): analyzer.SearchResult = {
        val implicitInfos = implicitInfoss.flatten
        if (implicitInfos.nonEmpty) {
          infos = implicitInfos
        }
        super.searchImplicit(implicitInfoss, isLocalToCallsite)
      }
    }.bestImplicit

    val secondBest = infos.tail.head

    global.gen.mkAttributedRef(secondBest.pre, secondBest.sym).asInstanceOf[Tree]
  }

  materializeSecondFoo.say // bool

Протестировано в 2.12.8.По мотивам shapeless.Cached.


В 2.13.0 materializeSecondFoo.say следует заменить на

val m = materializeSecondFoo
m.say
...