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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With