3
votes

The method doesNotCompile accepts only HLists that contain only Label[A] entries. There is a Mapper that transforms a Label[A] to a String (to be precise: Const[String]#λ). However when I apply the mapper, the return type is ev1.Out. I know that's actually an HList of only Strings, but how can I convince the compiler?

import shapeless._
import shapeless.poly._
import shapeless.ops.hlist._
import shapeless.UnaryTCConstraint._

object Util {
  case class Label[A](name: String, value: A)

  object GetLabelName extends (Label ~> Const[String]#λ) {
    def apply[A](label: Label[A]) = label.name
  }
}

object Main {
  import Util._

  def bar(l: List[String]) = ???

  def compiles = {
    val names = "a" :: "b" :: HNil
    bar(names.toList)
  }

  // A is an HList whose members are all Label[_]
  def doesNotCompile[A <: HList : *->*[Label]#λ](labels: A)(
    implicit ev1: Mapper[GetLabelName.type, A]) = {
  // implicit ev1: Mapper[String, A]) = {

    val names = labels map GetLabelName
    // names is of type `ev1.Out` - I want it to be an HList of Strings

    bar(names.toList)
    // error: could not find implicit value for parameter toTraversableAux:
    // shapeless.ops.hlist.ToTraversable.Aux[ev1.Out,List,Lub]
  }
}

Here is the full gist incl. build.sbt - download and run sbt compile: https://gist.github.com/mpollmeier/6c53e375d88a32016250

1

1 Answers

5
votes

As the error says, you are missing a type class instance of ToTraversable without which you cannot call names.toList. We can solve this by adding an implicit parameter, but first we need to solve a problem with GetLabelName, since it can not be used when mapping over an HList :

scala> GetLabelName(Label("a", 5))
res3: String = a

scala> (Label("a", 5) :: HNil) map GetLabelName
java.lang.AbstractMethodError: GetLabelName$.caseUniv()Lshapeless/PolyDefns$Case;
  ... 43 elided

A solution is to create a new polymorphic function extending Poly1 :

object getLabelName extends Poly1 {
  implicit def caseLabel[T] = at[Label[T]](_.name)
}

Now we can add that implicit parameter to the function :

def bar(l: List[String]) = l

def labelNames[L <: HList : *->*[Label]#λ, M <: HList](labels: L)(implicit
  mapper: Mapper.Aux[getLabelName.type, L, M],
  trav: ToTraversable.Aux[M, List, String]
): List[String] = bar(labels.map(getLabelName).toList)

Which can be used as :

val labels = Label("a", 5) :: Label("b", 1d) :: Label("c", None) :: HNil
labelNames(labels) // List[String] = List(a, b, c)