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?
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 aliasGetResult
. Remove importslick.jdbc.GetResult
and write in your source fileNow
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