Defining instances of a third-party typeclass, implicit not found but explicit works fine
Asked Answered
K

1

0

I'm working with Slick's GetResult typeclass and wanted to use Shapeless to derive instances of GetResult[Option[(A, B, C...)]]

What I want:

Given an implicit GetResult[Option[A]], GetResult[Option[B]], ...,
implicitly generate a GetResult[Option[(A, B, ...)]]

What I tried

trait CanGetOption[T] {
    def getOption: GetResult[Option[T]]
}
object CanGetOption {
    // convenience implicit resolver
    def apply[T](implicit canGetOption: CanGetOption[T]): CanGetOption[T] = canGetOption

    // base case: HNil
    implicit val getHNilOption: CanGetOption[HNil] = from(GetResult { _ => Some(HNil) })

    // recursion case: H :: Tail
    implicit def getHConsOption[H, Tail <: HList](
        implicit getHeadOption: GetResult[Option[H]], 
        canGetTailOption: CanGetOption[Tail]
    ): CanGetOption[H :: Tail] = from(GetResult[Option[H :: Tail]] { r =>
        val headOpt = getHeadOption(r)
        val tailOpt = canGetTailOption.getOption(r)
        for(head <- headOpt; tail <- tailOpt) yield head :: tail
    })

    // generic case: A, given a A <-> Repr conversion
    // I also tried moving this into a "LowPriorityImplicits" thing, just in case
    implicit def getGenericOption[A, Repr <: HList](
        implicit gen: Generic.Aux[A, Repr], 
        getReprOpt: CanGetOption[Repr]
    ): CanGetOption[A] = from(GetResult { r =>
        val reprOpt = getReprOpt.getOption(r)
        reprOpt.map(gen.from)
    })
}

implicit def resolveOptionGetter[T: CanGetOption]: GetResult[Option[T]] = 
    CanGetOption[T].getOption

Problem:

When I've imported the above, the resolveOptionGetter doesn't seem to be considered when searching for implicits:

scala> implicitly[GetResult[Option[(Int, Int)]]]
<console>:19: error: could not find implicit value for parameter e: scala.slick.jdbc.GetResult[Option[(Int, Int)]]
       implicitly[GetResult[Option[(Int, Int)]]]
             ^

scala> resolveOptionGetter[(Int, Int)]
res1: scala.slick.jdbc.GetResult[Option[(Int, Int)]] = <function1>

Why can't the compiler find resolveOptionGetter in the implicit search? What can I do to help it?

Kilby answered 4/10, 2018 at 19:16 Comment(3)
Where exactly is resolveOptionGetter defined? In your example it's not inside any object or class which is illegal.Monandry
@Monandry It was all in a container element, and I imported that all in my REPL session. I omitted it from the question to avoid over-indenting the already-long lines in it.Kilby
@Kilby I guess I finally found solution. I updated my answer.Beehive
B
1

The thing is that slick.jdbc.GetResult is covariant. If it were invariant, types would be inferred correctly and implicits would be resolved.

A workaround is hiding covariant slick.jdbc.GetResult with custom invariant type alias GetResult. Remove import slick.jdbc.GetResult and write in your source file

type GetResult[T] = slick.jdbc.GetResult[T]

object GetResult {
  def apply[T](implicit f: PositionedResult => T): GetResult[T] = slick.jdbc.GetResult.apply
}

Now implicitly[GetResult[Option[(Int, Int)]]] compiles. Tested in Scala 2.12.7 + Shapeless 2.3.3 + Slick 3.2.3.

Variance often makes troubles for implicit resolution:

https://github.com/scala/bug/issues/10099

https://github.com/locationtech/geotrellis/issues/1292

Implicit resolution with covariance

Beehive answered 5/10, 2018 at 11:36 Comment(2)
I'm accepting your updated answer since it addresses what I asked. Unfortunately it doesn't help with my actual usage, which is to construct instances of StaticQuery[R], where R belongs to the GetResult typeclass. Having the invariant version of GetResult imported instead of the main one doesn't seem to help with that use case. I still have to say implicit val getter = resolveOptionGetter[(Int, Int)] before I can call StaticQuery[Option[(Int, Int)]] :(Kilby
You can remove import slick.jdbc.StaticQuery as well as slick.jdbc.GetResult and write object StaticQuery { def apply[R: GetResult]: StaticQuery[Unit, R] = slick.jdbc.StaticQuery.apply }. Then StaticQuery[Option[(Int, Int)]] compiles in Scala 2.12.7 + Shapeless 2.3.3 + Slick 2.1.0.Beehive

© 2022 - 2024 — McMap. All rights reserved.