Is there a type-class that checks for existence of at least one implicit of a type?

by jwheat   Last Updated August 14, 2019 05:26 AM - source

I have a trait Foo[T, U] and a type-level algorithm that given an L <: HList and a target type U, tells me whether there exists T in L such that there is an implicit Foo[T, U] in scope. This is implemented using the following type class:

trait Search[L <: HList, U]

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

  ...
}

and we have the following:

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

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

  Search[L, Boolean] //compiles

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

What I would like is for the search to not take place at all if there is no Foo[T, U] for any T at all in scope, as then we already know that the algorithm will not complete. In other words, I want a type-class trait Exists[F[_]] for which instances exist if and only if there is at least one implicit F in scope, so the function Search.apply instead has signature:

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

In this case the compiler will only try to resolve s if there is any implicit Foo in scope.

Is such a type-class possible to define? Does one already exist?



Answers 1


Try

import scala.language.experimental.macros
import scala.reflect.macros.blackbox

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[scala.reflect.macros.contexts.Context]
    val global: context.universe.type = context.universe
    val analyzer: global.analyzer.type = global.analyzer
    val typerContext = 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 = typerContext,
      saveAmbiguousDivergent = true,
      pos = c.enclosingPosition.asInstanceOf[global.Position]
    )

    val isAmbiguous = typerContext.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")    
  }
}

Test

// 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

Tested in 2.13.0

Dmytro Mitin
Dmytro Mitin
August 14, 2019 05:18 AM

Related Questions