1
votes

I have the following method:

import shapeless._
import shapeless.UnaryTCConstraint._
def method[L <: HList : *->*[Seq]#λ](list: L) = println("checks")

It allows me to ensure the following happens:

val multipleTypes = "abc" :: 1 :: 5.5 :: HNil
val onlyLists = Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil

method(multipleTypes)  // throws could not find implicit value ...
method(onlyList) // prints checks

How can I augment method with another parameter list, something like:

def method2[L <: HList : *->*[Seq]#λ, M <: HList](list: L)(builder: M => String) = println("checks")

But with the restriction that the HList M must be of the same size as the HList L and only contain elements of the inner types of the HList L. Let me give an example:

// This should work
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
  case a :: 1 :: d :: HNil => "works"
}
// This should throw some error at compile time, because the second element is Seq[Int]
// therefore in the builder function I would like the second element to be of type Int.
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
  case a :: true :: d :: HNil => "fails"
}
// This should also fail because the HLists are not of the same length.
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
  case 1 :: d :: HNil => "fails"
}

If I define method2 like this:

def method2[L <: HList : *->*[Seq]#λ](list: L)(builder: L => String) = println("checks")

It almost solves the problem, the only thing that is missing is that builder will have elements of type Seq[T] instead of elements of type T.

1
What should be the result of method2(Seq("abc", "def") :: Seq(1,2,3) :: Seq(5.5) :: HNil) { case a :: b :: c :: HNil => a.toString + b.toString + c.toString } ?Kolmar
It should work since the HList is of the same size, and you made no restriction on the types of a, b and c, so they should be correctly inferred to be String, Int and Double.Simão Martins

1 Answers

3
votes

This is a case for ops.hlist.Comapped.

You'd want to define method2 as

def method2[L <: HList : *->*[Seq]#λ, M <: HList]
  (list: L)
  (builder: M => String)
  (implicit ev: Comapped.Aux[L, Seq, M])

But this won't work, because the type M should be calculated prior to typechecking the builder argument.

So the actual implementation becomes something like:

class Impl[L <: HList : *->*[Seq]#λ, M <: HList]
  (list: L)
  (implicit ev: Comapped.Aux[L, Seq, M]) 
{
  def apply(builder: M => String) = println("checks")
}

def method2[L <: HList : *->*[Seq]#λ, M <: HList]
  (list: L)
  (implicit ev: Comapped.Aux[L, Seq, M]) = new Impl[L, M](list)

And you can't call it directly. You may use an additional apply, or some other method to provide the implicit argument list implicitly:

method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil) apply {
  case a :: 1 :: d :: HNil => "works"
}