Существует ли класс типа, который проверяет наличие хотя бы одного неявного типа?

У меня есть черта Foo[T, U] и алгоритм уровня типа, который задает L <: HList и целевой тип U, сообщает мне, существует ли T в L, такое что есть неявное Foo[T, U] в области видимости. Это реализовано с использованием следующего класса типов:

trait Search[L <: HList, U]

object Search {
  def apply[L <: HList, U](implicit s: Search[L, U]): U = null

  ...
}

и у нас есть следующее:

object Test {
  type L = Int :: String :: HNil

  implicit val foo: Foo[String, Boolean] = null

  Search[L, Boolean] //compiles

  Search[L, Double] //does not compile
}

Я бы хотел, чтобы поиск вообще не проводился, если в области нет Foo[T, U] ни для одного T, поскольку тогда мы уже знаем, что алгоритм не завершится. Другими словами, мне нужен класс типа trait Exists[F[_]], экземпляры которого существуют тогда и только тогда, когда в области видимости есть хотя бы один неявный F, поэтому функция Search.apply вместо этого имеет подпись:

def apply[L <: HList, U](implicit ev: Exists[Foo[_, U]], s: Search[L, U]): U = null

В этом случае компилятор будет пытаться разрешить s только в том случае, если в области видимости есть неявное Foo.

Можно ли определить такой тип-класс? Он уже существует?


person jbwheatley    schedule 13.08.2019    source источник


Ответы (2)


Пытаться

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

trait Exists[A]

object Exists {
  implicit def materialize[A]: Exists[A] = macro impl[A]

  def impl[A: c.WeakTypeTag](c: blackbox.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 searchResult = analyzer.inferImplicit(
      tree = EmptyTree.asInstanceOf[global.Tree],
      pt = tpA.asInstanceOf[global.Type],
      reportAmbiguous = false,
      isView = false,
      context = callsiteContext,
      saveAmbiguousDivergent = true,
      pos = c.enclosingPosition.asInstanceOf[global.Position]
    )

    val isAmbiguous = callsiteContext.reporter.firstError match {
      case Some(analyzer.AmbiguousImplicitTypeError(_,_)) => true
      case _ => false
    }

    if (searchResult.isSuccess || searchResult.isAmbiguousFailure || isAmbiguous) 
      q"new Exists[$tpA] {}"
    else c.abort(c.enclosingPosition, s"no implicit $tpA")    
  }
}

Тестовое задание

// no implicit Int
// implicitly[Exists[Int]] // doesn't compile

implicit val i: Int = 1 
implicitly[Exists[Int]] // compiles

implicit val i: Int = 1 
implicit val i1: Int = 2 
implicitly[Exists[Int]] // compiles

Я предполагаю, что оригинальный Search был

trait Foo[U, V]

trait Search[L <: HList, V]

trait LowPrioritySearch {
  implicit def tail[H, T <: HList, V](implicit search: Search[T, V]): Search[H :: T, V] = null
}

object Search extends LowPrioritySearch {
  def apply[L <: HList, U](implicit s: Search[L, U]): U = null.asInstanceOf[U]

  implicit def head[U, T <: HList, V](implicit foo: Foo[U, V]): Search[U :: T, V] = null
}

Теперь с Exists

def apply[L <: HList, U](implicit ev: Exists[Foo[_, U]], s: Search[L, U]): U = null.asInstanceOf[U]

тоже работает

Search[L, Boolean] //compiles
// Search[L, Double] //does not compile

Проверено в 2.13.0

libraryDependencies ++= Seq(
  scalaOrganization.value % "scala-reflect" % scalaVersion.value,
  scalaOrganization.value % "scala-compiler" % scalaVersion.value
)
person Dmytro Mitin    schedule 14.08.2019
comment
Это работает, но на данный момент я не рекомендую делать это с макросами Scala 2, тем более приведения к внутренним компонентам компилятора Scala 2, как вы это сделали здесь. Показанный вами не-макрос Search - это правильный путь, если время компиляции не является совершенно неприемлемым. - person Miles Sabin; 14.08.2019
comment
Спасибо Дмитрию, ожидал, что это можно сделать только с помощью макросов. Я также согласен с точкой зрения @MilesSabin, что это, вероятно, не принесет значительных улучшений времени компиляции по сравнению с моим алгоритмом Search, но, тем не менее, подумал, что это интересная проблема ... возможно ли такое в scala 3 без макросов? - person jbwheatley; 14.08.2019
comment
Это можно сделать с помощью специального встроенного и неявного сопоставления в Scala 3. В ветке shapeless-3 есть несколько примеров или связанных вещей. - person Miles Sabin; 15.08.2019
comment
@MilesSabin Не могли бы вы объяснить, как этого добиться? У меня inline def summon[T] <: T = given match { case t: T => t } given as Int = 1 given ambig as Int = 2 summon[Int] выдает ошибку ambiguous implicit arguments. Посмотрел ветку shapeless-3. Какой код оттуда использовать? - person Dmytro Mitin; 08.09.2019
comment
@DmytroMitin да, это приведет к двусмысленности. Я имел в виду, что класс типа Search может быть реализован с точки зрения специализации встроенных и неявных совпадений. - person Miles Sabin; 11.09.2019
comment
Можно было бы использовать тот факт, что Scala 3 различает неоднозначные имплициты и просто не найденные имплициты, хотя он также предоставляет свои собственные классы типов. - person user; 30.11.2020

В Scala 3 для этого можно использовать scala.util.Not (скоро будет NotGiven?), Который существует, если не найдено имплицитов данного типа:

implicit val b: Byte = 1
  
summon[Not[Not[Byte]]] //compiles

implicit val i: Int = 0
implicit val i2: Int = 2

summon[Not[Not[Int]]] //compiles

summon[Not[Not[String]]] //doesn't compile - Not[String] not found

См. Его в Scastie.

Ваш Exists класс типов теперь может выглядеть следующим образом (синтаксис для givens может скоро измениться, но вы поняли идею):

@annotation.implicitNotFound("No implicit of type ${T} was found")
trait Exists[T]
object Exists {
  given [T](using Not[Not[T]]) as Exists[T]
}

См. Его в Scastie.

person user    schedule 30.11.2020