Создать двусмысленный скрытый низкий приоритет

Рассмотрим кодек по умолчанию, предлагаемый в io пакет.

implicitly[io.Codec].name  //res0: String = UTF-8

Это неявный "низкий приоритет", поэтому его легко переопределить без двусмысленности.

implicit val betterCodec: io.Codec = io.Codec("US-ASCII")

implicitly[io.Codec].name  //res1: String = US-ASCII

Также легко повысить уровень приоритета.

import io.Codec.fallbackSystemCodec
implicit val betterCodec: io.Codec = io.Codec("US-ASCII")

implicitly[io.Codec].name  //won't compile: ambiguous implicit values

Но можем ли мы пойти в противоположном направлении? Можем ли мы создать неявный низкий уровень, который отключает ("неоднозначно") значение по умолчанию? Я смотрел на уравнение приоритета и играл с последствиями низкого приоритета, но мне еще предстоит создать нечто неоднозначное по умолчанию.

1 ответ

Если я правильно понимаю, вы хотите проверить во время компиляции, что существует локальный неявный io.Codec("более высокий приоритет") или в противном случае вызовет ошибку компиляции. Это можно сделать с помощью макросов (используя внутренности компилятора).

import scala.language.experimental.macros
import scala.reflect.macros.{contexts, whitebox}

object Macros {

  def localImplicitly[A]: A = macro impl[A]

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

    val context = c.asInstanceOf[contexts.Context]
    val global: context.universe.type = context.universe
    val analyzer: global.analyzer.type = global.analyzer
    val callsiteContext = context.callsiteTyper.context

    val tpA = weakTypeOf[A]

    val localImplicit = new analyzer.ImplicitSearch(
      tree = EmptyTree.asInstanceOf[global.Tree],
      pt = tpA.asInstanceOf[global.Type],
      isView = false,
      context0 = callsiteContext.makeImplicit(reportAmbiguousErrors = true),
      pos0 = c.enclosingPosition.asInstanceOf[global.Position]
    ) {
      override def searchImplicit(
                                   implicitInfoss: List[List[analyzer.ImplicitInfo]],
                                   isLocalToCallsite: Boolean
                                 ): analyzer.SearchResult = {
        if (isLocalToCallsite)
          super.searchImplicit(implicitInfoss, isLocalToCallsite)
        else analyzer.SearchFailure
      }
    }.bestImplicit

    if (localImplicit.isSuccess)
      localImplicit.tree.asInstanceOf[c.Tree]
    else c.abort(c.enclosingPosition, s"no local implicit $tpA")
  }
}

localImplicitly[io.Codec].name // doesn't compile
// Error: no local implicit scala.io.Codec

implicit val betterCodec: io.Codec = io.Codec("US-ASCII")
localImplicitly[Codec].name // US-ASCII

import io.Codec.fallbackSystemCodec
localImplicitly[Codec].name // UTF-8

import io.Codec.fallbackSystemCodec
implicit val betterCodec: io.Codec = io.Codec("US-ASCII")
localImplicitly[Codec].name // doesn't compile
//Error: ambiguous implicit values:
// both value betterCodec in object App of type => scala.io.Codec
// and lazy value fallbackSystemCodec in trait LowPriorityCodecImplicits of type => //scala.io.Codec
// match expected type scala.io.Codec

Проверено в 2.13.0.

libraryDependencies ++= Seq(
  scalaOrganization.value % "scala-reflect" % scalaVersion.value,
  scalaOrganization.value % "scala-compiler" % scalaVersion.value
)

Вроде да.

Вы можете сделать это, создав новый тип. Т.е. тип, который является просто прокси для io.Codecи оборачивает экземпляр. Это означает, что вам также нужно изменить все неявные аргументы io.Codec в CodecWrapper, что может быть невозможно.

trait CodecWraper {
  def orphan: io.Codec
}

object CodecWrapper {
  /* because it's in the companion, this will have the highest implicit resolution priority. */
  implicit def defaultInstance: CodecWrapper = 
    new CodecWrapper {
      def orphan = new io.Codec { /* your default implementation here */ }
    }
  }
}

import io.Codec.fallbackSystemCodec
implicitly[CodecWrapper].orphan // io.Codec we defined above - no ambiguity
Другие вопросы по тегам