scalashapelesshlist

Implicits not found when mapping a shapeless HList with a natural transformation


I'm manipulating shapeless records (hlists with field labels) of values of type Param[A]. Each of them can generate an upper bound of type ParamMatcher[A] and I've written this poly function to extract them:

  object getUpperBound extends (Param ~> ParamMatcher) {
    override def apply[T](param: Param[T]): ParamMatcher[T] = param.upperBound
  }

When I apply it to single params it works but when I try to map over a record I get an error about missing Mapper or MapValues implicits.

This is a snippet to reproduce it (I've tried with scala 2.13.8 and shapeless 2.3.9):

import shapeless._
import shapeless.record._
import shapeless.PolyDefns.~>
import shapeless.syntax.singleton._

object MinimalRepro extends App {

  trait Param[A] {
    def upperBound: ParamMatcher[A]
  }

  sealed abstract class ParamMatcher[A]
  object ParamMatcher {
    case class MatchAny[A]() extends ParamMatcher[A]
  }

  case class UnboundedParam[A]() extends Param[A] {
    override def upperBound: ParamMatcher[A] = ParamMatcher.MatchAny()
  }

  val stringParam = UnboundedParam[String]()
  val intParam = UnboundedParam[Int]()

  object getUpperBound extends (Param ~> ParamMatcher) {
    override def apply[T](param: Param[T]): ParamMatcher[T] = param.upperBound
  }

  println(getUpperBound(stringParam)) // OK
  println(getUpperBound(intParam)) // OK

  println((stringParam :: intParam :: HNil).map(getUpperBound)) // KO
  println((("name" ->> stringParam) :: ("retries" ->> intParam) :: HNil).mapValues(getUpperBound)) // KO
}

Solution

  • The reason implicit resolution fail is that UnboundedParam[Int] is a subtype of Param[Int]. I've solved it by writing getUpperBound as a Poly1 that requires a proof of subtyping with <:<:

      object getUpperBound extends Poly1 {
        implicit def aligned[A, P](implicit ev: P <:< Param[A]): Case.Aux[P, ParamMatcher[A]] =
          at(param => ev(param).upperBound)
      }