Simão Martins Simão Martins - 1 month ago 17
Scala Question

Shapeless: FnToProduct not working with a Type Constructor

Why does this works as expected:

def method1[L <: HList, M <: HList, F, R](list: L)(builder: F)
(implicit ev: Comapped.Aux[L, Seq, M],
fntp: FnToProduct.Aux[F, M => R]) = println("asd")

method1(Seq("asd") :: HNil) { (s: String) =>
6
}


But this does not?

def method2[L <: HList, M <: HList, F, R](list: L)(builder: F)
(implicit ev: Comapped.Aux[L, Seq, M],
fntp: FnToProduct.Aux[F, M => Seq[R]]) = println("asd")

method2(Seq("asd") :: HNil) { (s: String) =>
Seq(6)
}


It seems that adding a type constructor in the return type for the FnToProduct.Aux breaks it.

Answer

The issue is that when the compiler is trying to infer the type parameters for method1, it's going to find L and M and F with no problems, but then when it gets to R it's not going to be able to tell that the second parameter of the FnToProduct.Aux has the right shape to line up with M => Seq[R].

Off the top of my head I don't have a good explanation of why this happens in the second case and not the first, but it's a limitation you'll run into fairly often, and there are a couple of workarounds.

The first would be to leave the Aux off of the FnToProduct altogether. Depending on what your actual method is doing, this may be just fine, but if you need to refer to R explicitly it's not going to work.

The second workaround would be to let the return type of that second type parameter be inferred, and then ask for evidence that it's a Seq:

import shapeless._, ops.function.FnToProduct, ops.hlist.Comapped

def method2[L <: HList, M <: HList, F, S, R](list: L)(builder: F)
  (implicit
    ev: Comapped.Aux[L, Seq, M],
    fntp: FnToProduct.Aux[F, M => S],
    s: S <:< Seq[R]
  ) = println("asd")

method2(Seq("asd") :: HNil) { (s: String) => Seq(6) }

It's a little extra syntactic noise, but it works.