Your combine method returns what is called a βdependent type of methodβ , which means that its return type depends on one of its arguments β in this case, as a path-dependent type that includes l in its path.
In many cases, the compiler will statically know something about the dependent return type, but in your example this is not so. I will try to explain why per second, but first consider the following simpler example:
scala> trait Foo { type A; def a: A } defined trait Foo scala> def fooA(foo: Foo): foo.A = foo.a fooA: (foo: Foo)foo.A scala> fooA(new Foo { type A = String; def a = "I'm a StringFoo" }) res0: String = I'm a StringFoo
The res0 type in res0 is String , since the compiler statically knows that A argument foo String . However, we cannot write one of the following:
scala> def fooA(foo: Foo): String = foo.a <console>:12: error: type mismatch; found : foo.A required: String def fooA(foo: Foo): String = foo.a ^ scala> def fooA(foo: Foo) = foo.a.substring <console>:12: error: value substring is not a member of foo.A def fooA(foo: Foo) = foo.a.substring ^
Because here the compiler does not statically know that foo.A is String .
Here's a more complex example:
sealed trait Baz { type A type B def b: B } object Baz { def makeBaz[T](t: T): Baz { type A = T; type B = T } = new Baz { type A = T type B = T def b = t } }
Now we know that it is impossible to create Baz with different types for A and B , but the compiler does not, so it will not accept the following:
scala> def bazB(baz: Baz { type A = String }): String = baz.b <console>:13: error: type mismatch; found : baz.B required: String def bazB(baz: Baz { type A = String }): String = baz.b ^
This is exactly what you see. If we look at the code in shapeless.ops.hlist , we can make sure that the LeftFolder created here will have the same type for In and Out , but the compiler cannot (or, rather, win a 't-it design solution) follow us in this reasoning, which means that we will not consider l.Out as a tuple without additional evidence.
Fortunately, this proof is pretty easy to provide thanks to LeftFolder.Aux , which is just an alias for LeftFolder with an Out element as a parameter of the fourth type:
def combine[A <: HList](columns: A, suffix: String, separator: String = " and ")( implicit l: LeftFolder.Aux[ A, (String, String, String), columnCombinator.type, (String, String, String) ] ): String = columns.foldLeft((suffix, separator, ""))(columnCombinator)._3
(You can also use the syntax of a type element with the plain old LeftFolder type in l , but that would make this signature even more messy.)
The columns.foldLeft(...)(...) still returns l.Out , but now the compiler statically knows that a tuple of strings.