scala - 寻找第二个匹配隐式

考虑以下设置:

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 中起到间接作用的)。

我已经尝试 =:!= from shapeless 但无济于事:

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
       ^

最佳答案

肮脏的 hack 是将宏上下文向下转换为它的实现并使用编译器内部结构。

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

后者在 2.13.10 中仍然有效。

Scala 3 实现:

import scala.quoted.{Quotes, Type, Expr, quotes}
import dotty.tools.dotc.typer.{Implicits => dottyImplicits}

transparent inline def materializeSecondFoo: Foo[_] = ${impl}

def impl(using Quotes): Expr[Foo[_]] = {
  import quotes.reflect.*

  given c: dotty.tools.dotc.core.Contexts.Context =
    quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx

  val typer = c.typer

  val search = new typer.ImplicitSearch(
    TypeRepr.of[Foo[_]].asInstanceOf[dotty.tools.dotc.core.Types.Type],
    dotty.tools.dotc.ast.tpd.EmptyTree,
    Position.ofMacroExpansion.asInstanceOf[dotty.tools.dotc.util.SourcePosition].span
  )

  def eligible(contextual: Boolean): List[dottyImplicits.Candidate] =
    if contextual then
      if c.gadt.isNarrowing then
        dotty.tools.dotc.core.Contexts.withoutMode(dotty.tools.dotc.core.Mode.ImplicitsEnabled) {
          c.implicits.uncachedEligible(search.wildProto)
        }
      else c.implicits.eligible(search.wildProto)
    else search.implicitScope(search.wildProto).eligible


  def implicits(contextual: Boolean): List[dottyImplicits.SearchResult] =
    eligible(contextual).map(search.tryImplicit(_, contextual))

  val contextualImplicits = implicits(true)
  val nonContextualImplicits = implicits(false)
  val contextualSymbols = contextualImplicits.map(_.tree.symbol)
  val filteredNonContextual = nonContextualImplicits.filterNot(sr => contextualSymbols.contains(sr.tree.symbol))

  val successes = (contextualImplicits ++ filteredNonContextual).collect {
    case success: dottyImplicits.SearchSuccess => success.tree.asInstanceOf[ImplicitSearchSuccess].tree
  }

  successes.tail.head.asExprOf[Foo[_]]
}
materializeSecondFoo.say // bool
val foo = materializeSecondFoo
foo: Foo[Boolean] // compiles

Scala 3.2.0。

https://stackoverflow.com/questions/56777174/

相关文章:

google-chrome - Chrome 开发工具 : memory heap snapshot

python-3.x - 如何访问当前正在执行的模块?

python - 命令提示符中的彩色文本在 "cls"之后但不在 "cls"之前

c++ - ArrayFire GFOR 循环意外错误

c# - 使用具有多个证书和私钥的 PEM 文件

git - 列出与完全 merge 的分支的提交差异时如何忽略精心挑选的提交

android - 为什么 PlacesClient.fetchPlace Task 永远不会触发回

python - 如何修复将文件上传到 Google Cloud Storage 时的内存泄漏?

python - 如何删除 seaborn 的 lmplot 函数中的图例?

apache-kafka - 无法从客户端 session ID 读取其他数据