您的combine 方法返回所谓的"dependent method type",这只是意味着它的返回类型取决于它的一个参数——在这种情况下,它是一个路径相关类型,它的路径中包含l。
在许多情况下,编译器会静态地了解相关返回类型,但在您的示例中它不知道。我稍后会尝试解释原因,但首先考虑以下更简单的示例:
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
这里res0 的推断类型是String,因为编译器静态知道foo 参数的A 是String。但是,我们不能写以下任何一个:
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
^
因为这里编译器并不静态知道foo.A 是String。
这是一个更复杂的例子:
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
}
}
现在我们知道不可能为A和B创建不同类型的Baz,但编译器不会,所以它不会接受以下:
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
^
这正是您所看到的。如果我们查看shapeless.ops.hlist 中的代码,我们可以说服自己,我们在这里创建的LeftFolder 将具有与In 和Out 相同的类型,但是编译器不能(或者更确切地说不会——这是一个设计决定)遵循我们的推理,这意味着它不会让我们在没有更多证据的情况下将l.Out 视为一个元组。
幸运的是,感谢LeftFolder.Aux,提供证据非常容易,它只是LeftFolder 的别名,Out 类型成员作为第四个类型参数:
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
(您也可以在 l 的类型中使用带有普通旧 LeftFolder 的类型成员语法,但这会使此签名更加混乱。)
columns.foldLeft(...)(...) 部分仍然返回 l.Out,但现在编译器静态知道这是一个字符串元组。