Search code examples
scalaimplicitshapelesshlist

Shapeless HList fill based on length of type


Working with HLists of options, I am trying to generate an HList with all elements equal to None. However, there is a problem with the implicit resolution of the Fill object.

The natLength function stems from this SO answer: The length of HList type paremeter in terms of Nat.

type OL = Option[Double] :: Option[Int] :: Option[String] :: HNil


def emptyList[T <: HList: *->*[Option]#λ](length: Nat)(
  implicit fill: Fill.Aux[length.N, None.type, T]
): T = {
  HList.fill(length)(None)
}

def natLength[T <: HList](implicit length: Length[T]): length.Out = length()

emptyList[OL](natLength[OL])

The above code lets the compiler fail with this error:

could not find implicit value for parameter fill: 
shapeless.ops.hlist.Fill[shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]],None.type]{type Out = Option[Double] :: Option[Int] :: Option[String] :: shapeless.HNil}

Is what I am trying to achieve possible? Generating an HList only based on its type's length?


Solution

  • Standard way to debug implicits is to try to resolve them manually (explicitly) and look at compile errors.

    emptyList[OL](natLength[OL])(
      implicitly[*->*[Option]#λ[Option[Double] :: Option[Int] :: Option[String] :: HNil]],
      implicitly[Fill.Aux[_3, None.type, None.type :: None.type :: None.type :: HNil]],
    )
    

    produces

    Error: type mismatch;
     found   : shapeless.ops.hlist.Fill[shapeless.nat._3,None.type]{type Out = None.type :: None.type :: None.type :: shapeless.HNil}
        (which expands to)  shapeless.ops.hlist.Fill[shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]],None.type]{type Out = None.type :: None.type :: None.type :: shapeless.HNil}
     required: shapeless.ops.hlist.Fill[shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]],None.type]{type Out = Option[Double] :: Option[Int] :: Option[String] :: shapeless.HNil}
        implicitly[Fill.Aux[_3, None.type, None.type :: None.type :: None.type :: HNil]],
    

    What should be T in def emptyList[T <: HList: *->*[Option]#λ]...? Is it Option[Double] :: Option[Int] :: Option[String] :: HNil? Then why do you request implicit Fill.Aux[length.N, None.type, T] with Out-type being the same T while it should be None.type :: None.type :: None.type :: HNil.

    Try

    def emptyList(length: Nat)(
      implicit fill: Fill[length.N, None.type]
    ): fill.Out = {
      HList.fill(length)(None)
    }
    
    emptyList(natLength[OL]) //None :: None :: None :: HNil
    

    *->*[Option]#λ can be context bound in def natLength.