1

I have an implicit conversion - below - which feels like it should definitely be working but is definitely not. Can anyone shed any light? I know implicitly can sometimes fail when type refinements are used - is that the issue here?

trait GetItem[A[_], T, R] {
  type Out
  def ret(a: A[T], ref: R): Out
}
object GetItem {
  implicit def ifRefIsInt[A[_], T]: GetItem[A, T, Int] { type Out = A[T] } = new GetItem[A, T, Int] {
    type Out = A[T]
    def ret(a: A[T], ref: Int): Out = a
  }
}
import GetItem._
//this works fine:
val t: GetItem[List, Double, Int] { type Out = List[Double] } = ifRefIsInt[List, Double]
// so does this:
implicitly[GetItem[List, Double, Int] { type Out = List[Double] }](t)
// this does not:
implicitly[GetItem[List, Double, Int] { type Out = List[Double] }] 
// Could not find implicit parameter for value e: Example.Main.GetItem[List, Double, Int]{type Out = List[Double]}

Any help much appreciated, I have been staring at this for some time with little success.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Chris J Harris
  • 1,597
  • 2
  • 14
  • 26

2 Answers2

2

Not sure why it doesn't work like that, however a good technique to solve this kind of problems is to use the Aux pattern to lift the type members into a type parameter, which improves resolution.

trait GetItem[A[_], T, R] {
  type Out
  def ret(a: A[T], ref: R): Out
}

object GetItem {
  type Aux[A[_], T, R, O] = GetItem[A, T, R] { type Out = O }

  implicit def ifRefIsInt[A[_], T]: Aux[A, T, Int, A[T]] = new GetItem[A, T, Int] {
    type Out = A[T]
    def ret(a: A[T], ref: Int): Out = a
  }
}

Which you can test like this:

implicitly[GetItem.Aux[List, Double, Int, List[Double]]]
// res: GetItem.Aux[List, Double, Int, List[Double]] = ...

implicitly[GetItem[List, Double, Int]] 
// res: GetItem[List, Double, Int] = ...
  • Thanks. I am a fan of the Aux pattern, I just wanted to keep the example simple. I'm honestly considering just restarting my PC and seeing if that makes a difference. – Chris J Harris Nov 13 '20 at 02:27
  • 1
    So (having restarted my PC to no effect), I am finding that your response (with the Aux pattern) works fine on my PC, whereas my original example does not. Which is a solution, but I am still bothered by the question of why the original isn't working. – Chris J Harris Nov 13 '20 at 02:36
2

Seems to be another example of over-constrained implicits (1 2 3 4 5 6). This seems to be too much work for implicits in a single step. Compiler doesn't like complex types like (A, B), H :: L, and A[T] (in our case) in a type refinement. If we replace

implicit def ifRefIsInt[A[_], T]: GetItem[A, T, Int] { type Out = A[T] } = 
  new GetItem[A, T, Int] {
    type Out = A[T]
    def ret(a: A[T], ref: Int): Out = a
  }

with

implicit def ifRefIsInt[A[_], T, O](implicit 
  ev: A[T] =:= O
): GetItem[A, T, Int] { type Out = O } = new GetItem[A, T, Int] {
  type Out = O
  def ret(a: A[T], ref: Int): Out = a
}

then

implicitly[GetItem.Aux[List, Double, Int, List[Double]]]
implicitly[GetItem[List, Double, Int] { type Out = List[Double] }]

compile: https://scastie.scala-lang.org/P5iXP2ZfQUCKEIMukYyqIg (Scala 2.13.3)

For some reason compiler swallows a warning (with -Xlog-implicits switched on). If I trigger implicit search manually

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

def foo[A]: Unit = macro fooImpl[A]

def fooImpl[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 typ = weakTypeOf[A]

  val search = new analyzer.ImplicitSearch(
    tree = EmptyTree.asInstanceOf[global.Tree],
    pt = typ.asInstanceOf[global.Type],
    isView = false,
    context0 = callsiteContext.makeImplicit(reportAmbiguousErrors = true),
    pos0 = c.enclosingPosition.asInstanceOf[scala.reflect.internal.util.Position]
  )

  println(s"allImplicits=${search.allImplicits}")

  q""
}

then

foo[GetItem[List, Double, Int] { type Out = List[Double] }]

produces a warning

App.this.GetItem.ifRefIsInt is not a valid implicit value for App.GetItem[List,Double,Int]{type Out = List[Double]} because:
hasMatchingSymbol reported error: polymorphic expression cannot be instantiated to expected type;
 found   : [A[_], T]App.GetItem[A,T,Int]{type Out = A[T]}
 required: App.GetItem[List,Double,Int]{type Out = List[Double]}

scalac: allImplicits=List()

i.e. A, T are not inferred.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • thanks! From the other linked examples I can faintly see what is meant by an over-constrained implicit but I don't think I trust myself to necessarily predict or diagnose them. In your view, would switching to Scala 3 help with these kind of issues? It seems to handle them better, and the project I am working on has few dependencies so can probably be moved over without any problems. – Chris J Harris Nov 13 '20 at 04:06
  • @Chrisper Well, many issues disappear when we replace Scalac by Dotty. But from the other side, in Dotty some issues arise while they are absent in Scalac. https://github.com/lampepfl/dotty/issues/8882 – Dmytro Mitin Nov 13 '20 at 04:22
  • https://users.scala-lang.org/t/how-to-understand-the-comment-in-shapeless-guide/8930 – Dmytro Mitin Mar 27 '23 at 05:07