Search code examples
scalatypesshapelessderivedhlist

Scala doesn't derive dependent type for shapeless HList (using Aux)


Given a type dependent Converter type class which can convert a String into an Integer:

trait Converter[From] {
  type To
  def to(from: From): To
}

object Converter {
  type Aux[From, Out0] = Converter[From] { type To = Out0 }

  implicit val forInteger = new Converter[Integer] {
    type To = String
    def to(value: Integer) = ???
  }
}

// this works
val single = the[Converter[Integer]]
implicitly[single.To =:= String]
val singleAux = the[Converter.Aux[Integer, String]]

I would like it to work for HLists, e.g. Integer :: HNil. In theory, all I need is implicits for HNil and HList:

implicit val forHNil = new Converter[HNil] {
  type To = HNil
  def to(value: HNil) = HNil
}

implicit def forHList[Head, HeadConverted, Tail <: HList, TailConverted <: HList](
  implicit
  hConverter: Converter.Aux[Head, HeadConverted],
  tConverter: Converter.Aux[Tail, TailConverted]
  ): Converter[Head :: Tail] = new Converter[Head :: Tail] {
    type To = HeadConverted :: TailConvertedHList
    def to(values: Head :: Tail) = ???
  }

The above works fine for HNil:

val hnil = the[Converter[HNil]]
implicitly[hnil.To =:= HNil]
val hnilAux = the[Converter.Aux[HNil, HNil]]

But not for an HList. Interestingly, it does find an instance, but does not derive the resulting type

val hlist = the[Converter[Integer :: HNil]]
type expected = String :: HNil
implicitly[hlist.To =:= expected] //fails
val hlistAux = the[Converter.Aux[Integer :: HNil, String :: HNil]] // fails

I've set it up in a self contained project which has a few more (failed) attempts to debug the issue: https://github.com/mpollmeier/shapeless-dependent-type-typeclass-problem


Solution

  • The problem is that I explicitly stated the return type of forHList as Converter[Head :: Tail]. That return type is incomplete, and it's missing exactly the part that I needed.

    Just changing the declared return type to Converter.Aux[Head :: Tail, HeadConverted :: TailConverted] (or even just leaving it out) fixes the problem.

    All credit for this answer goes to Miles Sabin, he mentioned the problem in the above comment. Thank you!