如何召唤“给定”成员?

And*_*kin 5 scala implicit initialization-order scala-3 given

假设我有一些类型类

trait FooBar[X]
Run Code Online (Sandbox Code Playgroud)

和一个实例FooBar[Int]

given intIsFooBar: FooBar[Int] = new FooBar {}
Run Code Online (Sandbox Code Playgroud)

现在,假设我有一个Intf具有某种成员类型的接口A,并且还保证有一个given FooBar[A]

trait Intf:
  type A
  given aIsFoobar: FooBar[A]
Run Code Online (Sandbox Code Playgroud)

现在,我有了类型Int,也有一个FooBar[Int],但是我如何实际实现这个接口呢Int

如果我尝试

class IntImpl() extends Intf:
  type A = Int
  given aIsFoobar: FooBar[A] = summon
Run Code Online (Sandbox Code Playgroud)

然后我收到“函数体 IntImpl.aIsFoobar 中的无限循环”错误,因为summon似乎看到的是aIsFoobar而不是intIsFooBar

如果我尝试使用summon某些辅助辅助变量中的实例,如下所示:

class IntImpl() extends Intf:
  type A = Int
  private final val _aIsFoobar: FooBar[A] = summon
  given aIsFoobar: FooBar[A] = _aIsFoobar
Run Code Online (Sandbox Code Playgroud)

然后我遇到了初始化顺序问题:aIsFoobar结果是null,我的应用程序崩溃了NullPointerExceptions,这有点荒谬。

我也尝试过export,但这些都不起作用:

  export FooBar[Int] as aIsFoobar // doesn't compile, invalid syntax
Run Code Online (Sandbox Code Playgroud)

如何使“规范”FooBar[Int]作为aIsFoobar给定成员可用?


完整代码:

trait FooBar[X]

given intIsFooBar: FooBar[Int] = new FooBar {}

trait Intf:
  type A
  given aIsFoobar: FooBar[A]

object IntImpl extends Intf:
  type A = Int
  given aIsFoobar: FooBar[A] = summon
Run Code Online (Sandbox Code Playgroud)

Dmy*_*tin 3

在 Scala 2 中,您可以使用按名称隐藏隐式的技巧

// Scala 2

trait FooBar[X] {
  def value: String
}
object FooBar {
  implicit val intIsFooBar: FooBar[Int] = new FooBar[Int] {
    override val value: String = "a"
  }
}

trait Intf {
  type A
  implicit def aIsFoobar: FooBar[A]
}

object IntImpl extends Intf {
  override type A = Int

  override implicit val aIsFoobar: FooBar[A] = {
    lazy val aIsFoobar = ???
    implicitly[FooBar[A]]
  }
}

println(IntImpl.aIsFoobar.value) // a
Run Code Online (Sandbox Code Playgroud)

隐式解析时出现 NullPointerException

在 Scala 3 中,使用擦除类型的模式匹配的规范方法是什么?

Scala 中有此格式参数的解决方法吗?

使用需要隐式成员的特征扩展对象

构造一个可重写的隐式答案


在 Scala 3 中这个技巧不再起作用了。

在 Scala 3 中,您可以尝试使该方法内联并使用,scala.compiletime.summonInline而不是普通的summon

// Scala 3

trait FooBar[X]:
  def value: String
object FooBar:
  given intIsFooBar: FooBar[Int] = new FooBar[Int]:
    override val value: String = "a"

trait Intf:
  type A
  /*inline*/ given aIsFoobar: FooBar[A]

object IntImpl extends Intf:
  override type A = Int
  override inline given aIsFoobar: FooBar[A] = summonInline[FooBar[A]]

println(IntImpl.aIsFoobar.value) // a
Run Code Online (Sandbox Code Playgroud)

重写内联方法:https://docs.scala-lang.org/scala3/reference/metaprogramming/inline.html#rules-for-overriding


请注意,通过内联,我们修改了方法语义。隐式在调用站点而不是定义站点解析

// Scala 2

trait FooBar[X] {
  def value: String
}
object FooBar {
  implicit val intIsFooBar: FooBar[Int] = new FooBar[Int] {
    override val value: String = "a"
  }
}

trait Intf {
  type A
  implicit def aIsFoobar: FooBar[A]
}

object IntImpl extends Intf {
  override type A = Int

  override implicit val aIsFoobar: FooBar[A] = {
    lazy val aIsFoobar = ???
    implicitly[FooBar[A]]
  }
}

{
  implicit val anotherIntFooBar: FooBar[Int] = new FooBar[Int] {
    override val value: String = "b"
  }

  println(IntImpl.aIsFoobar.value) // a
}
Run Code Online (Sandbox Code Playgroud)
// Scala 3

trait FooBar[X]:
  def value: String
object FooBar:
  given intIsFooBar: FooBar[Int] = new FooBar[Int]:
    override val value: String = "a"

trait Intf:
  type A
  /*inline*/ given aIsFoobar: FooBar[A]

object IntImpl extends Intf:
  override type A = Int
  override inline given aIsFoobar: FooBar[A] = summonInline[FooBar[A]]

{
  given anotherIntFooBar: FooBar[Int] = new FooBar[Int]:
    override val value: String = "b"

  println(IntImpl.aIsFoobar.value) // b
}
Run Code Online (Sandbox Code Playgroud)

implicitly关于与 的区别implicit

当使用类型参数进行隐式解析时,为什么 val 放置很重要?

为什么Scala编译器可以提供隐式的out of object,但不能提供inside?回答

根据类型类设置抽象类型

Scala 中的 SYB `cast` 函数

在scala 2中,是否可以使用宏或任何语言特性来重写所有子类中的抽象类型具体化机制?斯卡拉 3 怎么样?

在Scala 2.13中,为什么可以为抽象类型调用不合格的TypeTag?


在 Scala 2 中,内联可以通过Scala 2 宏来实现。

Scala 中值类的隐式 Json 格式化程序


https://docs.scala-lang.org/scala3/reference/contextual/relationship-implicits.html#abstract-implicits中写道

Scala 2 中的抽象隐式 val 或 def 可以在 Scala 3 中使用常规抽象定义和给定的别名来表达。例如,Scala 2

implicit def symDecorator: SymDecorator
Run Code Online (Sandbox Code Playgroud)

在 Scala 3 中可以表示为

def symDecorator: SymDecorator
given SymDecorator = symDecorator
Run Code Online (Sandbox Code Playgroud)

您可以询问如何在 Scala 3 中重写隐式而不更改定义站点语义。可能只是手动解决隐式问题而不是使用summon

// Scala 3

trait FooBar[X]:
  def value: String
object FooBar:
  given intIsFooBar: FooBar[Int] = new FooBar[Int]:
    override val value: String = "a"

trait Intf:
  type A
  def aIsFoobar: FooBar[A]
  given FooBar[A] = aIsFoobar

object IntImpl extends Intf:
  override type A = Int
  override val aIsFoobar: FooBar[A] = FooBar.intIsFooBar

{
  given anotherIntFooBar: FooBar[Int] = new FooBar[Int]:
    override val value: String = "b"

  println(IntImpl.aIsFoobar.value) // a
}
Run Code Online (Sandbox Code Playgroud)

更通用但不太传统的解决方案是使用Scala 3 宏+ 编译器内部结构

// Scala 3.2.1

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

transparent inline def summonSecondBest[A]: A = ${summonSecondBestImpl[A]}

def summonSecondBestImpl[A: Type](using Quotes): Expr[A] =
  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[A].asInstanceOf[DottyType],
    dotty.tools.dotc.ast.tpd.EmptyTree,
    Position.ofMacroExpansion.asInstanceOf[dotty.tools.dotc.util.SourcePosition].span
  )

  val wildProtoMethod = classOf[typer.ImplicitSearch].getDeclaredField("wildProto")
  wildProtoMethod.setAccessible(true)
  val wildProto = wildProtoMethod.get(search).asInstanceOf[DottyType]

  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(wildProto)
        }
      else c.implicits.eligible(wildProto)
    else search.implicitScope(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[A]
Run Code Online (Sandbox Code Playgroud)
// Scala 3

trait FooBar[X]:
  def value: String
object FooBar:
  given intIsFooBar: FooBar[Int] = new FooBar[Int]:
    override val value: String = "a"

trait Intf:
  type A
  def aIsFoobar: FooBar[A]
  given FooBar[A] = aIsFoobar

object IntImpl extends Intf:
  override type A = Int
  override val aIsFoobar: FooBar[A] = summonSecondBest[FooBar[A]]

{
  given anotherIntFooBar: FooBar[Int] = new FooBar[Int]:
    override val value: String = "b"

  println(IntImpl.aIsFoobar.value) // a
}
Run Code Online (Sandbox Code Playgroud)

查找第二个隐式匹配


或者您可以尝试创建A类型参数而不是类型成员

trait FooBar[X]
object FooBar:
  given FooBar[Int] with {}

trait Intf[A: FooBar]

object IntImpl extends Intf[Int]
Run Code Online (Sandbox Code Playgroud)

https://docs.scala-lang.org/scala3/reference/changed-features/implicit-resolution.html

  1. 现在选择隐式时会考虑嵌套。例如考虑以下场景:

    def f(implicit i: C) =
      def g(implicit j: C) =
        implicitly[C]
    
    Run Code Online (Sandbox Code Playgroud)

现在,这将解决对 的隐式调用j,因为j嵌套得比 更深i。以前,这会导致歧义错误。以前由于隐藏(其中隐式被嵌套定义隐藏)而导致隐式搜索失败的可能性不再适用。


@AndreyTyukin 的解决方案:

trait FooBar[X]:
  def value: String
object FooBar:
  given intIsFooBar: FooBar[Int] = new FooBar[Int]:
    override val value: String = "a"

trait Intf:
  type A
  def aIsFoobar: FooBar[A]
  object implicits:
    given FooBar[A] = aIsFoobar

object IntImpl extends Intf:
  override type A = Int
  override def aIsFoobar: FooBar[A] = summon[FooBar[Int]]

{
  given anotherIntFooBar: FooBar[Int] = new FooBar[Int]:
    override val value: String = "b"

  println(IntImpl.aIsFoobar.value) // a
}

{
  import IntImpl.implicits.given

  println(summon[FooBar[Int]].value) // a
}
Run Code Online (Sandbox Code Playgroud)