Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does HList#foldLeft() return?

I'm trying to play with HList's from Shapeless.

This is my first try:

trait Column[T] {
     val name: String
}

case class CV[T](col: Column[T], value: T)

object CV {
    object columnCombinator extends Poly2 {
        implicit def algo[A] = at[(String, String, String), CV[A]] { case ((suffix, separator, sql), cv) ⇒
            (suffix, separator, if (sql == "") cv.col.name+suffix else sql+separator+cv.col.name+suffix)
        }
    }

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

The problem is I don't know what foldLeft does return in this example.

I expect it to return (String, String, String), but the compiler tells me that returns l.Out. What is l.Out?

The source code is a little complicated to guess it.

There isn't much information in the web about this.

Some information I've consulted:

  • Shapeless Tests
  • Shapeless documentation
like image 306
david.perez Avatar asked Oct 28 '14 12:10

david.perez


1 Answers

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.

like image 50
Travis Brown Avatar answered Oct 13 '22 22:10

Travis Brown