What does HList#foldLeft() return?

情到浓时终转凉″ 提交于 2019-12-03 14:13:20
Travis Brown

Your combine method returns what's called a "dependent method type", which just 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 it doesn't. I'll try to explain why in a 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

Here the inferred type of res0 is String, since the compiler statically knows that the A of the foo argument is String. We can't write either of the following, though:

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 doesn't 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's not possible to create a Baz with different types for A and B, but the compiler doesn't, so it won't 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're seeing. If we look at the code in shapeless.ops.hlist, we can convince ourselves that the LeftFolder we're creating here will have the same type for In and Out, but the compiler can't (or rather won't—it's a design decision) follow us in this reasoning, which means it won't let us treat l.Out as a tuple without more evidence.

Fortunately that evidence is pretty easy to provide thanks to LeftFolder.Aux, which is just an alias for LeftFolder with the Out type member as a fourth type parameter:

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 could also use the type member syntax with plain old LeftFolder in l's type, but that would make this signature even messier.)

The columns.foldLeft(...)(...) part still returns l.Out, but now the compiler statically knows that that's a tuple of strings.

After having read the complete answer from Travis, here is a little variation of his solution:

type CombineTuple = (String, String, String)

def combine[A <: HList](columns: A, suffix: String, separator: String = " and ")(
  implicit l: LeftFolder[
    A,
    CombineTuple,
    columnCombinator.type
  ]
): String =
    columns.foldLeft((suffix, separator, ""))(columnCombinator).asInstanceof[CombineTuple]._3

In this way, the implicit signature is shorter, as it is needed in many methods that call this one.

UPDATED: As Travis has explained in comments, it is bestter to use LeftFolder.Aux.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!