Search code examples
scalafunctional-programminggeneric-programmingshapelesstype-level-computation

Shapeless type inference for HList does not work


I'm trying to implement generic function for taking the first element:

import shapeless.ops.hlist.IsHCons
import shapeless.{Generic, HList}

object App {

  def main(args : Array[String]) {
    val a: Int = getFirst((1, 2, 3))
    val b: String = getFirst(("a", "b", 10, 45, "aaa"))
  }

  def getFirst[A, Head, Repr <: HList, Tail <: HList](input: A)
                                                     (implicit hcons: IsHCons.Aux[Repr, Head, Tail],
                                                      gen: Generic.Aux[A, Repr]): Head = gen.to(input).head
}

The problem is the compiler cannot correctly infer Repr and Tail and sets them to Nothing. Here is it:

Error:(9, 26) could not find implicit value for parameter gen: shapeless.Generic.Aux[(Int, Int, Int),Repr]
    val a: Int = getFirst((1, 2, 3))
Error:(9, 26) not enough arguments for method getFirst: (implicit hcons: shapeless.ops.hlist.IsHCons.Aux[Nothing :: Nothing,Head,Tail], implicit gen: shapeless.Generic.Aux[(Int, Int, Int),Nothing :: Nothing])Head.
Unspecified value parameter gen.
    val a: Int = getFirst((1, 2, 3))
Error:(10, 29) could not find implicit value for parameter gen: shapeless.Generic.Aux[(String, String, Int),Repr]
    val b: String = getFirst(("a", "b", 10))
Error:(10, 29) not enough arguments for method getFirst: (implicit hcons: shapeless.ops.hlist.IsHCons.Aux[Nothing :: Nothing,Head,Tail], implicit gen: shapeless.Generic.Aux[(String, String, Int),Nothing :: Nothing])Head.
Unspecified value parameter gen.
    val b: String = getFirst(("a", "b", 10))

What would be a solution to fix it? The following works fine for sure:

val a: Int = getFirst[(Int, Int, Int), Int, Int :: Int :: Int :: HNil, Int :: Int :: HNil]((1, 2, 3))

But this is extremely cumbersome and ugly.


Solution

  • The solution is to swap implicit parameter order - Generic first, IsHCons second.

      def getFirst[A, Head, Repr <: HList, Tail <: HList](input: A)
                                                         (implicit gen: Generic.Aux[A, Repr], hcons: IsHCons.Aux[Repr, Head, Tail]
                                                          ): Head = gen.to(input).head
    

    (scastie)

    I don't know how exactly this works, but I've seen that inferring type parameters via resolving implicits works left to right.

    So, with your signature, Scala first looks for IsHCons.Aux[Repr, Head, Tail], but it doesn't know much about Repr at this point, just that it is an HList. It finds some instance - probably for something like Nothing :: HList, and then tries to look for Generic.Aux[A, Nothing :: HList] (A is inferred from input), which fails since that is not a valid Generic for A.